From 28144329ec736e1e3594cede1094f0d830ece05c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 6 Oct 2022 08:05:39 -0400 Subject: [PATCH] Use enum.Flag for qualities in IDL parsing - clearer than using a set (#23020) * Use enum.Flag for qualities in IDL parsing - clearer than using a set * Update the syntax of union a bit: accept None and use or syntax to specify default value --- scripts/idl/generators/java/__init__.py | 4 +- scripts/idl/matter_idl_parser.py | 26 ++++++----- scripts/idl/matter_idl_types.py | 39 +++++++++-------- scripts/idl/test_matter_idl_parser.py | 57 ++++++++++++------------- scripts/idl/test_xml_parser.py | 26 +++++------ scripts/idl/zapxml/handlers/handlers.py | 22 +++++----- scripts/idl/zapxml/handlers/parsing.py | 8 ++-- 7 files changed, 95 insertions(+), 87 deletions(-) diff --git a/scripts/idl/generators/java/__init__.py b/scripts/idl/generators/java/__init__.py index 1c749f91cf4552..6f18e9629304e5 100644 --- a/scripts/idl/generators/java/__init__.py +++ b/scripts/idl/generators/java/__init__.py @@ -31,10 +31,10 @@ def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Union[str, No if field.is_list: return None # lists are always specific per cluster - if FieldQuality.NULLABLE in field.qualities: + if FieldQuality.NULLABLE & field.qualities: return None - if FieldQuality.OPTIONAL in field.qualities: + if FieldQuality.OPTIONAL & field.qualities: return None actual = ParseDataType(field.data_type, context) diff --git a/scripts/idl/matter_idl_parser.py b/scripts/idl/matter_idl_parser.py index a37dfb0a0e3586..e694a7a7aec7c9 100755 --- a/scripts/idl/matter_idl_parser.py +++ b/scripts/idl/matter_idl_parser.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import enum +import functools import logging from lark import Lark @@ -16,6 +17,12 @@ from matter_idl_types import * +def UnionOfAllFlags(flags_list): + if not flags_list: + return None + return functools.reduce(lambda a, b: a | b, flags_list) + + class AddServerClusterToEndpointTransform: """Provides an 'apply' method that can be run on endpoints to add a server cluster to the given endpoint. @@ -164,13 +171,13 @@ def attr_nosubscribe(self, _): return AttributeQuality.NOSUBSCRIBE def attribute_qualities(self, qualities): - return qualities + return UnionOfAllFlags(qualities) or AttributeQuality.NONE def struct_fabric_scoped(self, _): return StructQuality.FABRIC_SCOPED def struct_qualities(self, qualities): - return qualities + return UnionOfAllFlags(qualities) or StructQuality.NONE def critical_priority(self, _): return EventPriority.CRITICAL @@ -185,7 +192,7 @@ def event_fabric_sensitive(self, _): return EventQuality.FABRIC_SENSITIVE def event_qualities(selt, qualities): - return set(qualities) + return UnionOfAllFlags(qualities) or EventQuality.NONE def timed_command(self, _): return CommandQuality.TIMED_INVOKE @@ -194,14 +201,13 @@ def fabric_scoped_command(self, _): return CommandQuality.FABRIC_SCOPED def command_qualities(self, attrs): - # List because attrs is a tuple - return set(list(attrs)) + return UnionOfAllFlags(attrs) or CommandQuality.NONE def struct_field(self, args): # Last argument is the named_member, the rest # are qualities field = args[-1] - field.qualities = set(args[:-1]) + field.qualities = UnionOfAllFlags(args[:-1]) or FieldQuality.NONE return field def server_cluster(self, _): @@ -317,22 +323,20 @@ def ESCAPED_STRING(self, s): @v_args(inline=True) def attribute(self, qualities, definition_tuple): - - qualities = set(qualities) (definition, acl) = definition_tuple # until we support write only (and need a bit of a reshuffle) # if the 'attr_readonly == READABLE' is not in the list, we make things # read/write if AttributeQuality.READABLE not in qualities: - qualities.add(AttributeQuality.READABLE) - qualities.add(AttributeQuality.WRITABLE) + qualities |= AttributeQuality.READABLE + qualities |= AttributeQuality.WRITABLE return Attribute(definition=definition, qualities=qualities, **acl) @v_args(inline=True) def struct(self, qualities, id, *fields): - return Struct(name=id, qualities=set(qualities), fields=list(fields)) + return Struct(name=id, qualities=qualities, fields=list(fields)) @v_args(inline=True) def request_struct(self, value): diff --git a/scripts/idl/matter_idl_types.py b/scripts/idl/matter_idl_types.py index 99b06eb830ce3f..ba66e4cad643ad 100644 --- a/scripts/idl/matter_idl_types.py +++ b/scripts/idl/matter_idl_types.py @@ -21,22 +21,26 @@ def __init__(self, meta: Meta = None, line: int = None, column: int = None): self.column = column -class StructQuality(enum.Enum): +class StructQuality(enum.Flag): + NONE = 0 FABRIC_SCOPED = enum.auto() -class FieldQuality(enum.Enum): +class FieldQuality(enum.Flag): + NONE = 0 OPTIONAL = enum.auto() NULLABLE = enum.auto() FABRIC_SENSITIVE = enum.auto() -class CommandQuality(enum.Enum): +class CommandQuality(enum.Flag): + NONE = 0 TIMED_INVOKE = enum.auto() FABRIC_SCOPED = enum.auto() -class AttributeQuality(enum.Enum): +class AttributeQuality(enum.Flag): + NONE = 0 READABLE = enum.auto() WRITABLE = enum.auto() NOSUBSCRIBE = enum.auto() @@ -54,7 +58,8 @@ class EventPriority(enum.Enum): CRITICAL = enum.auto() -class EventQuality(enum.Enum): +class EventQuality(enum.Flag): + NONE = 0 FABRIC_SENSITIVE = enum.auto() @@ -99,36 +104,36 @@ class Field: code: int name: str is_list: bool = False - qualities: Set[FieldQuality] = field(default_factory=set) + qualities: FieldQuality = FieldQuality.NONE @property def is_optional(self): - return FieldQuality.OPTIONAL in self.qualities + return FieldQuality.OPTIONAL & self.qualities @property def is_nullable(self): - return FieldQuality.NULLABLE in self.qualities + return FieldQuality.NULLABLE & self.qualities @dataclass class Attribute: definition: Field - qualities: Set[AttributeQuality] = field(default_factory=set) + qualities: AttributeQuality = AttributeQuality.NONE readacl: AccessPrivilege = AccessPrivilege.VIEW writeacl: AccessPrivilege = AccessPrivilege.OPERATE default: Optional[Union[str, int]] = None @property def is_readable(self): - return AttributeQuality.READABLE in self.qualities + return AttributeQuality.READABLE & self.qualities @property def is_writable(self): - return AttributeQuality.WRITABLE in self.qualities + return AttributeQuality.WRITABLE & self.qualities @property def is_subscribable(self): - return AttributeQuality.NOSUBSCRIBE not in self.qualities + return not (AttributeQuality.NOSUBSCRIBE & self.qualities) @dataclass @@ -137,7 +142,7 @@ class Struct: fields: List[Field] tag: Optional[StructTag] = None code: Optional[int] = None # for responses only - qualities: Set[StructQuality] = field(default_factory=set) + qualities: StructQuality = StructQuality.NONE @dataclass @@ -147,11 +152,11 @@ class Event: code: int fields: List[Field] readacl: AccessPrivilege = AccessPrivilege.VIEW - qualities: Set[EventQuality] = field(default_factory=set) + qualities: EventQuality = EventQuality.NONE @property def is_fabric_sensitive(self): - return EventQuality.FABRIC_SENSITIVE in self.qualities + return EventQuality.FABRIC_SENSITIVE & self.qualities @dataclass @@ -180,12 +185,12 @@ class Command: code: int input_param: Optional[str] output_param: str - qualities: Set[CommandQuality] = field(default_factory=set) + qualities: CommandQuality = CommandQuality.NONE invokeacl: AccessPrivilege = AccessPrivilege.OPERATE @property def is_timed_invoke(self): - return CommandQuality.TIMED_INVOKE in self.qualities + return CommandQuality.TIMED_INVOKE & self.qualities @dataclass diff --git a/scripts/idl/test_matter_idl_parser.py b/scripts/idl/test_matter_idl_parser.py index 36d5c087ea58f4..d200c3914b616f 100755 --- a/scripts/idl/test_matter_idl_parser.py +++ b/scripts/idl/test_matter_idl_parser.py @@ -79,12 +79,11 @@ def test_global_struct(self): fields=[ Field( data_type=DataType(name="CHAR_STRING"), code=1, name="astring", ), - Field(data_type=DataType(name="CLUSTER_ID"), code=2, name="idlist", is_list=True, qualities=set( - [FieldQuality.OPTIONAL])), - Field(data_type=DataType(name="int"), code=0x123, name="valueThatIsNullable", qualities=set( - [FieldQuality.NULLABLE])), + Field(data_type=DataType(name="CLUSTER_ID"), code=2, name="idlist", + is_list=True, qualities=FieldQuality.OPTIONAL), + Field(data_type=DataType(name="int"), code=0x123, name="valueThatIsNullable", qualities=FieldQuality.NULLABLE), Field(data_type=DataType(name="char_string", max_length=123), - code=222, name="sized_string", qualities=set()), + code=222, name="sized_string"), ])] ) self.assertEqual(actual, expected) @@ -100,14 +99,14 @@ def test_fabric_scoped_struct(self): expected = Idl(structs=[ Struct(name='FabricStruct', - qualities={StructQuality.FABRIC_SCOPED}, + qualities=StructQuality.FABRIC_SCOPED, fields=[ Field( data_type=DataType(name="CHAR_STRING"), code=1, name="astring", ), - Field(data_type=DataType(name="CLUSTER_ID"), code=2, name="idlist", is_list=True, qualities=set( - [FieldQuality.OPTIONAL])), - Field(data_type=DataType(name="int"), code=0x123, name="nullablesensitive", qualities=set( - [FieldQuality.NULLABLE, FieldQuality.FABRIC_SENSITIVE])), + Field(data_type=DataType(name="CLUSTER_ID"), code=2, name="idlist", + is_list=True, qualities=FieldQuality.OPTIONAL), + Field(data_type=DataType(name="int"), code=0x123, name="nullablesensitive", + qualities=FieldQuality.NULLABLE | FieldQuality.FABRIC_SENSITIVE), ])] ) self.assertEqual(actual, expected) @@ -127,14 +126,14 @@ def test_cluster_attribute(self): name="MyCluster", code=0x321, attributes=[ - Attribute(qualities=set([AttributeQuality.READABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE, definition=Field( data_type=DataType(name="int8u"), code=1, name="roAttr")), - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="int32u"), code=123, name="rwAttr", is_list=True)), - Attribute(qualities=set([AttributeQuality.NOSUBSCRIBE, AttributeQuality.READABLE]), definition=Field( + Attribute(qualities=AttributeQuality.NOSUBSCRIBE | AttributeQuality.READABLE, definition=Field( data_type=DataType(name="int8s"), code=0xAA, name="nosub", is_list=True)), - Attribute(qualities=set([AttributeQuality.READABLE]), definition=Field( - data_type=DataType(name="int8s"), code=0xAB, name="isNullable", qualities=set([FieldQuality.NULLABLE]))), + Attribute(qualities=AttributeQuality.READABLE, definition=Field( + data_type=DataType(name="int8s"), code=0xAB, name="isNullable", qualities=FieldQuality.NULLABLE)), ] )]) self.assertEqual(actual, expected) @@ -152,9 +151,9 @@ def test_sized_attribute(self): name="MyCluster", code=1, attributes=[ - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="char_string", max_length=11), code=1, name="attr1")), - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="octet_string", max_length=33), code=2, name="attr2", is_list=True)), ] )]) @@ -176,25 +175,25 @@ def test_attribute_access(self): name="MyCluster", code=1, attributes=[ - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="int8s"), code=1, name="attr1"), readacl=AccessPrivilege.VIEW, writeacl=AccessPrivilege.OPERATE ), - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="int8s"), code=2, name="attr2"), readacl=AccessPrivilege.VIEW, writeacl=AccessPrivilege.OPERATE ), - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="int8s"), code=3, name="attr3"), readacl=AccessPrivilege.MANAGE ), - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="int8s"), code=4, name="attr4"), writeacl=AccessPrivilege.ADMINISTER ), - Attribute(qualities=set([AttributeQuality.READABLE, AttributeQuality.WRITABLE]), definition=Field( + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( data_type=DataType(name="int8s"), code=5, name="attr5"), readacl=AccessPrivilege.OPERATE, writeacl=AccessPrivilege.MANAGE @@ -234,13 +233,13 @@ def test_cluster_commands(self): input_param="InParam", output_param="OutParam"), Command(name="TimedCommand", code=0xab, input_param="InParam", output_param="DefaultSuccess", - qualities=set([CommandQuality.TIMED_INVOKE])), + qualities=CommandQuality.TIMED_INVOKE), Command(name="FabricScopedCommand", code=0xac, input_param="InParam", output_param="DefaultSuccess", - qualities=set([CommandQuality.FABRIC_SCOPED])), + qualities=CommandQuality.FABRIC_SCOPED), Command(name="FabricScopedTimedCommand", code=0xad, input_param="InParam", output_param="DefaultSuccess", - qualities=set([CommandQuality.TIMED_INVOKE, CommandQuality.FABRIC_SCOPED])), + qualities=CommandQuality.TIMED_INVOKE | CommandQuality.FABRIC_SCOPED), ], )]) self.assertEqual(actual, expected) @@ -272,7 +271,7 @@ def test_cluster_command_access(self): Command(name="TimedCommand", code=2, input_param="InParam", output_param="OutParam", invokeacl=AccessPrivilege.MANAGE, - qualities=set([CommandQuality.TIMED_INVOKE])), + qualities=CommandQuality.TIMED_INVOKE), Command(name="OutOnly", code=3, input_param=None, output_param="OutParam", invokeacl=AccessPrivilege.ADMINISTER, @@ -387,11 +386,11 @@ def test_fabric_sensitive_event(self): code=0x123, events=[ Event(priority=EventPriority.INFO, readacl=AccessPrivilege.VIEW, - name="Hello", code=1, fields=[], qualities={EventQuality.FABRIC_SENSITIVE}), + name="Hello", code=1, fields=[], qualities=EventQuality.FABRIC_SENSITIVE), Event(priority=EventPriority.DEBUG, readacl=AccessPrivilege.MANAGE, - name="GoodBye", code=2, fields=[], qualities={EventQuality.FABRIC_SENSITIVE}), + name="GoodBye", code=2, fields=[], qualities=EventQuality.FABRIC_SENSITIVE), Event(priority=EventPriority.DEBUG, readacl=AccessPrivilege.ADMINISTER, - name="AdminEvent", code=3, fields=[], qualities={EventQuality.FABRIC_SENSITIVE}), + name="AdminEvent", code=3, fields=[], qualities=EventQuality.FABRIC_SENSITIVE), ])]) self.assertEqual(actual, expected) diff --git a/scripts/idl/test_xml_parser.py b/scripts/idl/test_xml_parser.py index ea380224cb512f..5f8882a6bb5b65 100755 --- a/scripts/idl/test_xml_parser.py +++ b/scripts/idl/test_xml_parser.py @@ -87,11 +87,11 @@ def testCluster(self): code=0x1234, attributes=[ Attribute(definition=Field(data_type=DataType(name='INT32U'), code=11, name='SomeIntAttribute', - qualities={FieldQuality.NULLABLE}), qualities={AttributeQuality.READABLE}, + qualities=FieldQuality.NULLABLE), qualities=AttributeQuality.READABLE, readacl=AccessPrivilege.VIEW, writeacl=AccessPrivilege.OPERATE), Attribute(definition=Field(data_type=DataType(name='INT8U'), code=22, name='AttributeWithAccess', - qualities={FieldQuality.OPTIONAL}), - qualities={AttributeQuality.READABLE, AttributeQuality.WRITABLE}, readacl=AccessPrivilege.OPERATE, + qualities=FieldQuality.OPTIONAL), + qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, readacl=AccessPrivilege.OPERATE, writeacl=AccessPrivilege.MANAGE) ], structs=[ @@ -106,7 +106,7 @@ def testCluster(self): Field(data_type=DataType(name='INT8U'), code=1, name='dataPoint1'), Field(data_type=DataType(name='INT8U'), code=2, name='dataPoint2', - qualities={FieldQuality.OPTIONAL}) + qualities=FieldQuality.OPTIONAL) ], tag=StructTag.RESPONSE, code=0x44) ], @@ -181,23 +181,23 @@ def testFabricScopedAndSensitive(self): fields=[Field(data_type=DataType(name='node_id'), code=1, name='AdminNodeID', - qualities={FieldQuality.NULLABLE})], + qualities=FieldQuality.NULLABLE)], readacl=AccessPrivilege.ADMINISTER, - qualities={EventQuality.FABRIC_SENSITIVE})], + qualities=EventQuality.FABRIC_SENSITIVE)], structs=[Struct(name='FabricStruct', fields=[Field(data_type=DataType(name='int32u'), code=1, name='Field1', - qualities={FieldQuality.FABRIC_SENSITIVE}), + qualities=FieldQuality.FABRIC_SENSITIVE), Field(data_type=DataType(name='int32u'), code=3, name='Field3', - qualities={FieldQuality.FABRIC_SENSITIVE}), + qualities=FieldQuality.FABRIC_SENSITIVE), Field(data_type=DataType(name='int32u', max_length=None), code=10, name='Field10')], - qualities={StructQuality.FABRIC_SCOPED})], + qualities=StructQuality.FABRIC_SCOPED)], )])) def testStruct(self): @@ -224,7 +224,7 @@ def testStruct(self): ''') struct = Struct( name='SomeStruct', - qualities={StructQuality.FABRIC_SCOPED}, + qualities=StructQuality.FABRIC_SCOPED, fields=[ Field(data_type=DataType(name='int16u'), code=1, name='FirstMember'), Field(data_type=DataType(name='int32u'), code=2, name='SecondMember') @@ -241,9 +241,9 @@ def testStruct(self): data_type=DataType(name='SomeStruct'), code=123, name='FabricAttribute', - qualities={FieldQuality.NULLABLE} + qualities=FieldQuality.NULLABLE ), - qualities={AttributeQuality.READABLE}, + qualities=AttributeQuality.READABLE, readacl=AccessPrivilege.VIEW, writeacl=AccessPrivilege.OPERATE)]), ])) @@ -291,7 +291,7 @@ def testSkipsNotProcessedFields(self): code=0, name='Type', ), - qualities={AttributeQuality.READABLE}, + qualities=AttributeQuality.READABLE, readacl=AccessPrivilege.VIEW, writeacl=AccessPrivilege.OPERATE)]), ])) diff --git a/scripts/idl/zapxml/handlers/handlers.py b/scripts/idl/zapxml/handlers/handlers.py index a0f0978df8a0f3..6048b271ecf3ac 100644 --- a/scripts/idl/zapxml/handlers/handlers.py +++ b/scripts/idl/zapxml/handlers/handlers.py @@ -79,7 +79,7 @@ def __init__(self, context: Context, cluster: Cluster, attrs): ) if attrs.get('isFabricSensitive', "false").lower() == 'true': - self._event.qualities.add(EventQuality.FABRIC_SENSITIVE) + self._event.qualities |= EventQuality.FABRIC_SENSITIVE def GetNextProcessor(self, name: str, attrs): if name.lower() == 'field': @@ -95,10 +95,10 @@ def GetNextProcessor(self, name: str, attrs): ) if attrs.get('optional', "false").lower() == 'true': - field.qualities.add(FieldQuality.OPTIONAL) + field.qualities |= FieldQuality.OPTIONAL if attrs.get('isNullable', "false").lower() == 'true': - field.qualities.add(FieldQuality.NULLABLE) + field.qualities |= FieldQuality.NULLABLE self._event.fields.append(field) return BaseHandler(self.context, handled=HandledDepth.SINGLE_TAG) @@ -174,7 +174,7 @@ def __init__(self, context: Context, attrs): # - code not set because not a response if attrs.get('isFabricScoped', "false").lower() == 'true': - self._struct.qualities.add(StructQuality.FABRIC_SCOPED) + self._struct.qualities |= StructQuality.FABRIC_SCOPED def GetNextProcessor(self, name: str, attrs): if name.lower() == 'item': @@ -200,13 +200,13 @@ def GetNextProcessor(self, name: str, attrs): ) if attrs.get('optional', "false").lower() == 'true': - field.qualities.add(FieldQuality.OPTIONAL) + field.qualities |= FieldQuality.OPTIONAL if attrs.get('isNullable', "false").lower() == 'true': - field.qualities.add(FieldQuality.NULLABLE) + field.qualities |= FieldQuality.NULLABLE if attrs.get('isFabricSensitive', "false").lower() == 'true': - field.qualities.add(FieldQuality.FABRIC_SENSITIVE) + field.qualities |= FieldQuality.FABRIC_SENSITIVE self._struct.fields.append(field) return BaseHandler(self.context, handled=HandledDepth.SINGLE_TAG) @@ -369,10 +369,10 @@ def __init__(self, context: Context, cluster: Cluster, attrs): ) if attrs.get('isFabricScoped', 'false') == 'true': - self._command.qualities.add(CommandQuality.FABRIC_SCOPED) + self._command.qualities |= CommandQuality.FABRIC_SCOPED if attrs.get('mustUseTimedInvoke', 'false') == 'true': - self._command.qualities.add(CommandQuality.TIMED_INVOKE) + self._command.qualities |= CommandQuality.TIMED_INVOKE else: self._struct.tag = StructTag.RESPONSE @@ -394,10 +394,10 @@ def GetArgumentField(self, attrs): ) if attrs.get('optional', "false").lower() == 'true': - field.qualities.add(FieldQuality.OPTIONAL) + field.qualities |= FieldQuality.OPTIONAL if attrs.get('isNullable', "false").lower() == 'true': - field.qualities.add(FieldQuality.NULLABLE) + field.qualities |= FieldQuality.NULLABLE return field diff --git a/scripts/idl/zapxml/handlers/parsing.py b/scripts/idl/zapxml/handlers/parsing.py index 09deee57400d63..f9315f3dab6875 100644 --- a/scripts/idl/zapxml/handlers/parsing.py +++ b/scripts/idl/zapxml/handlers/parsing.py @@ -73,16 +73,16 @@ def AttrsToAttribute(attrs) -> Attribute: attribute = Attribute(definition=field) if attrs.get('optional', "false").lower() == 'true': - attribute.definition.qualities.add(FieldQuality.OPTIONAL) + attribute.definition.qualities |= FieldQuality.OPTIONAL if attrs.get('isNullable', "false").lower() == 'true': - attribute.definition.qualities.add(FieldQuality.NULLABLE) + attribute.definition.qualities |= FieldQuality.NULLABLE if attrs.get('readable', "true").lower() == 'true': - attribute.qualities.add(AttributeQuality.READABLE) + attribute.qualities |= AttributeQuality.READABLE if attrs.get('writable', "false").lower() == 'true': - attribute.qualities.add(AttributeQuality.WRITABLE) + attribute.qualities |= AttributeQuality.WRITABLE # TODO(#22937): NOSUBSCRIBE attribute tag is not available - could find no # clear source to get this info.