diff --git a/asyncua/client/client.py b/asyncua/client/client.py index f975cb977..ea1c38eee 100644 --- a/asyncua/client/client.py +++ b/asyncua/client/client.py @@ -816,11 +816,11 @@ async def get_namespace_index(self, uri: str) -> int: async def delete_nodes(self, nodes: Iterable[Node], recursive=False) -> Tuple[List[Node], List[ua.StatusCode]]: return await delete_nodes(self.uaclient, nodes, recursive) - async def import_xml(self, path=None, xmlstring=None, strict_mode=True) -> List[ua.NodeId]: + async def import_xml(self, path=None, xmlstring=None, strict_mode=True, auto_load_definitions: bool = True) -> List[ua.NodeId]: """ Import nodes defined in xml """ - importer = XmlImporter(self, strict_mode=strict_mode) + importer = XmlImporter(self, strict_mode=strict_mode, auto_load_definitions=auto_load_definitions) return await importer.import_xml(path, xmlstring) async def export_xml(self, nodes, path, export_values: bool = False) -> None: diff --git a/asyncua/common/xmlimporter.py b/asyncua/common/xmlimporter.py index f20a89635..d267e1bce 100644 --- a/asyncua/common/xmlimporter.py +++ b/asyncua/common/xmlimporter.py @@ -24,10 +24,11 @@ def _parse_version(version_string: str) -> List[int]: class XmlImporter: - def __init__(self, server: Union[asyncua.Server, asyncua.Client], strict_mode: bool = True): + def __init__(self, server: Union[asyncua.Server, asyncua.Client], strict_mode: bool = True, auto_load_definitions: bool = True): ''' strict_mode: stop on an error, if False only an error message is logged, but the import continues + auto_load_definitions: auto generate code stubs on the fly for enum and structs ''' self.parser = None self.session = server @@ -36,6 +37,7 @@ def __init__(self, server: Union[asyncua.Server, asyncua.Client], strict_mode: b self._unmigrated_aliases: Dict[str, str] = {} # Dict[name, nodeId string] self.refs = None self.strict_mode = strict_mode + self.auto_load_definitions = auto_load_definitions async def _map_namespaces(self): """ @@ -413,9 +415,12 @@ def _get_ext_class(self, name: str): async def _make_ext_obj(self, obj): try: extclass = self._get_ext_class(obj.objname) - except Exception: - await self.session.load_data_type_definitions() # load new data type definitions since a customn class should be created - extclass = self._get_ext_class(obj.objname) + except Exception as exp: + if self.auto_load_definitions: + await self.session.load_data_type_definitions() # load new data type definitions since a customn class should be created + extclass = self._get_ext_class(obj.objname) + else: + raise exp args = {} for name, val in obj.body: if not isinstance(val, list): @@ -627,13 +632,14 @@ async def add_datatype(self, obj, no_namespace_migration=False): res = await self._get_server().add_nodes([node]) res[0].StatusCode.check() await self._add_refs(obj) - if is_struct: - await load_custom_struct_xml_import(node.RequestedNewNodeId, attrs) - if is_enum: - await load_enum_xml_import(node.RequestedNewNodeId, attrs, is_option_set) - if is_alias: - if node.ParentNodeId != ua.NodeId(ua.ObjectIds.Structure): - await load_basetype_alias_xml_import(self.session, node.BrowseName.Name, node.RequestedNewNodeId, node.ParentNodeId) + if self.auto_load_definitions: + if is_struct: + await load_custom_struct_xml_import(node.RequestedNewNodeId, attrs) + if is_enum: + await load_enum_xml_import(node.RequestedNewNodeId, attrs, is_option_set) + if is_alias: + if node.ParentNodeId != ua.NodeId(ua.ObjectIds.Structure): + await load_basetype_alias_xml_import(self.session, node.BrowseName.Name, node.RequestedNewNodeId, node.ParentNodeId) return res[0].AddedNodeId async def _add_refs(self, obj): diff --git a/asyncua/server/server.py b/asyncua/server/server.py index 47d0215a6..c24bc78a3 100644 --- a/asyncua/server/server.py +++ b/asyncua/server/server.py @@ -718,11 +718,11 @@ async def _create_custom_type(self, idx, name, basetype, properties, variables, await custom_t.add_method(idx, method[0], method[1], method[2], method[3]) return custom_t - async def import_xml(self, path=None, xmlstring=None, strict_mode=True): + async def import_xml(self, path=None, xmlstring=None, strict_mode=True, auto_load_definitions: bool = True): """ Import nodes defined in xml """ - importer = XmlImporter(self, strict_mode) + importer = XmlImporter(self, strict_mode, auto_load_definitions) return await importer.import_xml(path, xmlstring) async def export_xml(self, nodes, path, export_values: bool = False): diff --git a/tests/test_common.py b/tests/test_common.py index 03befebce..d9364fa6a 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1327,8 +1327,12 @@ async def test_guid_node_id(): async def test_import_xml_data_type_definition(opc): + if hasattr(ua, "MySubstruct"): + delattr(ua, "MySubstruct") + if hasattr(ua, "MyStruct"): + delattr(ua, "MyStruct") + nodes = await opc.opc.import_xml("tests/substructs.xml") - await opc.opc.load_data_type_definitions() assert hasattr(ua, "MySubstruct") assert hasattr(ua, "MyStruct") @@ -1362,6 +1366,19 @@ async def test_import_xml_data_type_definition(opc): [n.append(opc.opc.get_node(node)) for node in nodes] await opc.opc.delete_nodes(n) +async def test_import_xml_data_no_auto_load_type_definition(opc): + # if al present in ua remove it (left overs of other tests) + if hasattr(ua, "MySubstruct"): + delattr(ua, "MySubstruct") + if hasattr(ua, "MyStruct"): + delattr(ua, "MyStruct") + if hasattr(ua, "MyEnum"): + delattr(ua, "MyEnum") + await opc.opc.import_xml("tests/substructs.xml", auto_load_definitions = False) + assert hasattr(ua, "MySubstruct") is False + assert hasattr(ua, "MyStruct") is False + assert hasattr(ua, "MyEnum") is False + async def test_struct_data_type(opc): assert isinstance(ua.AddNodesItem.data_type, ua.NodeId) @@ -1372,8 +1389,10 @@ async def test_struct_data_type(opc): async def test_import_xml_enum_data_type_definition(opc): + if hasattr(ua, "MyEnum"): + delattr(ua, "MyEnum") + nodes = await opc.opc.import_xml("tests/testenum104.xml") - await opc.opc.load_data_type_definitions() assert hasattr(ua, "MyEnum") e = ua.MyEnum.val2 var = await opc.opc.nodes.objects.add_variable(