2020_TBytesLength_Arg = typing .TypeVar ("_TBytesLength_Arg" , bound = int )
2121
2222
23+ def _create_class (cls : type , length : int ) -> type :
24+ cls_name = f"{ cls .__name__ } [{ length } ]"
25+ return types .new_class (
26+ cls_name ,
27+ bases = (cls ,),
28+ exec_body = lambda ns : ns .update (
29+ _length = length ,
30+ ),
31+ )
32+
33+
2334class _FixedBytesMeta (type ):
2435 __concrete__ : typing .ClassVar [dict [type , type ]] = {}
2536
2637 # get or create a type that is parametrized with element_t and length
2738 def __getitem__ (cls , length_t : type ) -> type :
39+ if length_t == typing .Any :
40+ return cls
41+
2842 cache = cls .__concrete__
2943 if c := cache .get (length_t , None ):
3044 return c
3145
3246 length = get_int_literal_from_type_generic (length_t )
33- cls_name = f"{ cls .__name__ } [{ length } ]"
34- cache [length_t ] = c = types .new_class (
35- cls_name ,
36- bases = (cls ,),
37- exec_body = lambda ns : ns .update (
38- _length = length ,
39- ),
40- )
47+ cache [length_t ] = c = _create_class (cls , length )
4148
4249 return c
4350
@@ -63,9 +70,13 @@ def __init__(self, value: Bytes | bytes | None = None, /):
6370 if value is None :
6471 self .value = b"\x00 " * self ._length
6572 return
73+
6674 self .value = as_bytes (value )
75+ if not hasattr (self , "_length" ):
76+ self ._length = len (self .value )
77+
6778 if len (self .value ) != self ._length :
68- raise TypeError (f"expected value of length { self ._length } , not { len (self .value )} " )
79+ raise ValueError (f"expected value of length { self ._length } , not { len (self .value )} " )
6980
7081 def __repr__ (self ) -> str :
7182 return repr (self .value )
@@ -74,10 +85,10 @@ def __str__(self) -> str:
7485 return str (self .value )
7586
7687 def __bool__ (self ) -> bool :
77- return bool (self .value )
88+ return bool (self ._length )
7889
7990 def __len__ (self ) -> int :
80- return len ( self .value )
91+ return self ._length
8192
8293 # mypy suggests due to Liskov below should be other: object
8394 # need to consider ramifications here, ignoring it for now
@@ -96,25 +107,19 @@ def __hash__(self) -> int:
96107 def __add__ (self , other : FixedBytes [_TBytesLength_Arg ] | Bytes | bytes ) -> Bytes :
97108 """Concatenate FixedBytes with another Bytes or bytes literal e.g.
98109 `FixedBytes[typing.Literal[5]](b"Hello ") + b"World"`."""
99- if isinstance (other , (Bytes | FixedBytes )):
100- return _checked_result (self .value + other .value , "+" )
101- else :
102- result = self .value + as_bytes (other )
103- return _checked_result (result , "+" )
110+ result = self .value + as_bytes (other )
111+ return _checked_result (result , "+" )
104112
105- def __radd__ (self , other : Bytes | bytes ) -> Bytes :
113+ def __radd__ (self , other : FixedBytes [ _TBytesLength_Arg ] | Bytes | bytes ) -> Bytes :
106114 """Concatenate FixedBytes with another Bytes or bytes literal e.g. `b"Hello " +
107115 FixedBytes[typing.Literal[5]](b"World")`."""
108- if isinstance (other , (Bytes | FixedBytes )):
109- return _checked_result (other .value + self .value , "+" )
110- else :
111- result = as_bytes (other ) + self .value
112- return _checked_result (result , "+" )
116+ result = as_bytes (other ) + self .value
117+ return _checked_result (result , "+" )
113118
114119 @property
115120 def length (self ) -> UInt64 :
116121 """Returns the length of the Bytes."""
117- return UInt64 (len ( self .value ) )
122+ return UInt64 (self ._length )
118123
119124 def __getitem__ (
120125 self , index : UInt64 | int | slice
@@ -126,6 +131,8 @@ def __getitem__(
126131 else :
127132 int_index = index .value if isinstance (index , UInt64 ) else index
128133 int_index = len (self .value ) + int_index if int_index < 0 else int_index
134+ if (int_index >= len (self .value )) or (int_index < 0 ):
135+ raise ValueError ("FixedBytes index out of range" )
129136 # my_bytes[0:1] => b'j' whereas my_bytes[0] => 106
130137 return Bytes (self .value [slice (int_index , int_index + 1 )])
131138
@@ -139,55 +146,77 @@ def __reversed__(self) -> Iterator[Bytes]:
139146 return _FixedBytesIter (self , - 1 )
140147
141148 @typing .overload
142- def __and__ (self , other : FixedBytes [_TBytesLength ]) -> FixedBytes [_TBytesLength ]: # type: ignore[overload-overlap]
143- ...
144-
149+ def __and__ (self , other : typing .Self ) -> typing .Self : ... # type: ignore[overload-overlap]
145150 @typing .overload
146- def __and__ (self , other : FixedBytes [typing .Any ] | bytes | Bytes ) -> Bytes : ...
147-
148- def __and__ (
149- self , other : FixedBytes [typing .Any ] | bytes | Bytes
150- ) -> FixedBytes [_TBytesLength ] | Bytes :
151+ def __and__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> Bytes : ...
152+ def __and__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> typing .Self | Bytes :
151153 """Compute the bitwise AND of the FixedBytes with another FixedBytes, Bytes, or
152154 bytes.
153155
154156 Returns FixedBytes if other has the same length, otherwise returns Bytes.
155157 """
156158 return self ._operate_bitwise (other , "and_" )
157159
158- def __rand__ (self , other : FixedBytes [typing .Any ] | bytes | Bytes ) -> Bytes :
160+ @typing .overload
161+ def __rand__ (self , other : typing .Self ) -> typing .Self : ... # type: ignore[overload-overlap]
162+ @typing .overload
163+ def __rand__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> Bytes : ...
164+ def __rand__ (
165+ self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes
166+ ) -> typing .Self | Bytes :
159167 return self & other
160168
161- @typing .overload
162- def __or__ (self , other : FixedBytes [_TBytesLength ]) -> FixedBytes [_TBytesLength ]: # type: ignore[overload-overlap]
163- ...
169+ def __iand__ (self , other : Bytes | typing .Self | bytes ) -> typing .Self : # type: ignore[misc]
170+ other_bytes = as_bytes (other )
171+ other_fixed_bytes = self .__class__ (other_bytes )
172+ result = self ._operate_bitwise (other_fixed_bytes , "and_" )
173+ assert isinstance (result , self .__class__ )
174+ return result
164175
165176 @typing .overload
166- def __or__ (self , other : FixedBytes [typing .Any ] | bytes | Bytes ) -> Bytes : ...
167-
168- def __or__ (
169- self , other : FixedBytes [typing .Any ] | bytes | Bytes
170- ) -> FixedBytes [_TBytesLength ] | Bytes :
177+ def __or__ (self , other : typing .Self ) -> typing .Self : ... # type: ignore[overload-overlap]
178+ @typing .overload
179+ def __or__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> Bytes : ...
180+ def __or__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> typing .Self | Bytes :
171181 return self ._operate_bitwise (other , "or_" )
172182
173- def __ror__ (self , other : FixedBytes [typing .Any ] | bytes | Bytes ) -> Bytes :
183+ @typing .overload
184+ def __ror__ (self , other : typing .Self ) -> typing .Self : ... # type: ignore[overload-overlap]
185+ @typing .overload
186+ def __ror__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> Bytes : ...
187+ def __ror__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> typing .Self | Bytes :
174188 return self | other
175189
176- @typing .overload
177- def __xor__ (self , other : FixedBytes [_TBytesLength ]) -> FixedBytes [_TBytesLength ]: # type: ignore[overload-overlap]
178- ...
190+ def __ior__ (self , other : Bytes | typing .Self | bytes ) -> typing .Self : # type: ignore[misc]
191+ other_bytes = as_bytes (other )
192+ other_fixed_bytes = self .__class__ (other_bytes )
193+ result = self ._operate_bitwise (other_fixed_bytes , "or_" )
194+ assert isinstance (result , self .__class__ )
195+ return result
179196
180197 @typing .overload
181- def __xor__ (self , other : FixedBytes [typing .Any ] | bytes | Bytes ) -> Bytes : ...
182-
183- def __xor__ (
184- self , other : FixedBytes [typing .Any ] | bytes | Bytes
185- ) -> FixedBytes [_TBytesLength ] | Bytes :
198+ def __xor__ (self , other : typing .Self ) -> typing .Self : ... # type: ignore[overload-overlap]
199+ @typing .overload
200+ def __xor__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> Bytes : ...
201+ def __xor__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> typing .Self | Bytes :
186202 return self ._operate_bitwise (other , "xor" )
187203
188- def __rxor__ (self , other : FixedBytes [typing .Any ] | bytes | Bytes ) -> Bytes :
204+ @typing .overload
205+ def __rxor__ (self , other : typing .Self ) -> typing .Self : ... # type: ignore[overload-overlap]
206+ @typing .overload
207+ def __rxor__ (self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ) -> Bytes : ...
208+ def __rxor__ (
209+ self , other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes
210+ ) -> typing .Self | Bytes :
189211 return self ^ other
190212
213+ def __ixor__ (self , other : Bytes | typing .Self | bytes ) -> typing .Self : # type: ignore[misc]
214+ other_bytes = as_bytes (other )
215+ other_fixed_bytes = self .__class__ (other_bytes )
216+ result = self ._operate_bitwise (other_fixed_bytes , "xor" )
217+ assert isinstance (result , self .__class__ )
218+ return result
219+
191220 def __invert__ (self ) -> typing .Self :
192221 """Compute the bitwise inversion of the Bytes.
193222
@@ -198,9 +227,9 @@ def __invert__(self) -> typing.Self:
198227
199228 def _operate_bitwise (
200229 self ,
201- other : FixedBytes [typing . Any ] | bytes | Bytes ,
230+ other : FixedBytes [_TBytesLength_Arg ] | bytes | Bytes ,
202231 operator_name : str ,
203- ) -> FixedBytes [ _TBytesLength ] | Bytes :
232+ ) -> typing . Self | Bytes :
204233 op = getattr (operator , operator_name )
205234 maybe_bytes = as_bytes (other )
206235 # pad the shorter of self.value and other bytes with leading zero
@@ -227,27 +256,32 @@ def from_base32(cls, value: str) -> typing.Self:
227256 """Creates Bytes from a base32 encoded string e.g.
228257 `Bytes.from_base32("74======")`"""
229258 bytes_value = base64 .b32decode (value )
230- return cls (bytes_value )
259+ c = _create_class (cls , len (bytes_value )) if not hasattr (cls , "_length" ) else cls
260+ return c (bytes_value ) # type: ignore[no-any-return]
231261
232262 @classmethod
233263 def from_base64 (cls , value : str ) -> typing .Self :
234264 """Creates Bytes from a base64 encoded string e.g.
235265 `Bytes.from_base64("RkY=")`"""
236266 bytes_value = base64 .b64decode (value )
237- return cls (bytes_value )
267+ c = _create_class (cls , len (bytes_value )) if not hasattr (cls , "_length" ) else cls
268+ return c (bytes_value ) # type: ignore[no-any-return]
238269
239270 @classmethod
240271 def from_hex (cls , value : str ) -> typing .Self :
241272 """Creates Bytes from a hex/octal encoded string e.g. `Bytes.from_hex("FF")`"""
242273 bytes_value = base64 .b16decode (value )
243- return cls (bytes_value )
274+ c = _create_class (cls , len (bytes_value )) if not hasattr (cls , "_length" ) else cls
275+ return c (bytes_value ) # type: ignore[no-any-return]
244276
245277 @classmethod
246278 def from_bytes (cls , value : Bytes | bytes ) -> typing .Self :
247279 """Construct an instance from the underlying bytes (no validation)"""
248- result = cls ()
249- result .value = as_bytes (value )
250- return result
280+ bytes_value = as_bytes (value )
281+ c = _create_class (cls , len (bytes_value )) if not hasattr (cls , "_length" ) else cls
282+ result = c ()
283+ result .value = bytes_value
284+ return result # type: ignore[no-any-return]
251285
252286 @property
253287 def bytes (self ) -> Bytes :
0 commit comments