Skip to content

Commit b1ab4a1

Browse files
committed
Address review feedback: fix field duplication and refactor type resolution
1 parent 1fa9d54 commit b1ab4a1

File tree

2 files changed

+41
-29
lines changed

2 files changed

+41
-29
lines changed

asyncua/common/structures104.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ def setter(self, value):
196196
return property(getter, setter)
197197

198198

199-
def make_structure(data_type: ua.NodeId, struct_name: str, sdef: ua.StructureDefinition, log_error: bool = True) -> dict[str, type]:
199+
def make_structure(
200+
data_type: ua.NodeId, struct_name: str, sdef: ua.StructureDefinition, log_error: bool = True
201+
) -> dict[str, type]:
200202
"""
201203
given a StructureDefinition object, generate Python class
202204
"""
@@ -235,9 +237,6 @@ def make_structure(data_type: ua.NodeId, struct_name: str, sdef: ua.StructureDef
235237

236238
for idx, sfield in enumerate(sdef.Fields, start=1):
237239
fname = clean_name(sfield.Name)
238-
if fname in seen_names:
239-
_logger.warning("Field name %s is duplicated in structure %s, renaming to %s_%s", fname, struct_name, fname, idx)
240-
fname = f"{fname}_{idx}"
241240
seen_names.add(fname)
242241
if sfield.DataType.NamespaceIndex == 0 and sfield.DataType.Identifier in ua.ObjectIdNames:
243242
if sfield.DataType.Identifier == 24:
@@ -409,18 +408,20 @@ def __str__(self):
409408
return f"<{self.__class__.__name__}: {self.name!r}>"
410409

411410

412-
async def _recursive_parse(server, base_node, dtypes, parent_sdef=None, add_existing=False):
411+
async def _recursive_parse(server, base_node, dtypes, parent_sdef=None, add_existing=False) -> None:
413412
ch = await base_node.get_children_descriptions(refs=ua.ObjectIds.HasSubtype)
414413

415414
requests = [_read_data_type_definition(server, desc, read_existing=add_existing) for desc in ch]
416415
results = await asyncio.gather(*requests)
417416

418-
def __add_recursion(sdef, desc):
417+
def __add_recursion(sdef, desc) -> Any:
419418
name = clean_name(desc.BrowseName.Name)
420419
if sdef:
421420
if parent_sdef:
421+
names = [f.Name for f in sdef.Fields]
422422
for sfield in reversed(parent_sdef.Fields):
423-
sdef.Fields.insert(0, sfield)
423+
if sfield.Name not in names:
424+
sdef.Fields.insert(0, sfield)
424425
if isinstance(sdef, ua.StructureDefinition):
425426
dtypes.append(DataTypeSorter(desc.NodeId, name, desc, sdef))
426427
return _recursive_parse(
@@ -443,7 +444,7 @@ def __add_recursion(sdef, desc):
443444
await asyncio.gather(*requests)
444445

445446

446-
async def _get_parent_types(node: Node):
447+
async def _get_parent_types(node: Node) -> list[Node]:
447448
parents = []
448449
tmp_node = node
449450
for _ in range(10):
@@ -463,16 +464,18 @@ async def load_custom_struct(node: Node) -> Any:
463464
name = (await node.read_browse_name()).Name
464465
for parent in await _get_parent_types(node):
465466
parent_sdef = await parent.read_data_type_definition()
467+
names = [f.Name for f in sdef.Fields]
466468
for f in reversed(parent_sdef.Fields):
467-
sdef.Fields.insert(0, f)
469+
if f.Name not in names:
470+
sdef.Fields.insert(0, f)
468471
env = _generate_object(name, sdef, data_type=node.nodeid)
469472
struct = env[name]
470473
setattr(ua, name, struct)
471474
ua.register_extension_object(name, sdef.DefaultEncodingId, struct, node.nodeid)
472475
return struct
473476

474477

475-
async def load_custom_struct_xml_import(node_id: ua.NodeId, attrs: ua.DataTypeAttributes):
478+
async def load_custom_struct_xml_import(node_id: ua.NodeId, attrs: ua.DataTypeAttributes) -> Any:
476479
"""
477480
This function is used to load custom structs from xmlimporter
478481
"""
@@ -488,7 +491,7 @@ async def load_custom_struct_xml_import(node_id: ua.NodeId, attrs: ua.DataTypeAt
488491
return struct
489492

490493

491-
async def _recursive_parse_basedatatypes(server, base_node, parent_datatype, new_alias) -> Any:
494+
async def _recursive_parse_basedatatypes(server, base_node, parent_datatype, new_alias) -> None:
492495
for desc in await base_node.get_children_descriptions(refs=ua.ObjectIds.HasSubtype):
493496
name = clean_name(desc.BrowseName.Name)
494497
if parent_datatype not in "Number":
@@ -500,7 +503,7 @@ async def _recursive_parse_basedatatypes(server, base_node, parent_datatype, new
500503
await _recursive_parse_basedatatypes(server, server.get_node(desc.NodeId), name, new_alias)
501504

502505

503-
async def load_basetype_alias_xml_import(server, name, nodeid, parent_datatype_nid):
506+
async def load_basetype_alias_xml_import(server, name, nodeid, parent_datatype_nid) -> Any:
504507
"""
505508
Insert alias for a datatype used for xml import
506509
"""
@@ -521,7 +524,7 @@ def make_basetype(name: str, parent_datatype: str) -> dict[str, Any]:
521524
return {name: getattr(ua, parent_datatype)}
522525

523526

524-
async def _load_base_datatypes(server: Server | Client) -> Any:
527+
async def _load_base_datatypes(server: Server | Client) -> dict[str, Any]:
525528
new_alias = {}
526529
descriptions = await server.nodes.base_data_type.get_children_descriptions()
527530
for desc in descriptions:

asyncua/ua/ua_binary.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -285,26 +285,33 @@ def deserialize(data):
285285
def resolve_uatype(ftype: Any) -> tuple[Any, bool]:
286286
if isinstance(ftype, str):
287287
# Resolve string hint
288-
try:
289-
# This is a bit hacky, we assume ftype was derived from a field's hint
290-
# But for Unions, we might need a better way.
291-
# Actually, let's just use eval if it looks like a simple name
292-
if ftype.startswith("ua."):
293-
ftype = getattr(ua, ftype[3:])
294-
elif hasattr(ua, ftype):
295-
ftype = getattr(ua, ftype)
296-
else:
297-
ftype = eval(ftype, {"ua": ua, "typing": typing, "list": list, "List": list, "Union": typing.Union, "Optional": typing.Optional, "Dict": dict}) # type: ignore[arg-type]
298-
except Exception:
299-
_logger.exception("Failed to resolve type hint: %s", ftype)
288+
if ftype.startswith("ua."):
289+
ftype = getattr(ua, ftype[3:])
290+
elif hasattr(ua, ftype):
291+
ftype = getattr(ua, ftype)
292+
else:
293+
try:
294+
ftype = eval(
295+
ftype,
296+
{
297+
"ua": ua,
298+
"typing": typing,
299+
"list": list,
300+
"List": list,
301+
"Union": typing.Union,
302+
"Optional": typing.Optional,
303+
"Dict": dict,
304+
},
305+
) # type: ignore[arg-type]
306+
except Exception:
307+
_logger.exception("Failed to resolve type hint: %s", ftype)
300308
is_optional = type_is_optional(ftype)
301309
if is_optional:
302310
ftype = type_from_optional(ftype)
303311
return ftype, is_optional
304312

305313

306-
def field_serializer(ftype, dataclazz) -> Callable[[Any], bytes]:
307-
uatype, is_optional = resolve_uatype(ftype)
314+
def field_serializer(uatype: Any, is_optional: bool, dataclazz: type) -> Callable[[Any], bytes]:
308315
if type_is_list(uatype):
309316
ft = type_from_list(uatype)
310317
if is_optional:
@@ -334,7 +341,7 @@ def create_dataclass_serializer(dataclazz):
334341
if issubclass(dataclazz, ua.UaUnion):
335342
# Union is a class with Encoding and Value field
336343
# the value depends on encoding
337-
encoding_funcs = [field_serializer(t, dataclazz) for t in dataclazz._union_types]
344+
encoding_funcs = [field_serializer(*resolve_uatype(t), dataclazz) for t in dataclazz._union_types]
338345

339346
def union_serialize(obj):
340347
bin = Primitives.UInt32.pack(obj.Encoding)
@@ -358,7 +365,9 @@ def enc_value(obj):
358365
enc |= enc_val
359366
return enc
360367

361-
encoding_functions = [(f.name, field_serializer(resolved_fieldtypes[f.name], dataclazz)) for f in data_fields]
368+
encoding_functions = [
369+
(f.name, field_serializer(*resolve_uatype(resolved_fieldtypes[f.name]), dataclazz)) for f in data_fields
370+
]
362371

363372
def serialize(obj):
364373
return b"".join(

0 commit comments

Comments
 (0)