diff --git a/src/capellambse/aird/__init__.py b/src/capellambse/aird/__init__.py index a885e536e..e1e106508 100644 --- a/src/capellambse/aird/__init__.py +++ b/src/capellambse/aird/__init__.py @@ -62,8 +62,23 @@ class _DiagramDescriptor(t.NamedTuple): target: etree._Element +def _iter_views(model: loader.Loader, /) -> cabc.Iterator: + # cf. C.XP_VIEWS + for tree in model.trees.values(): + if tree.fragment_type != loader.FragmentType.VISUAL: + continue + if tree.root.tag == helpers.TAG_XMI: + roots = tree.root.iterchildren() + else: + roots = iter([tree.root]) + for root in roots: + if root.tag != f"{{{_n.NAMESPACES['viewpoint']}}}DAnalysis": + continue + yield from root.iterchildren("ownedViews") + + def enumerate_descriptors( - model: loader.MelodyLoader, + model: loader.Loader, *, viewpoint: str | None = None, ) -> cabc.Iterator[DRepresentationDescriptor]: @@ -77,7 +92,7 @@ def enumerate_descriptors( Only return diagrams of the given viewpoint. If not given, all diagrams are returned. """ - for view in model.xpath(C.XP_VIEWS): + for view in _iter_views(model): if viewpoint and _viewpoint_of(view) != viewpoint: continue @@ -89,7 +104,7 @@ def enumerate_descriptors( raise RuntimeError( f"Malformed diagram reference: {rep_path!r}" ) - diag_root = model[rep_path] + diag_root = model.follow_link(d, rep_path) if diag_root.tag not in DIAGRAM_ROOTS: continue @@ -130,12 +145,12 @@ def parse_diagrams( def _build_descriptor( - model: loader.MelodyLoader, + model: loader.Loader, descriptor: DRepresentationDescriptor, ) -> _DiagramDescriptor: assert isinstance(descriptor, etree._Element) - diag_root = model[descriptor.attrib["repPath"]] + diag_root = model.follow_link(descriptor, descriptor.attrib["repPath"]) styleclass = get_styleclass(descriptor) target = find_target(model, descriptor) @@ -151,7 +166,7 @@ def _build_descriptor( def find_target( - model: loader.MelodyLoader, descriptor: DRepresentationDescriptor + model: loader.Loader, descriptor: DRepresentationDescriptor ) -> etree._Element: assert isinstance(descriptor, etree._Element) target_anchors = list(descriptor.iterchildren("target")) @@ -180,7 +195,7 @@ def get_styleclass(descriptor: DRepresentationDescriptor) -> str | None: def parse_diagram( - model: loader.MelodyLoader, + model: loader.Loader, descriptor: DRepresentationDescriptor, **params: t.Any, ) -> diagram.Diagram: @@ -255,7 +270,11 @@ def parse_diagram( def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement: """Construct a single diagram element from the model XML.""" element = ebd.data_element.get("element") - tag = ebd.melodyloader[element].tag if element else None + tag = ( + ebd.melodyloader.follow_link(ebd.data_element, element).tag + if element + else None + ) if element is not None and tag != "ownedRepresentationDescriptors": factory = _semantic.from_xml else: @@ -264,7 +283,7 @@ def _element_from_xml(ebd: C.ElementBuilder) -> diagram.DiagramElement: def iter_visible( - model: loader.MelodyLoader, + model: loader.Loader, descriptor: DRepresentationDescriptor, ) -> cabc.Iterator[etree._Element]: r"""Iterate over all semantic elements that are visible in a diagram. diff --git a/src/capellambse/aird/_box_factories.py b/src/capellambse/aird/_box_factories.py index 59f82ac3a..3f17147b2 100644 --- a/src/capellambse/aird/_box_factories.py +++ b/src/capellambse/aird/_box_factories.py @@ -427,8 +427,9 @@ def statemode_activities_factory(seb: C.SemanticElementBuilder) -> diagram.Box: for elm in seb.diag_element.iterchildren("ownedElements"): elm_id = elm.get("uid") try: - target_id = next(elm.iterchildren("target")).attrib["href"] - target = seb.melodyloader[target_id] + target_link = next(elm.iterchildren("target")) + target_id = target_link.attrib["href"] + target = seb.melodyloader.follow_link(target_link, target_id) mapping_id = next(elm.iterchildren("actualMapping")).attrib["href"] except (KeyError, StopIteration): C.LOGGER.error("No usable target or mapping for %r", elm_id) diff --git a/src/capellambse/aird/_common.py b/src/capellambse/aird/_common.py index d4fdf2415..928d3620b 100644 --- a/src/capellambse/aird/_common.py +++ b/src/capellambse/aird/_common.py @@ -52,7 +52,7 @@ class ElementBuilder: target_diagram: diagram.Diagram diagram_tree: etree._Element data_element: etree._Element - melodyloader: capellambse.loader.MelodyLoader + melodyloader: capellambse.loader.Loader fragment: pathlib.PurePosixPath diff --git a/src/capellambse/aird/_edge_factories.py b/src/capellambse/aird/_edge_factories.py index fc75b2e56..3d4ac1ee2 100644 --- a/src/capellambse/aird/_edge_factories.py +++ b/src/capellambse/aird/_edge_factories.py @@ -608,7 +608,9 @@ def req_relation_factory(seb: C.SemanticElementBuilder) -> diagram.Edge: if not label: try: reltype_id = seb.melodyobjs[0].attrib["relationType"] - reltype = seb.melodyloader[reltype_id] + reltype = seb.melodyloader.follow_link( + seb.melodyobjs[0], reltype_id + ) label = reltype.attrib["ReqIFLongName"] except KeyError: C.LOGGER.warning( diff --git a/src/capellambse/aird/_filters/__init__.py b/src/capellambse/aird/_filters/__init__.py index 88c348cc9..099271f32 100644 --- a/src/capellambse/aird/_filters/__init__.py +++ b/src/capellambse/aird/_filters/__init__.py @@ -142,7 +142,7 @@ class FilterArguments: target_diagram: diagram.Diagram diagram_root: etree._Element - melodyloader: capellambse.loader.MelodyLoader + melodyloader: capellambse.loader.Loader params: dict[str, t.Any] @@ -174,7 +174,7 @@ def applyfilters(args: FilterArguments) -> None: dgobject.styleclass or dgobject.__class__.__name__, dgobject.uuid, ) - data_element = args.melodyloader[dgobject.uuid] + data_element = args.melodyloader.follow_link(None, dgobject.uuid) p2flt( c.ElementBuilder( target_diagram=args.target_diagram, @@ -280,7 +280,9 @@ def __init__( self._model = model self._diagram = diagram assert isinstance(diagram._element, etree._Element) - self._target = self._model._loader[diagram._element.attrib["repPath"]] + self._target = self._model._loader.follow_link( + diagram._element, diagram._element.attrib["repPath"] + ) @property def _elements(self) -> t.Iterator[etree._Element]: diff --git a/src/capellambse/aird/_filters/global.py b/src/capellambse/aird/_filters/global.py index 991039b08..913a75800 100644 --- a/src/capellambse/aird/_filters/global.py +++ b/src/capellambse/aird/_filters/global.py @@ -179,7 +179,8 @@ def hide_alloc_func_exch( for cex in component_exchanges: assert cex.uuid is not None # Find all allocated functional exchanges - for fex in args.melodyloader[cex.uuid].iterchildren( + cex_elem = args.melodyloader.follow_link(None, cex.uuid) + for fex in cex_elem.iterchildren( "ownedComponentExchangeFunctionalExchangeAllocations" ): target = fex.attrib["targetElement"].rsplit("#", 1)[-1] @@ -189,7 +190,7 @@ def hide_alloc_func_exch( def _stringify_exchange_items( obj: diagram.DiagramElement, - melodyloader: capellambse.loader.MelodyLoader, + melodyloader: capellambse.loader.Loader, sort_items: bool = False, ) -> str: assert obj.uuid is not None @@ -209,11 +210,11 @@ def _stringify_exchange_items( def _get_allocated_exchangeitem_names( *try_ids: str, alloc_attr: str, - melodyloader: capellambse.loader.MelodyLoader, + melodyloader: capellambse.loader.Loader, ) -> tuple[lxml.etree._Element | None, list[str]]: for obj_id in try_ids: try: - elm = melodyloader[obj_id] + elm = melodyloader.follow_link(None, obj_id) except KeyError: pass else: @@ -223,7 +224,7 @@ def _get_allocated_exchangeitem_names( if elm.tag == "ownedDiagramElements": targetlink = next(elm.iterchildren("target")) - elm = melodyloader[targetlink.attrib["href"]] + elm = melodyloader.follow_link(targetlink, targetlink.attrib["href"]) names = [] for elem in melodyloader.follow_links( diff --git a/src/capellambse/aird/_visual.py b/src/capellambse/aird/_visual.py index 2969d6f05..9489e6f4c 100644 --- a/src/capellambse/aird/_visual.py +++ b/src/capellambse/aird/_visual.py @@ -66,9 +66,10 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box: assert ebd.target_diagram.styleclass is not None uid = ebd.data_element.attrib[c.ATT_XMID] - element = ebd.data_element.get("element") - if element is not None: - label = ebd.melodyloader[element].attrib["name"] + element_id = ebd.data_element.get("element") + if element_id is not None: + element = ebd.melodyloader.follow_link(ebd.data_element, element_id) + label = element.attrib["name"] description = ebd.data_element.get("description", "") else: label = ebd.data_element.get("description", "") @@ -108,7 +109,7 @@ def shape_factory(ebd: c.ElementBuilder) -> diagram.Box: int(layout.attrib.get("height", "0")), ) - if element is not None: + if element_id is not None: styleclass = "RepresentationLink" else: styleclass = "Note" diff --git a/src/capellambse/helpers.py b/src/capellambse/helpers.py index a8c37f9a9..a8758999e 100644 --- a/src/capellambse/helpers.py +++ b/src/capellambse/helpers.py @@ -845,7 +845,7 @@ def resolve_namespace(tag: str) -> str: def unescape_linked_text( - loader: capellambse.loader.MelodyLoader, attr_text: str | None + loader: capellambse.loader.Loader, attr_text: str | None ) -> markupsafe.Markup: """Transform the ``linkedText`` into regular HTML.""" @@ -863,7 +863,7 @@ def flatten_element( ehref = html.escape(href) try: - target = loader[href] + target = loader.follow_link(None, href) except KeyError: yield f"<deleted element {ehref}>" else: @@ -887,7 +887,7 @@ def flatten_element( def escape_linked_text( - loader: capellambse.loader.MelodyLoader, attr_text: str + loader: capellambse.loader.Loader, attr_text: str ) -> str: """Transform simple HTML with object links into ``LinkedText``. diff --git a/src/capellambse/loader/__init__.py b/src/capellambse/loader/__init__.py index 633e8b30c..29a1b5be9 100644 --- a/src/capellambse/loader/__init__.py +++ b/src/capellambse/loader/__init__.py @@ -9,5 +9,5 @@ .. _LXML Documentation: https://lxml.de/ """ +from ._typing import * from .core import * -from .modelinfo import ModelInfo as ModelInfo diff --git a/src/capellambse/loader/_typing.py b/src/capellambse/loader/_typing.py new file mode 100644 index 000000000..fe9ec6bdc --- /dev/null +++ b/src/capellambse/loader/_typing.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG +# SPDX-License-Identifier: Apache-2.0 + +__all__ = [ + "FragmentType", + "Loader", + "ModelInfo", +] + +import dataclasses +import enum +import pathlib +import typing as t + +from capellambse import filehandler + +if t.TYPE_CHECKING: + from . import core + +Loader: t.TypeAlias = "core.MelodyLoader" + + +class FragmentType(enum.Enum): + """The type of an XML fragment.""" + + SEMANTIC = enum.auto() + VISUAL = enum.auto() + OTHER = enum.auto() + + +@dataclasses.dataclass +class ModelInfo: + url: str | None + title: str | None + entrypoint: pathlib.PurePosixPath + resources: dict[str, filehandler.abc.HandlerInfo] + capella_version: str + viewpoints: dict[str, str] diff --git a/src/capellambse/loader/core.py b/src/capellambse/loader/core.py index 4c7da5309..cf1606b1e 100644 --- a/src/capellambse/loader/core.py +++ b/src/capellambse/loader/core.py @@ -6,7 +6,6 @@ __all__ = [ "CorruptModelError", - "FragmentType", "MelodyLoader", "ModelFile", ] @@ -14,7 +13,6 @@ import collections import collections.abc as cabc import contextlib -import enum import itertools import logging import operator @@ -33,7 +31,8 @@ import capellambse._namespaces as _n from capellambse import filehandler, helpers from capellambse.loader import exs -from capellambse.loader.modelinfo import ModelInfo + +from ._typing import FragmentType, ModelInfo if sys.version_info >= (3, 13): from warnings import deprecated @@ -160,14 +159,6 @@ def _round_version(v: str, prec: int) -> str: return v[:pos] + re.sub(r"[^.]+", "0", v[pos:]) -class FragmentType(enum.Enum): - """The type of an XML fragment.""" - - SEMANTIC = enum.auto() - VISUAL = enum.auto() - OTHER = enum.auto() - - class MissingResourceLocationError(KeyError): """Raised when a model needs an additional resource location.""" @@ -1294,6 +1285,17 @@ def follow_links( raise return targets + def find_references(self, target_id: str) -> cabc.Iterator[etree._Element]: + for i in self.xpath( + f"//*[@*[contains(., '#{target_id}')] | */@*[contains(., '#{target_id}')]]", + roots=[ + i.root + for i in self.trees.values() + if i.fragment_type != FragmentType.VISUAL + ], + ): + yield self._follow_href(i) + def _find_fragment( self, element: etree._Element ) -> tuple[pathlib.PurePosixPath, ModelFile]: diff --git a/src/capellambse/loader/modelinfo.py b/src/capellambse/loader/modelinfo.py index 298dfd3e5..591208c25 100644 --- a/src/capellambse/loader/modelinfo.py +++ b/src/capellambse/loader/modelinfo.py @@ -3,17 +3,11 @@ from __future__ import annotations -import dataclasses -import pathlib +import warnings -from capellambse import filehandler +from ._typing import ModelInfo as ModelInfo - -@dataclasses.dataclass -class ModelInfo: - url: str | None - title: str | None - entrypoint: pathlib.PurePosixPath - resources: dict[str, filehandler.abc.HandlerInfo] - capella_version: str - viewpoints: dict[str, str] +warnings.warn( + f"{__name__} is deprecated, import ModelInfo from capellambse.loader directly instead", + stacklevel=2, +) diff --git a/src/capellambse/model/_descriptors.py b/src/capellambse/model/_descriptors.py index 7ca408f76..8dbebb799 100644 --- a/src/capellambse/model/_descriptors.py +++ b/src/capellambse/model/_descriptors.py @@ -50,7 +50,7 @@ from lxml import etree import capellambse -from capellambse import helpers +from capellambse import helpers, loader from . import T, T_co, U, U_co @@ -1130,6 +1130,10 @@ def __repr__(self) -> str: def _delete( self, model: capellambse.MelodyModel, elements: list[etree._Element] ) -> None: + if not isinstance(model._loader, loader.MelodyLoader): + raise TypeError( + f"{type(self).__name__} can only be used with the legacy lxml backend" + ) all_elements = ( list( itertools.chain.from_iterable( @@ -1163,7 +1167,7 @@ def _resolve( ) -> etree._Element: if self.follow_abstract: if abstype := elem.get("abstractType"): - elem = obj._model._loader[abstype] + elem = obj._model._loader.follow_link(elem, abstype) else: raise RuntimeError("Broken XML: No abstractType defined?") return elem @@ -1171,12 +1175,20 @@ def _resolve( def _getsubelems( self, obj: _obj.ModelObject ) -> cabc.Iterator[etree._Element]: + if not isinstance(obj._model._loader, loader.MelodyLoader): + raise TypeError( + f"{type(self).__name__} can only be used with the legacy lxml backend" + ) return itertools.chain.from_iterable( obj._model._loader.iterchildren_xt(i, *iter(self.xtypes)) for i in self._findroots(obj) ) def _findroots(self, obj: _obj.ModelObject) -> list[etree._Element]: + if not isinstance(obj._model._loader, loader.MelodyLoader): + raise TypeError( + f"{type(self).__name__} can only be used with the legacy lxml backend" + ) roots = [obj._element] for xtype in self.rootelem: roots = list( @@ -1328,6 +1340,10 @@ def _getsubelems( self, obj: _obj.ModelObject ) -> cabc.Iterator[etree._Element]: ldr = obj._model._loader + if not isinstance(ldr, loader.MelodyLoader): + raise TypeError( + f"{type(self).__name__} can only be used with the legacy lxml backend" + ) roots = [obj._element] for xtype in self.rootelem: roots = list( @@ -3102,7 +3118,7 @@ def delete( assert obj._model is elmlist._model model = obj._model all_elements = [ - *list(model._loader.iterdescendants_xt(obj._element)), + *list(model._loader.iterdescendants(obj._element)), obj._element, ] with contextlib.ExitStack() as stack: diff --git a/src/capellambse/model/_model.py b/src/capellambse/model/_model.py index 0ba7866d8..0c190b8d0 100644 --- a/src/capellambse/model/_model.py +++ b/src/capellambse/model/_model.py @@ -156,6 +156,7 @@ def __init__( | None ) = None, fallback_render_aird: bool = False, + loader_backend: t.Literal["lxml"] = "lxml", **kwargs: t.Any, ) -> None: """Load a project. @@ -266,10 +267,16 @@ def __init__( AIRD diagrams, and only used during cache misses (``FileNotFoundError`` from the underlying file handler) for all other types of diagrams. + loader_backend + Choose which Loader backend to use. Currently the only + supported option is "lxml", which uses the traditional + :class:`~capellambse.loader.core.MelodyLoader`, based on + modifying in-memory XML trees loaded with the `lxml` + library. **kwargs - Additional arguments are passed on to the underlying - :class:`~capellambse.loader.core.MelodyLoader`, which in - turn passes it on to the primary resource's FileHandler. + Additional arguments are passed on to the underlying Loader + instance, which in turn passes it on to the primary + resource's FileHandler. See Also -------- @@ -286,13 +293,20 @@ def __init__( """ capellambse.load_model_extensions() - self._loader = loader.MelodyLoader(path, **kwargs) + if loader_backend == "lxml": + self._loader: loader.Loader = loader.MelodyLoader(path, **kwargs) + else: + raise ValueError( + f"Unsupported loader backend {loader_backend!r}," + " currently only 'lxml' is supported" + ) self.__viewpoints = dict(self._loader.referenced_viewpoints()) + self._fallback_render_aird = fallback_render_aird if diagram_cache: if diagram_cache == path: - self.diagram_cache = self._loader.filehandler + self.diagram_cache = self._loader.resources["\x00"] elif isinstance(diagram_cache, filehandler.FileHandler): self.diagram_cache = diagram_cache elif isinstance(diagram_cache, cabc.Mapping): @@ -305,7 +319,7 @@ def __init__( self.diagram_cache = None @property - def resources(self) -> dict[str, filehandler.FileHandler]: + def resources(self) -> cabc.MutableMapping[str, filehandler.FileHandler]: return self._loader.resources def save(self, **kw: t.Any) -> None: @@ -470,7 +484,7 @@ class as the superclass of every concrete model element def by_uuid(self, uuid: str) -> t.Any: """Search the entire model for an element with the given UUID.""" - return _obj.wrap_xml(self, self._loader[uuid]) + return _obj.wrap_xml(self, self._loader.follow_link(None, uuid)) def find_references( self, target: _obj.ModelObject | str, / @@ -506,14 +520,7 @@ def find_references( if not capellambse.helpers.is_uuid_string(uuid): raise ValueError(f"Malformed or missing UUID for {target!r}") - for elem in self._loader.xpath( - f"//*[@*[contains(., '#{uuid}')] | */@*[contains(., '#{uuid}')]]", - roots=[ - i.root - for i in self._loader.trees.values() - if i.fragment_type != loader.FragmentType.VISUAL - ], - ): + for elem in self._loader.find_references(uuid): obj = _obj.wrap_xml(self, elem) for attr in _reference_attributes(type(obj)): if attr.startswith("_"): diff --git a/src/capellambse/model/_obj.py b/src/capellambse/model/_obj.py index a0080894a..d8fc8c4b3 100644 --- a/src/capellambse/model/_obj.py +++ b/src/capellambse/model/_obj.py @@ -618,7 +618,8 @@ def progress_status(self) -> str: if uuid is None: return "NOT_SET" - return wrap_xml(self._model, self._model._loader[uuid]).name + elem = self._model._loader.follow_link(self._element, uuid) + return wrap_xml(self._model, elem).name @classmethod @deprecated("ModelElement.from_model is deprecated, use wrap_xml instead") diff --git a/tests/conftest.py b/tests/conftest.py index 22c7b2b03..0ff4bf830 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import importlib.metadata as imm import pathlib import shutil +import typing as t import pytest @@ -12,6 +13,7 @@ INSTALLED_PACKAGE = pathlib.Path(capellambse.__file__).parent TEST_DATA = pathlib.Path(__file__).parent / "data" +MODEL_PARAMS = (pytest.param({"loader_backend": "lxml"}, id="lxml-loader"),) capellambse.load_model_extensions() @@ -36,8 +38,10 @@ class Models: writemodel = TEST_DATA.joinpath("models", "writemodel") -@pytest.fixture(scope="session") -def session_shared_model() -> capellambse.MelodyModel: +@pytest.fixture(scope="session", params=MODEL_PARAMS) +def session_shared_model( + request: pytest.FixtureRequest, +) -> capellambse.MelodyModel: """Load the standard test model. Unlike the ``model`` fixture, this fixture is shared across the @@ -47,24 +51,27 @@ def session_shared_model() -> capellambse.MelodyModel: This fixture exists as a speed optimization for tests that only read from the model. """ - return capellambse.MelodyModel(Models.test7_0) + return capellambse.MelodyModel(Models.test7_0, **request.param) -@pytest.fixture -def model() -> capellambse.MelodyModel: +@pytest.fixture(params=MODEL_PARAMS) +def model(request: pytest.FixtureRequest) -> capellambse.MelodyModel: """Load the Capella 7.0 test model.""" - return capellambse.MelodyModel(Models.test7_0) + return capellambse.MelodyModel(Models.test7_0, **request.param) -@pytest.fixture -def empty_model() -> capellambse.MelodyModel: +@pytest.fixture(params=MODEL_PARAMS) +def empty_model(request: pytest.FixtureRequest) -> capellambse.MelodyModel: """Load the empty test model.""" - return capellambse.MelodyModel(Models.empty) + return capellambse.MelodyModel(Models.empty, **request.param) -@pytest.fixture -def tmp_model(tmp_path: pathlib.Path) -> pathlib.Path: +@pytest.fixture(params=MODEL_PARAMS) +def tmp_model( + request: pytest.FixtureRequest, + tmp_path: pathlib.Path, +) -> tuple[pathlib.Path, dict[str, t.Any]]: """Copy the Capella 7.0 test model to a temporary directory.""" path = tmp_path / "model" shutil.copytree(Models.test7_0, path) - return path + return path, request.param diff --git a/tests/test_model_creation_deletion.py b/tests/test_model_creation_deletion.py index 7bf5e7896..de066fecd 100644 --- a/tests/test_model_creation_deletion.py +++ b/tests/test_model_creation_deletion.py @@ -39,7 +39,7 @@ def test_created_elements_show_up_in_xml_after_adding_them( ) try: - writemodel._loader[newobj.uuid] + writemodel._loader.follow_link(newobj._element, newobj.uuid) except KeyError as err: raise AssertionError( "Cannot find added element via subscripting" @@ -66,7 +66,7 @@ def test_deleted_elements_are_removed( assert len(comps) != 2, "List length did not change" with pytest.raises(KeyError): - writemodel._loader[olduuid] + writemodel._loader.follow_link(None, olduuid) assert not writemodel._loader.xpath(XPATH_UUID.format(olduuid)), ( "Element is still present in tree after deleting" diff --git a/tests/test_model_loading.py b/tests/test_model_loading.py index d33bef2e3..1b5fc5e5b 100644 --- a/tests/test_model_loading.py +++ b/tests/test_model_loading.py @@ -10,6 +10,7 @@ import shutil import subprocess import sys +import typing as t from importlib import metadata import pytest @@ -673,9 +674,10 @@ def test_model_diagram_visible_nodes_can_be_accessed_when_a_cache_was_specified( def test_updated_namespaces_use_rounded_versions( - tmp_model: pathlib.Path, + tmp_model: tuple[pathlib.Path, dict[str, t.Any]], ): - (afm,) = tmp_model.glob("*.afm") + modelpath, params = tmp_model + (afm,) = modelpath.glob("*.afm") data = etree.parse(afm) for child in data.getroot().iterchildren("viewpointReferences"): if child.get("vpId") == capellambse.model.CORE_VIEWPOINT: @@ -683,7 +685,7 @@ def test_updated_namespaces_use_rounded_versions( with afm.open("wb") as f: exs.write(data, f, line_length=sys.maxsize) - model = capellambse.MelodyModel(tmp_model) + model = capellambse.MelodyModel(modelpath, **params) model._loader.update_namespaces() assert model.info.capella_version == "7.1.0"