From fef21160142644e50dcc265fbd0f5db585b566de Mon Sep 17 00:00:00 2001 From: ewuerger Date: Fri, 9 Sep 2022 10:46:21 +0200 Subject: [PATCH 1/5] fix: Collection of allocated functions for interface context --- .../collectors/exchanges.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/capellambse_context_diagrams/collectors/exchanges.py b/capellambse_context_diagrams/collectors/exchanges.py index f576a7f5..b4d57f34 100644 --- a/capellambse_context_diagrams/collectors/exchanges.py +++ b/capellambse_context_diagrams/collectors/exchanges.py @@ -22,7 +22,7 @@ class ExchangeCollector(metaclass=abc.ABCMeta): """Base class for context collection on Exchanges.""" - intermap = { + intermap: dict[str, DT] = { DT.OAB: ("source", "target", "allocated_interactions", "activities"), DT.SAB: ( "source.parent", @@ -34,13 +34,13 @@ class ExchangeCollector(metaclass=abc.ABCMeta): "source.parent", "target.parent", "allocated_functional_exchanges", - "functions", + "allocated_functions", ), DT.PAB: ( "source.parent", "target.parent", "allocated_functional_exchanges", - "functions", + "allocated_functions", ), } @@ -57,7 +57,7 @@ def __init__( self.get_source = operator.attrgetter(src) self.get_target = operator.attrgetter(trg) self.get_alloc_fex = operator.attrgetter(alloc_fex) - self.get_functions = operator.attrgetter(fncs) + self.get_alloc_functions = operator.attrgetter(fncs) def get_functions_and_exchanges( self, comp: common.GenericElement, interface: common.GenericElement @@ -70,7 +70,7 @@ def get_functions_and_exchanges( `FunctionalExchange`s for given `Component` and `interface`. """ functions, outgoings, incomings = [], [], [] - alloc_functions = self.get_functions(comp) + alloc_functions = self.get_alloc_functions(comp) for fex in self.get_alloc_fex(interface): source = self.get_source(fex) if source in alloc_functions: @@ -170,7 +170,8 @@ def get_left_and_right(self) -> None: def get_capella_order( comp: common.GenericElement, functions: list[common.GenericElement] ) -> list[common.GenericElement]: - return [fnc for fnc in comp.functions if fnc in functions] + alloc_functions = self.get_alloc_functions(comp) + return [fnc for fnc in alloc_functions if fnc in functions] def make_boxes( comp: common.GenericElement, functions: list[common.GenericElement] @@ -180,7 +181,7 @@ def make_boxes( box["children"] = [ makers.make_box(c) for c in functions - if c in self.get_functions(comp) + if c in self.get_alloc_functions(comp) ] self.data["children"].append(box) made_children.add(comp.uuid) From 3c9aa388e2fbb66aa369d921d11b63f14de035f5 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Fri, 9 Sep 2022 10:47:06 +0200 Subject: [PATCH 2/5] test: Add interface context test for SA --- tests/data/ContextDiagram.aird | 67 ++++++++++++++++++++++++++++++- tests/data/ContextDiagram.capella | 9 +++++ tests/test_interface_diagrams.py | 9 ++++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/tests/data/ContextDiagram.aird b/tests/data/ContextDiagram.aird index 50d7577d..62a38cfe 100644 --- a/tests/data/ContextDiagram.aird +++ b/tests/data/ContextDiagram.aird @@ -17,7 +17,7 @@ - + @@ -285,6 +285,17 @@ + + + + + + + + + + + @@ -360,6 +371,17 @@ + + + + + + + + + + + @@ -630,6 +652,22 @@ + + + + + + + + + + + + + + + + @@ -808,6 +846,15 @@ + + + + + + + + + KEEP_LOCATION KEEP_SIZE KEEP_RATIO @@ -858,6 +905,15 @@ + + + + + + + + + KEEP_LOCATION KEEP_SIZE KEEP_RATIO @@ -1091,6 +1147,15 @@ + + + + + + + + + diff --git a/tests/data/ContextDiagram.capella b/tests/data/ContextDiagram.capella index e193285f..5d3af774 100644 --- a/tests/data/ContextDiagram.capella +++ b/tests/data/ContextDiagram.capella @@ -1939,6 +1939,9 @@ The predator is far away id="da6131e9-dbc0-4f16-8e9c-be6932fa05d8" targetElement="#77dd3442-c092-4b69-8b50-830bc1c5d584" sourceElement="#86a1afc2-b7fd-4023-bbd5-ab44f5dc2c28"/> + + @@ -2334,6 +2340,9 @@ The predator is far away id="b5aaa4ee-c96b-48f3-be37-36b596eed46d" targetElement="#91fd613c-4cea-4c2b-8acb-0c8c27f5f0da" sourceElement="#b2d2a4f1-0c21-45f4-918c-3d3c5e8af104"/> + diff --git a/tests/test_interface_diagrams.py b/tests/test_interface_diagrams.py index dd363962..ff1a9190 100644 --- a/tests/test_interface_diagrams.py +++ b/tests/test_interface_diagrams.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2022 Copyright DB Netz AG and the capellambse-context-diagrams contributors # SPDX-License-Identifier: Apache-2.0 +import pathlib + import capellambse import pytest @@ -9,14 +11,17 @@ "uuid", [ # pytest.param("3c9764aa-4981-44ef-8463-87a053016635", id="OA"), - # pytest.param("86a1afc2-b7fd-4023-bbd5-ab44f5dc2c28", id="SA"), + pytest.param("86a1afc2-b7fd-4023-bbd5-ab44f5dc2c28", id="SA"), pytest.param("3ef23099-ce9a-4f7d-812f-935f47e7938d", id="LA"), ], ) def test_interface_diagrams_get_rendered( - model: capellambse.MelodyModel, uuid: str + model: capellambse.MelodyModel, uuid: str, tmp_path: pathlib.Path ) -> None: obj = model.by_uuid(uuid) + filename = tmp_path / "tmp.svg" + diag = obj.context_diagram + diag.render("svgdiagram").save_drawing(filename=filename) assert diag.nodes From 9047ffc5466907794d9c8ffad30851ccde628c42 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Fri, 9 Sep 2022 11:18:38 +0200 Subject: [PATCH 3/5] fix: Styling of interface context diagrams - Add default `Circle.FunctionalExchange` styling to all supported diagram types of generic context diagrams. --- capellambse_context_diagrams/__init__.py | 21 ++++++++---- capellambse_context_diagrams/context.py | 42 +++++++++++++----------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index f6149e59..485b997a 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -18,7 +18,9 @@ """ from __future__ import annotations +import collections.abc as cabc import logging +import typing as t from importlib import metadata from . import context @@ -29,6 +31,11 @@ __version__ = "0.0.0+unknown" del metadata +if t.TYPE_CHECKING: + from capellambse.model.modeltypes import DiagramType + + ClassPair = tuple[type[common.GenericElement], DiagramType] + logger = logging.getLogger(__name__) ATTR_NAME = "context_diagram" @@ -46,10 +53,7 @@ def register_classes() -> None: from capellambse.model.layers import ctx, la, oa, pa from capellambse.model.modeltypes import DiagramType - patch_styles() - supported_classes: list[ - tuple[type[common.GenericElement], DiagramType] - ] = [ + supported_classes: list[ClassPair] = [ (oa.Entity, DiagramType.OAB), (oa.OperationalActivity, DiagramType.OAIB), (oa.OperationalCapability, DiagramType.OCB), @@ -62,6 +66,7 @@ def register_classes() -> None: (pa.PhysicalComponent, DiagramType.PAB), (pa.PhysicalFunction, DiagramType.PDFB), ] + patch_styles(supported_classes) class_: type[common.GenericElement] for class_, dgcls in supported_classes: common.set_accessor( @@ -69,7 +74,7 @@ def register_classes() -> None: ) -def patch_styles() -> None: +def patch_styles(classes: cabc.Iterable[ClassPair]) -> None: """Add missing default styling to default styles. See Also @@ -90,6 +95,9 @@ def patch_styles() -> None: capstyle.STYLES["Operational Capabilities Blank"][ "Box.OperationalCapability" ] = cap + circle_style = {"fill": COLORS["_CAP_xAB_Function_Border_Green"]} + for _, dt in classes: + capstyle.STYLES[dt.value]["Circle.FunctionalExchange"] = circle_style def register_interface_context() -> None: @@ -134,6 +142,7 @@ def register_functional_context() -> None: from capellambse.model.layers import ctx, la, oa, pa from capellambse.model.modeltypes import DiagramType + attr_name = f"functional_{ATTR_NAME}" supported_classes: list[ tuple[type[common.GenericElement], DiagramType] ] = [ @@ -146,7 +155,7 @@ def register_functional_context() -> None: for class_, dgcls in supported_classes: common.set_accessor( class_, - f"functional_{ATTR_NAME}", + attr_name, context.FunctionalContextAccessor(dgcls.value), ) diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index 630a6a76..8ac1285b 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -120,8 +120,7 @@ def __get__( # type: ignore class ContextDiagram(diagram.AbstractDiagram): - """ - An automatically generated context diagram. + """An automatically generated context diagram. Attributes ---------- @@ -152,6 +151,23 @@ class ContextDiagram(diagram.AbstractDiagram): [`collectors.exchange_data_collector`][capellambse_context_diagrams.collectors.generic.exchange_data_collector]. """ + def __init__( + self, + class_: str, + obj: common.GenericElement, + *, + render_styles: dict[str, styling.Styler] | None = None, + display_symbols_as_boxes: bool = False, + ) -> None: + super().__init__(obj._model) + self.target = obj + self.styleclass = class_ + + self.render_styles = render_styles or styling.BLUE_ACTOR_FNCS + self.serializer = serializers.DiagramSerializer(self) + self.__filters: cabc.MutableSet[str] = self.FilterSet(self) + self.display_symbols_as_boxes = display_symbols_as_boxes + @property def uuid(self) -> str: # type: ignore """Returns diagram UUID.""" @@ -228,23 +244,6 @@ def __iter__(self) -> cabc.Iterator[str]: def __len__(self) -> int: return self._set.__len__() - def __init__( - self, - class_: str, - obj: common.GenericElement, - *, - render_styles: dict[str, styling.Styler] | None = None, - display_symbols_as_boxes: bool = False, - ) -> None: - super().__init__(obj._model) - self.target = obj - self.styleclass = class_ - - self.render_styles = render_styles or styling.BLUE_ACTOR_FNCS - self.serializer = serializers.DiagramSerializer(self) - self.__filters: cabc.MutableSet[str] = self.FilterSet(self) - self.display_symbols_as_boxes = display_symbols_as_boxes - def _create_diagram( self, params: dict[str, t.Any], @@ -270,9 +269,12 @@ def filters(self, value: cabc.Iterable[str]) -> None: class InterfaceContextDiagram(ContextDiagram): """An automatically generated Context Diagram exclusively for - ComponentExchanges. + ``ComponentExchange``s. """ + def __init__(self, class_: str, obj: common.GenericElement, **kw) -> None: + super().__init__(class_, obj, **kw, display_symbols_as_boxes=True) + @property def name(self) -> str: # type: ignore return f"Interface Context of {self.target.name}" From d164313cd5ff8abce0e96b4bed3b1aff9e1c09f5 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Fri, 9 Sep 2022 16:19:58 +0200 Subject: [PATCH 4/5] ci: Use latest pre-release of `capellambse` --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd31cc50..e306a5b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "capellambse", + "capellambse>=0.4.19.dev12", "typing_extensions", ] From 4209fe96b70b5bcf4d96b026b47009c066b218df Mon Sep 17 00:00:00 2001 From: ewuerger Date: Fri, 9 Sep 2022 16:44:07 +0200 Subject: [PATCH 5/5] lint: Fix pylint errors and warnings --- capellambse_context_diagrams/__init__.py | 26 +++++++----------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index 485b997a..54217868 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -23,6 +23,12 @@ import typing as t from importlib import metadata +from capellambse.aird import COLORS, CSSdef, capstyle +from capellambse.model import common +from capellambse.model.crosslayer import fa +from capellambse.model.layers import ctx, la, oa, pa +from capellambse.model.modeltypes import DiagramType + from . import context try: @@ -31,10 +37,7 @@ __version__ = "0.0.0+unknown" del metadata -if t.TYPE_CHECKING: - from capellambse.model.modeltypes import DiagramType - - ClassPair = tuple[type[common.GenericElement], DiagramType] +ClassPair = tuple[type[common.GenericElement], DiagramType] logger = logging.getLogger(__name__) @@ -50,9 +53,6 @@ def init() -> None: def register_classes() -> None: """Add the `context_diagram` property to the relevant model objects.""" - from capellambse.model.layers import ctx, la, oa, pa - from capellambse.model.modeltypes import DiagramType - supported_classes: list[ClassPair] = [ (oa.Entity, DiagramType.OAB), (oa.OperationalActivity, DiagramType.OAIB), @@ -82,8 +82,6 @@ def patch_styles(classes: cabc.Iterable[ClassPair]) -> None: [capstyle.get_style][capellambse.aird.capstyle.get_style] : Default style getter. """ - from capellambse.aird import COLORS, CSSdef, capstyle - cap: dict[str, CSSdef] = { "fill": [COLORS["_CAP_Entity_Gray_min"], COLORS["_CAP_Entity_Gray"]], "stroke": COLORS["dark_gray"], @@ -102,10 +100,6 @@ def patch_styles(classes: cabc.Iterable[ClassPair]) -> None: def register_interface_context() -> None: """Add the `context_diagram` property to interface model objects.""" - from capellambse.model.crosslayer import fa - from capellambse.model.layers import ctx, la, oa, pa - from capellambse.model.modeltypes import DiagramType - common.set_accessor( oa.CommunicationMean, ATTR_NAME, @@ -139,9 +133,6 @@ def register_functional_context() -> None: The functional context diagrams will be available soon. """ - from capellambse.model.layers import ctx, la, oa, pa - from capellambse.model.modeltypes import DiagramType - attr_name = f"functional_{ATTR_NAME}" supported_classes: list[ tuple[type[common.GenericElement], DiagramType] @@ -158,6 +149,3 @@ def register_functional_context() -> None: attr_name, context.FunctionalContextAccessor(dgcls.value), ) - - -from capellambse.model import common