diff --git a/scripts/py_matter_idl/matter_idl/generators/bridge/__init__.py b/scripts/py_matter_idl/matter_idl/generators/bridge/__init__.py index 5325ce8043f10e..a4f7e60b537554 100644 --- a/scripts/py_matter_idl/matter_idl/generators/bridge/__init__.py +++ b/scripts/py_matter_idl/matter_idl/generators/bridge/__init__.py @@ -13,9 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging import os import re +from typing import Tuple, Union from matter_idl.generators import CodeGenerator, GeneratorStorage from matter_idl.generators.types import (BasicInteger, BasicString, FundamentalType, IdlBitmapType, IdlEnumType, IdlType, @@ -38,40 +38,39 @@ def create_lookup_context(idl: Idl, cluster: Cluster) -> TypeLookupContext: return TypeLookupContext(idl, cluster) -def get_field_info(definition: Field, cluster: Cluster, idl: Idl): +def get_field_info(definition: Field, cluster: Cluster, idl: Idl) -> Tuple[str, str, Union[str, int, None], str]: context = create_lookup_context(idl, cluster) actual = ParseDataType(definition.data_type, context) - if type(actual) == IdlEnumType or type(actual) == IdlBitmapType: + if isinstance(actual, (IdlEnumType, IdlBitmapType)): actual = actual.base_type - if type(actual) == BasicString: + if isinstance(actual, BasicString): return 'OctetString', 'char', actual.max_length, \ 'ZCL_%s_ATTRIBUTE_TYPE' % actual.idl_name.upper() - if type(actual) == BasicInteger: + if isinstance(actual, BasicInteger): name = actual.idl_name.upper() ty = "int%d_t" % actual.power_of_two_bits if not actual.is_signed: ty = "u" + ty return "", ty, actual.byte_count, "ZCL_%s_ATTRIBUTE_TYPE" % name - if type(actual) == FundamentalType: + if isinstance(actual, FundamentalType): if actual == FundamentalType.BOOL: return "", "bool", 1, "ZCL_BOOLEAN_ATTRIBUTE_TYPE" - if actual == FundamentalType.FLOAT: + elif actual == FundamentalType.FLOAT: return "", "float", 4, "ZCL_SINGLE_ATTRIBUTE_TYPE" - if actual == FundamentalType.DOUBLE: + elif actual == FundamentalType.DOUBLE: return "", "double", 8, "ZCL_DOUBLE_ATTRIBUTE_TYPE" - logging.warn('Unknown fundamental type: %r' % actual) - return None - if type(actual) == IdlType: + else: + raise Exception('Unknown fundamental type: %r' % actual) + if isinstance(actual, IdlType): return '', actual.idl_name, 'sizeof(%s)' % actual.idl_name, \ 'ZCL_STRUCT_ATTRIBUTE_TYPE' - logging.warn('Unknown type: %r' % actual) - return None + raise Exception('UNKNOWN TYPE: %s' % actual) -def get_raw_size_and_type(attr: Attribute, cluster: Cluster, idl: Idl): +def get_raw_size_and_type(attr: Attribute, cluster: Cluster, idl: Idl) -> str: container, cType, size, matterType = get_field_info( attr.definition, cluster, idl) if attr.definition.is_list: diff --git a/scripts/py_matter_idl/matter_idl/generators/java/__init__.py b/scripts/py_matter_idl/matter_idl/generators/java/__init__.py index 775281d4462567..e52913bc1d194b 100644 --- a/scripts/py_matter_idl/matter_idl/generators/java/__init__.py +++ b/scripts/py_matter_idl/matter_idl/generators/java/__init__.py @@ -17,7 +17,7 @@ import enum import logging import os -from typing import List, Set, Union +from typing import List, Optional, Set from matter_idl.generators import CodeGenerator, GeneratorStorage from matter_idl.generators.types import (BasicInteger, BasicString, FundamentalType, IdlBitmapType, IdlEnumType, IdlType, @@ -57,24 +57,22 @@ class GlobalType: ] -def _UnderlyingType(field: Field, context: TypeLookupContext) -> Union[str, None]: +def _UnderlyingType(field: Field, context: TypeLookupContext) -> Optional[str]: actual = ParseDataType(field.data_type, context) - if type(actual) == IdlEnumType: - actual = actual.base_type - elif type(actual) == IdlBitmapType: + if isinstance(actual, (IdlEnumType, IdlBitmapType)): actual = actual.base_type - if type(actual) == BasicString: + if isinstance(actual, BasicString): if actual.is_binary: return 'OctetString' else: return 'CharString' - elif type(actual) == BasicInteger: + elif isinstance(actual, BasicInteger): if actual.is_signed: return "Int{}s".format(actual.power_of_two_bits) else: return "Int{}u".format(actual.power_of_two_bits) - elif type(actual) == FundamentalType: + elif isinstance(actual, FundamentalType): if actual == FundamentalType.BOOL: return 'Boolean' elif actual == FundamentalType.FLOAT: @@ -87,7 +85,7 @@ def _UnderlyingType(field: Field, context: TypeLookupContext) -> Union[str, None return None -def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Union[str, None]: +def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Optional[str]: """Global names are used for generic callbacks shared across all clusters (e.g. for bool/float/uint32 and similar) """ @@ -246,7 +244,7 @@ def attributesWithSupportedCallback(attrs, context: TypeLookupContext): # except non-list structures if not attr.definition.is_list: underlying = ParseDataType(attr.definition.data_type, context) - if type(underlying) == IdlType: + if isinstance(underlying, IdlType): continue yield attr @@ -419,7 +417,7 @@ def get_underlying_enum(self): def boxed_java_type(self): t = ParseDataType(self.data_type, self.context) - if type(t) == FundamentalType: + if isinstance(t, FundamentalType): if t == FundamentalType.BOOL: return "Boolean" elif t == FundamentalType.FLOAT: @@ -428,23 +426,23 @@ def boxed_java_type(self): return "Double" else: raise Exception("Unknown fundamental type") - elif type(t) == BasicInteger: + elif isinstance(t, BasicInteger): # the >= 3 will include int24_t to be considered "long" if t.byte_count >= 3: return "Long" else: return "Integer" - elif type(t) == BasicString: + elif isinstance(t, BasicString): if t.is_binary: return "byte[]" else: return "String" - elif type(t) == IdlEnumType: + elif isinstance(t, IdlEnumType): if t.base_type.byte_count >= 3: return "Long" else: return "Integer" - elif type(t) == IdlBitmapType: + elif isinstance(t, IdlBitmapType): if t.base_type.byte_count >= 3: return "Long" else: @@ -463,7 +461,7 @@ def boxed_java_signature(self): t = ParseDataType(self.data_type, self.context) - if type(t) == FundamentalType: + if isinstance(t, FundamentalType): if t == FundamentalType.BOOL: return "Ljava/lang/Boolean;" elif t == FundamentalType.FLOAT: @@ -472,22 +470,22 @@ def boxed_java_signature(self): return "Ljava/lang/Double;" else: raise Exception("Unknown fundamental type") - elif type(t) == BasicInteger: + elif isinstance(t, BasicInteger): if t.byte_count >= 3: return "Ljava/lang/Long;" else: return "Ljava/lang/Integer;" - elif type(t) == BasicString: + elif isinstance(t, BasicString): if t.is_binary: return "[B" else: return "Ljava/lang/String;" - elif type(t) == IdlEnumType: + elif isinstance(t, IdlEnumType): if t.base_type.byte_count >= 3: return "Ljava/lang/Long;" else: return "Ljava/lang/Integer;" - elif type(t) == IdlBitmapType: + elif isinstance(t, IdlBitmapType): if t.base_type.byte_count >= 3: return "Ljava/lang/Long;" else: diff --git a/scripts/py_matter_idl/matter_idl/generators/types.py b/scripts/py_matter_idl/matter_idl/generators/types.py index 19fa8bf35fd770..65e09915703749 100644 --- a/scripts/py_matter_idl/matter_idl/generators/types.py +++ b/scripts/py_matter_idl/matter_idl/generators/types.py @@ -110,11 +110,11 @@ class IdlEnumType: @property def byte_count(self): - return self.base_type.byte_count() + return self.base_type.byte_count @property def bits(self): - return self.base_type.bits() + return self.base_type.bits @dataclass @@ -129,11 +129,11 @@ class IdlBitmapType: @property def byte_count(self): - return self.base_type.byte_count() + return self.base_type.byte_count @property def bits(self): - return self.base_type.bits() + return self.base_type.bits class IdlItemType(enum.Enum): @@ -353,7 +353,7 @@ def is_bitmap_type(self, name: str): return any(map(lambda s: s.name == name, self.all_bitmaps)) -def ParseDataType(data_type: DataType, lookup: TypeLookupContext) -> Union[BasicInteger, BasicString, FundamentalType, IdlType]: +def ParseDataType(data_type: DataType, lookup: TypeLookupContext) -> Union[BasicInteger, BasicString, FundamentalType, IdlType, IdlEnumType, IdlBitmapType]: """ Given a AST data type and a lookup context, match it to a type that can be later be used for generation. diff --git a/scripts/py_matter_idl/matter_idl/lint/lint_rules_parser.py b/scripts/py_matter_idl/matter_idl/lint/lint_rules_parser.py index 39461810f1e0d3..64edb2b6850264 100755 --- a/scripts/py_matter_idl/matter_idl/lint/lint_rules_parser.py +++ b/scripts/py_matter_idl/matter_idl/lint/lint_rules_parser.py @@ -4,7 +4,7 @@ import os import xml.etree.ElementTree from dataclasses import dataclass -from typing import List, Mapping +from typing import List, MutableMapping from lark import Lark from lark.visitors import Discard, Transformer, v_args @@ -21,7 +21,12 @@ RequiredCommandsRule) -def parseNumberString(n): +class ElementNotFoundError(Exception): + def __init__(self, name): + super().__init__(f"Could not find {name}") + + +def parseNumberString(n: str) -> int: if n.startswith('0x'): return int(n[2:], 16) else: @@ -59,7 +64,11 @@ def DecodeClusterFromXml(element: xml.etree.ElementTree.Element): # - attribute with side, code and optional attributes try: - name = element.find('name').text.replace(' ', '') + name = element.find('name') + if not name or not name.text: + raise ElementNotFoundError('name') + + name = name.text.replace(' ', '') required_attributes = [] required_commands = [] @@ -75,8 +84,9 @@ def DecodeClusterFromXml(element: xml.etree.ElementTree.Element): # or # myName... attr_name = attr.text - if attr.find('description') is not None: - attr_name = attr.find('description').text + description = attr.find('description') + if description is not None: + attr_name = description.text required_attributes.append( RequiredAttribute( @@ -94,9 +104,13 @@ def DecodeClusterFromXml(element: xml.etree.ElementTree.Element): required_commands.append(RequiredCommand( name=cmd.attrib["name"], code=parseNumberString(cmd.attrib['code']))) + code = element.find('code') + if not code: + raise Exception("Failed to find cluster code") + return DecodedCluster( name=name, - code=parseNumberString(element.find('code').text), + code=parseNumberString(code.text), required_attributes=required_attributes, required_commands=required_commands ) @@ -132,7 +146,7 @@ def __init__(self): "Required commands") # Map cluster names to the underlying code - self._cluster_codes: Mapping[str, int] = {} + self._cluster_codes: MutableMapping[str, int] = {} def GetLinterRules(self): return [self._required_attributes_rule, self._required_commands_rule] @@ -300,7 +314,7 @@ def CreateParser(file_name: str): @click.option( '--log-level', default='INFO', - type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), + type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False), help='Determines the verbosity of script output.') @click.argument('filename') def main(log_level, filename=None): diff --git a/scripts/py_matter_idl/matter_idl/lint/types.py b/scripts/py_matter_idl/matter_idl/lint/types.py index c2b07ddef9b1b4..08b59ac73bcfb5 100644 --- a/scripts/py_matter_idl/matter_idl/lint/types.py +++ b/scripts/py_matter_idl/matter_idl/lint/types.py @@ -14,16 +14,21 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import List, Mapping, Optional +from typing import List, MutableMapping, Optional from matter_idl.matter_idl_types import ClusterSide, Idl, ParseMetaData +class MissingIdlError(Exception): + def __init__(self): + super().__init__("Missing IDL data") + + @dataclass class LocationInFile: file_name: str - line: int - column: int + line: Optional[int] + column: Optional[int] def __init__(self, file_name: str, meta: ParseMetaData): self.file_name = file_name @@ -91,7 +96,7 @@ def _AddLintError(self, text, location): def _ParseLocation(self, meta: Optional[ParseMetaData]) -> Optional[LocationInFile]: """Create a location in the current file that is being parsed. """ - if not meta or not self._idl.parse_file_name: + if not meta or not self._idl or not self._idl.parse_file_name: return None return LocationInFile(self._idl.parse_file_name, meta) @@ -145,6 +150,9 @@ def _ServerClusterDefinition(self, name: str, location: Optional[LocationInFile] On error returns None and _lint_errors is updated internlly """ + if not self._idl: + raise MissingIdlError() + cluster_definition = [ c for c in self._idl.clusters if c.name == name and c.side == ClusterSide.SERVER ] @@ -161,6 +169,9 @@ def _ServerClusterDefinition(self, name: str, location: Optional[LocationInFile] return cluster_definition[0] def _LintImpl(self): + if not self._idl: + raise MissingIdlError() + for endpoint in self._idl.endpoints: cluster_codes = set() @@ -223,8 +234,8 @@ def __init__(self, name): super(RequiredCommandsRule, self).__init__(name) # Maps cluster id to mandatory cluster requirement - self._mandatory_commands: Mapping[int, - List[ClusterCommandRequirement]] = {} + self._mandatory_commands: MutableMapping[int, + List[ClusterCommandRequirement]] = {} def __repr__(self): result = "RequiredCommandsRule{\n" @@ -248,6 +259,9 @@ def RequireCommand(self, cmd: ClusterCommandRequirement): self._mandatory_commands[cmd.cluster_code] = [cmd] def _LintImpl(self): + if not self._idl: + raise MissingIdlError() + for cluster in self._idl.clusters: if cluster.side != ClusterSide.SERVER: continue # only validate server-side: diff --git a/scripts/py_matter_idl/matter_idl/matter_idl_parser.py b/scripts/py_matter_idl/matter_idl/matter_idl_parser.py index f533e82b686ee9..44a35fcd57e3c4 100755 --- a/scripts/py_matter_idl/matter_idl/matter_idl_parser.py +++ b/scripts/py_matter_idl/matter_idl/matter_idl_parser.py @@ -9,19 +9,19 @@ from lark.visitors import Transformer, v_args try: - from .matter_idl_types import (AccessPrivilege, Attribute, AttributeInstantiation, AttributeOperation, AttributeQuality, - AttributeStorage, Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, - DeviceType, Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, - ParseMetaData, ServerClusterInstantiation, Struct, StructQuality, StructTag) -except ImportError: + from matter_idl.matter_idl_types import AccessPrivilege +except ModuleNotFoundError: import os import sys - sys.path.append(os.path.abspath(os.path.dirname(__file__))) + sys.path.append(os.path.dirname( + os.path.dirname(os.path.abspath(__file__)))) - from matter_idl_types import (AccessPrivilege, Attribute, AttributeInstantiation, AttributeOperation, AttributeQuality, - AttributeStorage, Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, - DeviceType, Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, - ParseMetaData, ServerClusterInstantiation, Struct, StructQuality, StructTag) + from matter_idl.matter_idl_types import AccessPrivilege + +from matter_idl.matter_idl_types import (Attribute, AttributeInstantiation, AttributeOperation, AttributeQuality, AttributeStorage, + Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, DeviceType, + Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, + ParseMetaData, ServerClusterInstantiation, Struct, StructQuality, StructTag) def UnionOfAllFlags(flags_list): @@ -48,7 +48,8 @@ def appply_to_idl(self, idl: Idl, content: str): # A doc comment will apply to any supported element assuming it immediately # preceeds id (skipping whitespace) for item in self.supported_types(idl): - if item.parse_meta and item.parse_meta.start_pos == actual_pos: + meta = item.parse_meta + if meta and meta.start_pos == actual_pos: item.description = self.value return @@ -555,7 +556,7 @@ def CreateParser(skip_meta: bool = False): @click.option( '--log-level', default='INFO', - type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), + type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False), help='Determines the verbosity of script output.') @click.argument('filename') def main(log_level, filename=None): diff --git a/scripts/py_matter_idl/matter_idl/matter_idl_types.py b/scripts/py_matter_idl/matter_idl/matter_idl_types.py index 649fb5b03ee2b1..9e3b4beb53ed79 100644 --- a/scripts/py_matter_idl/matter_idl/matter_idl_types.py +++ b/scripts/py_matter_idl/matter_idl/matter_idl_types.py @@ -15,9 +15,9 @@ class ParseMetaData: def __init__(self, meta: Optional[Meta] = None, line: Optional[int] = None, column: Optional[int] = None, start_pos: Optional[int] = None): if meta: - self.line = meta.line - self.column = meta.column - self.start_pos = meta.start_pos + self.line = getattr(meta, 'line', None) + self.column = getattr(meta, 'column', None) + self.start_pos = getattr(meta, 'start_pos', None) else: self.line = line self.column = column diff --git a/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py b/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py index b91cd58b3db1e1..2a3cfea87e76b1 100755 --- a/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py +++ b/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py @@ -15,23 +15,22 @@ # limitations under the License. try: - from .matter_idl_parser import CreateParser - from .matter_idl_types import (AccessPrivilege, Attribute, AttributeInstantiation, AttributeQuality, AttributeStorage, Bitmap, - Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, DeviceType, Endpoint, - Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, ParseMetaData, - ServerClusterInstantiation, Struct, StructQuality, StructTag) -except ImportError: + from matter_idl.matter_idl_parser import CreateParser +except ModuleNotFoundError: import os import sys - sys.path.append(os.path.abspath(os.path.dirname(__file__))) + sys.path.append(os.path.dirname( + os.path.dirname(os.path.abspath(__file__)))) + + from matter_idl.matter_idl_parser import CreateParser - from matter_idl_parser import CreateParser - from matter_idl_types import (AccessPrivilege, Attribute, AttributeInstantiation, AttributeQuality, AttributeStorage, Bitmap, - Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, DeviceType, Endpoint, - Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, ParseMetaData, - ServerClusterInstantiation, Struct, StructQuality, StructTag) import unittest +from matter_idl.matter_idl_types import (AccessPrivilege, Attribute, AttributeInstantiation, AttributeQuality, AttributeStorage, + Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, DeviceType, + Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, + ParseMetaData, ServerClusterInstantiation, Struct, StructQuality, StructTag) + def parseText(txt, skip_meta=True): return CreateParser(skip_meta=skip_meta).parse(txt) diff --git a/scripts/py_matter_idl/matter_idl/xml_parser.py b/scripts/py_matter_idl/matter_idl/xml_parser.py index bae5cdf47a5b50..e4033f98d8a92e 100755 --- a/scripts/py_matter_idl/matter_idl/xml_parser.py +++ b/scripts/py_matter_idl/matter_idl/xml_parser.py @@ -49,7 +49,7 @@ '--log-level', default='INFO', show_default=True, - type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), + type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False), help='Determines the verbosity of script output.') @ click.option( '--no-print', diff --git a/scripts/py_matter_idl/matter_idl/zapxml/__init__.py b/scripts/py_matter_idl/matter_idl/zapxml/__init__.py index 519307f298bf4a..606abc6c9b7cb1 100644 --- a/scripts/py_matter_idl/matter_idl/zapxml/__init__.py +++ b/scripts/py_matter_idl/matter_idl/zapxml/__init__.py @@ -41,6 +41,7 @@ def __init__(self, include_meta_data=True): # Context persists across all self._context = Context() self._include_meta_data = include_meta_data + self._locator = None def PrepareParsing(self, filename): # This is a bit ugly: filename keeps changing during parse @@ -56,7 +57,7 @@ def Finish(self) -> Idl: return self._idl def startDocument(self): - if self._include_meta_data: + if self._include_meta_data and self._locator: self._context.locator = self._locator self._processing_stack = [ZapXmlHandler(self._context, self._idl)] diff --git a/scripts/py_matter_idl/matter_idl/zapxml/handlers/context.py b/scripts/py_matter_idl/matter_idl/zapxml/handlers/context.py index 20d4841391ef02..230016e1eb5b98 100644 --- a/scripts/py_matter_idl/matter_idl/zapxml/handlers/context.py +++ b/scripts/py_matter_idl/matter_idl/zapxml/handlers/context.py @@ -39,7 +39,7 @@ class ProcessingPath: and in general to report things like 'this path found but was not handled'. """ - def __init__(self, paths: List[str] = None): + def __init__(self, paths: Optional[List[str]] = None): if paths is None: paths = [] self.paths = paths diff --git a/scripts/py_matter_idl/matter_idl/zapxml/handlers/handlers.py b/scripts/py_matter_idl/matter_idl/zapxml/handlers/handlers.py index 8d55fc531609f1..b24406c1565991 100644 --- a/scripts/py_matter_idl/matter_idl/zapxml/handlers/handlers.py +++ b/scripts/py_matter_idl/matter_idl/zapxml/handlers/handlers.py @@ -13,7 +13,7 @@ # limitations under the License. import logging -from typing import Any +from typing import Any, Optional from matter_idl.matter_idl_types import (Attribute, Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, Struct, StructQuality, @@ -489,12 +489,12 @@ def EndProcessing(self): class ClusterHandler(BaseHandler): """Handles /configurator/cluster elements.""" - def __init__(self, context: Context, idl: Idl): + def __init__(self, context: Context, idl: Optional[Idl]): super().__init__(context) self._cluster = Cluster( side=ClusterSide.CLIENT, - name=None, - code=None, + name="NAME-MISSING", + code=-1, parse_meta=context.GetCurrentLocationMeta() ) self._idl = idl @@ -524,9 +524,11 @@ def GetNextProcessor(self, name: str, attrs): return BaseHandler(self.context) def EndProcessing(self): - if self._cluster.name is None: + if not self._idl: + raise Exception("Missing idl") + if self._cluster.name == "NAME-MISSING": raise Exception("Missing cluster name") - elif self._cluster.code is None: + elif self._cluster.code == -1: raise Exception("Missing cluster code") self._idl.clusters.append(self._cluster) diff --git a/scripts/py_matter_idl/matter_idl/zapxml/handlers/parsing.py b/scripts/py_matter_idl/matter_idl/zapxml/handlers/parsing.py index bdae86a512f4ea..09243a627fea5e 100644 --- a/scripts/py_matter_idl/matter_idl/zapxml/handlers/parsing.py +++ b/scripts/py_matter_idl/matter_idl/zapxml/handlers/parsing.py @@ -66,7 +66,7 @@ def AttrsToAttribute(attrs) -> Attribute: field = Field( data_type=data_type, code=ParseInt(attrs['code']), - name=None, + name='', is_list=(attrs['type'].lower() == 'array') )