@@ -24,13 +24,26 @@ def __getitem__(cls, version):
2424 # Use [] lookups to move from primary class to "versioned" classes
2525 # which are simple wrappers around the primary class but with a _version attr
2626 if cls ._class_version is not None :
27- return cls ._VERSIONS [None ].__getitem__ (version )
27+ return cls ._VERSIONS [None ][version ]
28+ if cls ._valid_versions is not None :
29+ if version < 0 :
30+ version += 1 + cls .max_version # support negative index, e.g., [-1]
31+ if not cls .min_version <= version <= cls .max_version :
32+ raise ValueError ('Invalid version! min=%d, max=%d' % (cls .min_version , cls .max_version ))
2833 klass_name = cls .__name__ + '_v' + str (version )
2934 if klass_name in cls ._VERSIONS :
3035 return cls ._VERSIONS [klass_name ]
3136 cls ._VERSIONS [klass_name ] = type (klass_name , tuple (cls .mro ()), {'_class_version' : version }, init = False )
3237 return cls ._VERSIONS [klass_name ]
3338
39+ def __len__ (cls ):
40+ # Maintain compatibility
41+ if cls ._valid_versions is None :
42+ raise RuntimeError ('Unable to calculate __len__ for class without valid_versions' )
43+ elif cls ._class_version is not None :
44+ raise TypeError ('len() only supported on primary message class (not versioned)' )
45+ return cls ._valid_versions [1 ] + 1
46+
3447
3548class ApiMessageMeta (VersionSubscriptable , SlotsBuilder ):
3649 def __new__ (metacls , name , bases , attrs , ** kw ):
@@ -58,7 +71,7 @@ def __init__(cls, name, bases, attrs, **kw):
5871
5972
6073class ApiMessage (DataContainer , metaclass = ApiMessageMeta , init = False ):
61- __slots__ = ('_header' , '_version' )
74+ __slots__ = ('_header' )
6275
6376 def __init_subclass__ (cls , ** kw ):
6477 super ().__init_subclass__ (** kw )
@@ -72,11 +85,17 @@ def __init_subclass__(cls, **kw):
7285 ResponseClassRegistry .register_response_class (weakref .proxy (cls ))
7386
7487 def __init__ (self , * args , ** kwargs ):
75- super ().__init__ (* args , ** kwargs )
7688 self ._header = None
7789 self ._version = None
7890 if 'version' in kwargs :
7991 self .API_VERSION = kwargs ['version' ]
92+ if len (args ) > 0 :
93+ untagged_fields = self ._struct .untagged_fields (self .API_VERSION )
94+ if len (args ) != len (untagged_fields ):
95+ raise RuntimeError ('Unable to init ApiMessage via positional args: unexpected len' )
96+ kwargs .update ({field .name : args [i ] for i , field in enumerate (untagged_fields )})
97+ args = ()
98+ super ().__init__ (* args , ** kwargs )
8099
81100 @classproperty
82101 def name (cls ): # pylint: disable=E0213
@@ -171,7 +190,13 @@ def encode_header(self, flexible=False):
171190 return self ._header .encode (flexible = flexible ) # pylint: disable=E1120
172191
173192 @classmethod
174- def parse_header (cls , data , flexible = False ):
193+ def parse_header (cls , data , version = None ):
194+ version = cls ._class_version if version is None else version
195+ if version is None :
196+ raise ValueError ('Version required to decode data' )
197+ elif not 0 <= version <= cls .max_version :
198+ raise ValueError ('Invalid version %s (max version is %s).' % (version , cls .max_version ))
199+ flexible = cls .flexible_version_q (version )
175200 return cls .header_class .decode (data , flexible = flexible ) # pylint: disable=E1101
176201
177202 def encode (self , version = None , header = False , framed = False ):
@@ -206,15 +231,15 @@ def decode(cls, data, version=None, header=False, framed=False):
206231 else :
207232 data_class = cls
208233
209- flexible = cls .flexible_version_q (version )
210234 if isinstance (data , bytes ):
211235 data = io .BytesIO (data )
212236 if framed :
213237 size = Int32 .decode (data )
214238 if header :
215- hdr = cls .parse_header (data , flexible = flexible )
239+ hdr = cls .parse_header (data , version = version )
216240 else :
217241 hdr = None
242+ flexible = cls .flexible_version_q (version )
218243 ret = cls ._struct .decode (data , version = version , compact = flexible , tagged = flexible , data_class = data_class )
219244 if hdr is not None :
220245 ret ._header = hdr
0 commit comments