11from  __future__ import  annotations 
22
3+ from  functools  import  lru_cache 
34import  math 
45
56from  typing  import  ClassVar 
89from  mcproto .buffer  import  Buffer 
910from  mcproto .protocol  import  StructFormat 
1011from  mcproto .types .abc  import  MCType 
11- from  attrs  import  define 
12+ from  attrs  import  define ,  field ,  Attribute ,  validators 
1213from  mcproto .protocol .utils  import  to_twos_complement 
1314
1415
1516@define  
1617class  FixedBitset (MCType ):
17-     """Represents a fixed-size bitset.""" 
18+     """Represents a fixed-size bitset. 
19+ 
20+     The size of the bitset must be defined using the :meth:`of_size` method. 
21+     Each :class:`FixedBitset` class is unique to its size, and the size must be defined before using the class. 
22+ 
23+     :param data: The bits of the bitset. 
24+     """ 
1825
1926    __BIT_COUNT : ClassVar [int ] =  - 1 
2027
21-     data : bytearray 
28+     @staticmethod  
29+     def  data_length_check (_self : FixedBitset , attribute : Attribute [bytearray ], value : bytearray ) ->  None :
30+         """Check that the data length matches the bitset size. 
31+ 
32+         :raises ValueError: If the data length doesn't match the bitset size. 
33+         """ 
34+         if  _self .__BIT_COUNT  ==  - 1 :
35+             raise  ValueError ("Bitset size is not defined." )
36+         if  len (value ) !=  math .ceil (_self .__BIT_COUNT  /  8 ):
37+             raise  ValueError (f"Bitset size is { _self .__BIT_COUNT }  , but data length is { len (value )}  ." )
38+ 
39+     data : bytearray  =  field (validator = data_length_check .__get__ (object ))
2240
2341    @override  
2442    def  serialize_to (self , buf : Buffer ) ->  None :
@@ -32,18 +50,14 @@ def deserialize(cls, buf: Buffer) -> FixedBitset:
3250        data  =  buf .read (math .ceil (cls .__BIT_COUNT  /  8 ))
3351        return  cls (data = data )
3452
35-     @override  
36-     def  validate (self ) ->  None :
37-         """Validate the bitset.""" 
38-         if  self .__BIT_COUNT  ==  - 1 :
39-             raise  ValueError ("Bitset size is not defined." )
40-         if  len (self .data ) !=  math .ceil (self .__BIT_COUNT  /  8 ):
41-             raise  ValueError (f"Bitset size is { len (self .data ) *  8 }  , expected { self .__BIT_COUNT }  ." )
42- 
4353    @staticmethod  
54+     @lru_cache (maxsize = None ) 
4455    def  of_size (n : int ) ->  type [FixedBitset ]:
4556        """Return a new FixedBitset class with the given size. 
4657
58+         The result of this method is cached, so calling it multiple times with the same value will return the same 
59+         class. 
60+ 
4761        :param n: The size of the bitset. 
4862        """ 
4963        new_class  =  type (f"FixedBitset{ n }  " , (FixedBitset ,), {})
@@ -113,8 +127,17 @@ class Bitset(MCType):
113127    :param data: The bits of the bitset. 
114128    """ 
115129
116-     size : int 
117-     data : list [int ]
130+     @staticmethod  
131+     def  data_length_check (_self : Bitset , attribute : Attribute [list [int ]], value : list [int ]) ->  None :
132+         """Check that the data length matches the bitset size. 
133+ 
134+         :raises ValueError: If the data length doesn't match the bitset size. 
135+         """ 
136+         if  len (value ) !=  _self .size :
137+             raise  ValueError (f"Bitset size is { _self .size }  , but data length is { len (value )}  ." )
138+ 
139+     size : int  =  field (validator = validators .gt (0 ))
140+     data : list [int ] =  field (validator = data_length_check .__get__ (object ))
118141
119142    @override  
120143    def  serialize_to (self , buf : Buffer ) ->  None :
@@ -129,12 +152,6 @@ def deserialize(cls, buf: Buffer) -> Bitset:
129152        data  =  [buf .read_value (StructFormat .LONGLONG ) for  _  in  range (size )]
130153        return  cls (size = size , data = data )
131154
132-     @override  
133-     def  validate (self ) ->  None :
134-         """Validate the bitset.""" 
135-         if  self .size  !=  len (self .data ):
136-             raise  ValueError (f"Bitset size is ({ self .size }  ) doesn't match data size ({ len (self .data )}  )." )
137- 
138155    @classmethod  
139156    def  from_int (cls , n : int , size : int  |  None  =  None ) ->  Bitset :
140157        """Return a new Bitset with the given integer value. 
0 commit comments