diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 482a80bc..b4b29c24 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,6 +37,15 @@ repos: rev: v1.11.1 hooks: - id: mypy + args: [--follow-imports=silent] + additional_dependencies: + - capellambse==0.6.6 + - mkdocs + - mkdocs-gen-files + - mkdocs-literate-nav + - pydantic==2.7.3 + - pytest + - types-setuptools - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 hooks: diff --git a/capellambse_context_diagrams/__init__.py b/capellambse_context_diagrams/__init__.py index f0afe7b5..fb8e4605 100644 --- a/capellambse_context_diagrams/__init__.py +++ b/capellambse_context_diagrams/__init__.py @@ -23,11 +23,10 @@ import typing as t from importlib import metadata +import capellambse.model as m from capellambse.diagram import COLORS, CSSdef, capstyle -from capellambse.model import common -from capellambse.model.crosslayer import fa, information -from capellambse.model.layers import ctx, la, oa, pa -from capellambse.model.modeltypes import DiagramType +from capellambse.metamodel import fa, information, la, oa, pa, sa +from capellambse.model import DiagramType from . import _elkjs, context, styling @@ -38,9 +37,7 @@ del metadata DefaultRenderParams = dict[str, t.Any] -SupportedClass = tuple[ - type[common.GenericElement], DiagramType, DefaultRenderParams -] +SupportedClass = tuple[type[m.ModelElement], DiagramType, DefaultRenderParams] logger = logging.getLogger(__name__) ATTR_NAME = "context_diagram" @@ -78,10 +75,14 @@ def register_classes() -> None: {"display_parent_relation": True}, ), (oa.OperationalCapability, DiagramType.OCB, {}), - (ctx.Mission, DiagramType.MCB, {}), - (ctx.Capability, DiagramType.MCB, {"display_symbols_as_boxes": False}), + (sa.Mission, DiagramType.MCB, {}), ( - ctx.SystemComponent, + sa.Capability, + DiagramType.MCB, + {"display_symbols_as_boxes": False}, + ), + ( + sa.SystemComponent, DiagramType.SAB, { "display_symbols_as_boxes": True, @@ -90,7 +91,7 @@ def register_classes() -> None: }, ), ( - ctx.SystemFunction, + sa.SystemFunction, DiagramType.SAB, { "display_symbols_as_boxes": True, @@ -132,10 +133,10 @@ def register_classes() -> None: ), ] patch_styles(supported_classes) - class_: type[common.GenericElement] + class_: type[m.ModelElement] for class_, dgcls, default_render_params in supported_classes: accessor = context.ContextAccessor(dgcls.value, default_render_params) - common.set_accessor(class_, ATTR_NAME, accessor) + m.set_accessor(class_, ATTR_NAME, accessor) def patch_styles(classes: cabc.Iterable[SupportedClass]) -> None: @@ -157,14 +158,16 @@ def patch_styles(classes: cabc.Iterable[SupportedClass]) -> None: capstyle.STYLES["Operational Capabilities Blank"][ "Box.OperationalCapability" ] = cap - circle_style = {"fill": COLORS["_CAP_xAB_Function_Border_Green"]} + circle_style: dict[str, CSSdef] = { + "fill": COLORS["_CAP_xAB_Function_Border_Green"], + } for _, dt, _ in classes: capstyle.STYLES[dt.value]["Circle.FunctionalExchange"] = circle_style def register_interface_context() -> None: """Add the `context_diagram` property to interface model objects.""" - common.set_accessor( + m.set_accessor( oa.CommunicationMean, ATTR_NAME, context.InterfaceContextAccessor( @@ -175,13 +178,13 @@ def register_interface_context() -> None: {"include_interface": True}, ), ) - common.set_accessor( + m.set_accessor( fa.ComponentExchange, ATTR_NAME, context.InterfaceContextAccessor( { - ctx.SystemComponentPkg: DiagramType.SAB.value, - ctx.SystemComponent: DiagramType.SAB.value, + sa.SystemComponentPkg: DiagramType.SAB.value, + sa.SystemComponent: DiagramType.SAB.value, la.LogicalComponentPkg: DiagramType.LAB.value, la.LogicalComponent: DiagramType.LAB.value, pa.PhysicalComponentPkg: DiagramType.PAB.value, @@ -200,17 +203,15 @@ def register_functional_context() -> None: The functional context diagrams will be available soon. """ attr_name = f"functional_{ATTR_NAME}" - supported_classes: list[ - tuple[type[common.GenericElement], DiagramType] - ] = [ + supported_classes: list[tuple[type[m.ModelElement], DiagramType]] = [ (oa.Entity, DiagramType.OAB), - (ctx.SystemComponent, DiagramType.SAB), + (sa.SystemComponent, DiagramType.SAB), (la.LogicalComponent, DiagramType.LAB), (pa.PhysicalComponent, DiagramType.PAB), ] - class_: type[common.GenericElement] + class_: type[m.ModelElement] for class_, dgcls in supported_classes: - common.set_accessor( + m.set_accessor( class_, attr_name, context.FunctionalContextAccessor(dgcls.value), @@ -219,7 +220,7 @@ def register_functional_context() -> None: def register_tree_view() -> None: """Add the ``tree_view`` attribute to ``Class``es.""" - common.set_accessor( + m.set_accessor( information.Class, "tree_view", context.ClassTreeAccessor(DiagramType.CDB.value), @@ -235,8 +236,8 @@ def register_realization_view() -> None: supported_classes: list[SupportedClass] = [ (oa.Entity, DiagramType.OAB, {}), (oa.OperationalActivity, DiagramType.OAIB, {}), - (ctx.SystemComponent, DiagramType.SAB, {}), - (ctx.SystemFunction, DiagramType.SDFB, {}), + (sa.SystemComponent, DiagramType.SAB, {}), + (sa.SystemFunction, DiagramType.SDFB, {}), (la.LogicalComponent, DiagramType.LAB, {}), (la.LogicalFunction, DiagramType.LDFB, {}), (pa.PhysicalComponent, DiagramType.PAB, {}), @@ -244,7 +245,7 @@ def register_realization_view() -> None: ] styles: dict[str, dict[str, capstyle.CSSdef]] = {} for class_, dgcls, _ in supported_classes: - common.set_accessor( + m.set_accessor( class_, "realization_view", context.RealizationViewContextAccessor("RealizationView Diagram"), @@ -265,9 +266,9 @@ def register_realization_view() -> None: def register_data_flow_view() -> None: supported_classes: list[SupportedClass] = [ (oa.OperationalCapability, DiagramType.OAIB, {}), # portless - (ctx.Capability, DiagramType.SDFB, {}), # default + (sa.Capability, DiagramType.SDFB, {}), # default ] - class_: type[common.GenericElement] + class_: type[m.ModelElement] for class_, dgcls, default_render_params in supported_classes: accessor = context.DataFlowAccessor(dgcls.value, default_render_params) - common.set_accessor(class_, "data_flow_view", accessor) + m.set_accessor(class_, "data_flow_view", accessor) diff --git a/capellambse_context_diagrams/_elkjs.py b/capellambse_context_diagrams/_elkjs.py index f2548773..49f4dc84 100644 --- a/capellambse_context_diagrams/_elkjs.py +++ b/capellambse_context_diagrams/_elkjs.py @@ -206,6 +206,7 @@ class ELKOutputNode(ELKOutputDiagramElement): children: cabc.MutableSequence[ELKOutputChild] = pydantic.Field( default_factory=list ) + context: list[str] = pydantic.Field(default_factory=list) class ELKOutputJunction(ELKOutputElement): @@ -217,6 +218,7 @@ class ELKOutputJunction(ELKOutputElement): ) position: ELKPoint + context: list[str] = pydantic.Field(default_factory=list) class ELKOutputPort(ELKOutputDiagramElement): @@ -226,6 +228,7 @@ class ELKOutputPort(ELKOutputDiagramElement): children: cabc.MutableSequence[ELKOutputLabel] = pydantic.Field( default_factory=list ) + context: list[str] = pydantic.Field(default_factory=list) class ELKOutputLabel(ELKOutputDiagramElement): @@ -233,6 +236,7 @@ class ELKOutputLabel(ELKOutputDiagramElement): type: t.Literal["label"] text: str + context: list[str] = pydantic.Field(default_factory=list) class ELKOutputEdge(ELKOutputElement): @@ -246,6 +250,7 @@ class ELKOutputEdge(ELKOutputElement): children: cabc.MutableSequence[ t.Union[ELKOutputLabel, ELKOutputJunction] ] = pydantic.Field(default_factory=list) + context: list[str] = pydantic.Field(default_factory=list) ELKOutputChild = t.Union[ diff --git a/capellambse_context_diagrams/collectors/dataflow_view.py b/capellambse_context_diagrams/collectors/dataflow_view.py index 633bcf39..b1d3bbcc 100644 --- a/capellambse_context_diagrams/collectors/dataflow_view.py +++ b/capellambse_context_diagrams/collectors/dataflow_view.py @@ -10,16 +10,15 @@ import typing as t from itertools import chain -from capellambse.model import modeltypes -from capellambse.model.crosslayer import fa -from capellambse.model.layers import oa +import capellambse.model as m +from capellambse.metamodel import fa, oa from .. import _elkjs, context from . import default, generic, makers, portless -COLLECTOR_PARAMS: dict[modeltypes.DiagramType, dict[str, t.Any]] = { - modeltypes.DiagramType.OAIB: {"attribute": "involved_activities"}, - modeltypes.DiagramType.SDFB: { +COLLECTOR_PARAMS: dict[m.DiagramType, dict[str, t.Any]] = { + m.DiagramType.OAIB: {"attribute": "involved_activities"}, + m.DiagramType.SDFB: { "attribute": "involved_functions", "filter_attrs": ("source.owner", "target.owner"), "port_collector": default.port_collector, diff --git a/capellambse_context_diagrams/collectors/default.py b/capellambse_context_diagrams/collectors/default.py index 76f6c8dc..b97170f6 100644 --- a/capellambse_context_diagrams/collectors/default.py +++ b/capellambse_context_diagrams/collectors/default.py @@ -10,12 +10,10 @@ import typing as t from itertools import chain +import capellambse.model as m from capellambse import helpers -from capellambse.model import common -from capellambse.model.crosslayer import cs, fa -from capellambse.model.layers import ctx as sa -from capellambse.model.layers import la -from capellambse.model.modeltypes import DiagramType as DT +from capellambse.metamodel import cs, fa, la, sa +from capellambse.model import DiagramType as DT from .. import _elkjs from . import exchanges, generic, makers @@ -29,8 +27,8 @@ ] Filter: t.TypeAlias = cabc.Callable[ - [cabc.Iterable[common.GenericElement]], - cabc.Iterable[common.GenericElement], + [cabc.Iterable[m.ModelElement]], + cabc.Iterable[m.ModelElement], ] @@ -134,7 +132,7 @@ def _process_port_spread( port_spread[owner] += inc def _process_exchanges(self) -> tuple[ - list[common.GenericElement], + list[m.ModelElement], list[generic.ExchangeData], ]: inc, out = port_collector(self.diagram.target, self.diagram.type) @@ -285,13 +283,13 @@ def collector( def port_collector( - target: common.GenericElement | common.ElementList, diagram_type: DT -) -> tuple[list[common.GenericElement], list[common.GenericElement]]: + target: m.ModelElement | m.ElementList, diagram_type: DT +) -> tuple[list[m.ModelElement], list[m.ModelElement]]: """Savely collect ports from `target`.""" def __collect(target): - incoming_ports: list[common.GenericElement] = [] - outgoing_ports: list[common.GenericElement] = [] + incoming_ports: list[m.ModelElement] = [] + outgoing_ports: list[m.ModelElement] = [] for attr in generic.DIAGRAM_TYPE_TO_CONNECTOR_NAMES[diagram_type]: try: ports = getattr(target, attr) @@ -315,9 +313,9 @@ def __collect(target): return incoming_ports, outgoing_ports if isinstance(target, cabc.Iterable): - assert not isinstance(target, common.GenericElement) - incoming_ports: list[common.GenericElement] = [] - outgoing_ports: list[common.GenericElement] = [] + assert not isinstance(target, m.ModelElement) + incoming_ports: list[m.ModelElement] = [] + outgoing_ports: list[m.ModelElement] = [] for obj in target: inc, out = __collect(obj) incoming_ports.extend(inc) @@ -328,27 +326,27 @@ def __collect(target): def _extract_edges( - obj: common.GenericElement, + obj: m.ModelElement, attribute: str, filter: Filter, -) -> cabc.Iterable[common.GenericElement]: +) -> cabc.Iterable[m.ModelElement]: return filter(getattr(obj, attribute, [])) def port_exchange_collector( - ports: t.Iterable[common.GenericElement], + ports: t.Iterable[m.ModelElement], filter: Filter = lambda i: i, -) -> dict[str, common.ElementList[fa.AbstractExchange]]: +) -> dict[str, list[fa.AbstractExchange]]: """Collect exchanges from `ports` savely.""" - edges: dict[str, common.ElementList[fa.AbstractExchange]] = {} + edges: dict[str, list[fa.AbstractExchange]] = {} for port in ports: if exs := _extract_edges(port, "exchanges", filter): - edges[port.uuid] = exs + edges[port.uuid] = t.cast(list[fa.AbstractExchange], exs) continue if links := _extract_edges(port, "links", filter): - edges[port.uuid] = links + edges[port.uuid] = t.cast(list[fa.AbstractExchange], links) return edges @@ -356,9 +354,9 @@ def port_exchange_collector( class ContextInfo(t.NamedTuple): """ContextInfo data.""" - element: common.GenericElement + element: m.ModelElement """An element of context.""" - ports: list[common.GenericElement] + ports: list[m.ModelElement] """The context element's relevant ports. This list only contains ports that at least one of the exchanges @@ -370,7 +368,7 @@ class ContextInfo(t.NamedTuple): def port_context_collector( exchange_datas: t.Iterable[generic.ExchangeData], - local_ports: t.Container[common.GenericElement], + local_ports: t.Container[m.ModelElement], ) -> t.Iterator[ContextInfo]: """Collect the context objects. @@ -492,7 +490,7 @@ def derive_from_functions( ) -DERIVATORS: dict[type[common.GenericElement], DerivatorFunction] = { +DERIVATORS: dict[type[m.ModelElement], DerivatorFunction] = { la.LogicalComponent: derive_from_functions, sa.SystemComponent: derive_from_functions, } diff --git a/capellambse_context_diagrams/collectors/exchanges.py b/capellambse_context_diagrams/collectors/exchanges.py index a7baae83..f08f3733 100644 --- a/capellambse_context_diagrams/collectors/exchanges.py +++ b/capellambse_context_diagrams/collectors/exchanges.py @@ -9,9 +9,9 @@ import operator import typing as t -from capellambse.model import common -from capellambse.model.crosslayer import cs, fa -from capellambse.model.modeltypes import DiagramType as DT +import capellambse.model as m +from capellambse.metamodel import cs, fa +from capellambse.model import DiagramType as DT from .. import _elkjs, context from . import generic, makers @@ -130,8 +130,8 @@ class InterfaceContextCollector(ExchangeCollector): """Left (source) Component Box of the interface.""" right: _elkjs.ELKInputChild | None """Right (target) Component Box of the interface.""" - outgoing_edges: dict[str, common.GenericElement] - incoming_edges: dict[str, common.GenericElement] + outgoing_edges: dict[str, m.ModelElement] + incoming_edges: dict[str, m.ModelElement] def __init__( self, @@ -216,7 +216,7 @@ def make_all_owners( obj: fa.AbstractFunction | fa.FunctionPort, boxes: dict[str, _elkjs.ELKInputChild], ) -> str: - owners: list[common.GenericElement] = [] + owners: list[m.ModelElement] = [] assert self.right is not None and self.left is not None root: _elkjs.ELKInputChild | None = None for uuid in generic.get_all_owners(obj): @@ -230,7 +230,7 @@ def make_all_owners( if root is None: raise ValueError(f"No root found for {obj._short_repr_()}") - owner_box = root + owner_box: _elkjs.ELKInputChild = root for owner in reversed(owners): if isinstance(owner, fa.FunctionPort): if owner.uuid in (p.id for p in owner_box.ports): @@ -321,7 +321,7 @@ def collect(self) -> None: def is_hierarchical( - ex: common.GenericElement, + ex: m.ModelElement, box: _elkjs.ELKInputChild, key: t.Literal["ports"] | t.Literal["children"] = "ports", ) -> bool: diff --git a/capellambse_context_diagrams/collectors/generic.py b/capellambse_context_diagrams/collectors/generic.py index 81b0f2b9..a89ea002 100644 --- a/capellambse_context_diagrams/collectors/generic.py +++ b/capellambse_context_diagrams/collectors/generic.py @@ -11,21 +11,21 @@ import logging import typing as t -from capellambse.model import common, layers -from capellambse.model.crosslayer import interaction -from capellambse.model.modeltypes import DiagramType as DT +import capellambse.model as m +from capellambse.metamodel import interaction, la, oa, pa, sa +from capellambse.model import DiagramType as DT from .. import _elkjs, context, filters from . import makers logger = logging.getLogger(__name__) -SourceAndTarget = tuple[common.GenericElement, common.GenericElement] +SourceAndTarget = tuple[m.ModelElement, m.ModelElement] PHYSICAL_CONNECTOR_ATTR_NAMES = ("physical_ports",) """Attribute of PhysicalComponents for receiving connections.""" CONNECTOR_ATTR_NAMES = ("ports", "inputs", "outputs") -"""Attribute of GenericElements for receiving connections.""" +"""Attribute of ModelElements for receiving connections.""" DIAGRAM_TYPE_TO_CONNECTOR_NAMES: dict[DT, tuple[str, ...]] = { DT.OAB: (), DT.OAIB: (), @@ -43,11 +43,11 @@ """Default size of marker-ends in pixels.""" MARKER_PADDING = makers.PORT_PADDING """Default padding of markers in pixels.""" -PackageTypes: tuple[type[common.GenericElement], ...] = ( - layers.oa.EntityPkg, - layers.la.LogicalComponentPkg, - layers.ctx.SystemComponentPkg, - layers.pa.PhysicalComponentPkg, +PackageTypes: tuple[type[m.ModelElement], ...] = ( + oa.EntityPkg, + la.LogicalComponentPkg, + sa.SystemComponentPkg, + pa.PhysicalComponentPkg, ) @@ -71,7 +71,7 @@ def collector( def collect_exchange_endpoints( - ex: ExchangeData | common.GenericElement, + ex: ExchangeData | m.ModelElement, ) -> SourceAndTarget: """Safely collect exchange endpoints from ``ex``.""" if isinstance(ex, ExchangeData): @@ -84,7 +84,7 @@ def collect_exchange_endpoints( class ExchangeData(t.NamedTuple): """Exchange data for ELK.""" - exchange: common.GenericElement + exchange: m.ModelElement """An exchange from the capellambse model.""" elkdata: _elkjs.ELKInputData """The collected elkdata to add the edges in there.""" @@ -103,7 +103,7 @@ class ExchangeData(t.NamedTuple): def exchange_data_collector( data: ExchangeData, endpoint_collector: cabc.Callable[ - [common.GenericElement], SourceAndTarget + [m.ModelElement], SourceAndTarget ] = collect_exchange_endpoints, ) -> SourceAndTarget: """Return source and target port from `exchange`. @@ -179,7 +179,7 @@ def exchange_data_collector( return source, target -def collect_label(obj: common.GenericElement) -> str | None: +def collect_label(obj: m.ModelElement) -> str | None: """Return the label of a given object. The label usually comes from the `.name` attribute. Special handling @@ -195,7 +195,7 @@ def collect_label(obj: common.GenericElement) -> str | None: def move_parent_boxes_to_owner( boxes: dict[str, _elkjs.ELKInputChild], - obj: common.GenericElement, + obj: m.ModelElement, data: _elkjs.ELKInputData, filter_types: tuple[type, ...] = PackageTypes, ) -> None: @@ -221,8 +221,8 @@ def move_parent_boxes_to_owner( def move_edges( - boxes: dict[str, _elkjs.ELKInputChild], - connections: list[common.GenericElement], + boxes: cabc.Mapping[str, _elkjs.ELKInputChild], + connections: cabc.Sequence[m.ModelElement], data: _elkjs.ELKInputData, ) -> None: """Move edges to boxes.""" @@ -252,9 +252,9 @@ def move_edges( data.edges = [e for e in data.edges if e.id not in edges_to_remove] -def get_all_owners(obj: common.GenericElement) -> cabc.Iterator[str]: +def get_all_owners(obj: m.ModelElement) -> cabc.Iterator[str]: """Return the UUIDs from all owners of ``obj``.""" - current: common.GenericElement | None = obj + current: m.ModelElement | None = obj while current is not None: yield current.uuid current = getattr(current, "owner", None) diff --git a/capellambse_context_diagrams/collectors/makers.py b/capellambse_context_diagrams/collectors/makers.py index d05184a3..553b20b9 100644 --- a/capellambse_context_diagrams/collectors/makers.py +++ b/capellambse_context_diagrams/collectors/makers.py @@ -5,9 +5,10 @@ import collections.abc as cabc +import capellambse.model as m import typing_extensions as te from capellambse import helpers as chelpers -from capellambse.model import common, layers +from capellambse.metamodel import oa, sa from capellambse.svg import helpers as svghelpers from capellambse.svg.decorations import icon_padding, icon_size @@ -42,10 +43,10 @@ FAULT_PAD = 10 """Height adjustment for labels.""" BOX_TO_SYMBOL = ( - layers.ctx.Capability.__name__, - layers.oa.OperationalCapability.__name__, - layers.ctx.Mission.__name__, - layers.ctx.SystemComponent.__name__, + sa.Capability.__name__, + oa.OperationalCapability.__name__, + sa.Mission.__name__, + sa.SystemComponent.__name__, "SystemHumanActor", "SystemActor", ) @@ -94,7 +95,7 @@ def make_label( """ label_width, label_height = chelpers.get_text_extent(text) icon_width, _ = icon - lines = [text] + lines: cabc.Sequence[str] = [text] if max_width is not None and label_width > max_width: lines, _, _ = svghelpers.check_for_horizontal_overflow( text, @@ -129,14 +130,14 @@ class _LabelBuilder(te.TypedDict, total=True): def make_box( - obj: common.GenericElement, + obj: m.ModelElement, *, width: int | float = 0, height: int | float = 0, no_symbol: bool = False, slim_width: bool = True, label_getter: cabc.Callable[ - [common.GenericElement], cabc.Iterable[_LabelBuilder] + [m.ModelElement], cabc.Iterable[_LabelBuilder] ] = lambda i: [ { "text": i.name, @@ -198,7 +199,7 @@ def calculate_height_and_width( return width, max(height, _height) -def is_symbol(obj: str | common.GenericElement | None) -> bool: +def is_symbol(obj: str | m.ModelElement | None) -> bool: """Check if given `obj` is rendered as a Symbol instead of a Box.""" if obj is None: return False diff --git a/capellambse_context_diagrams/collectors/portless.py b/capellambse_context_diagrams/collectors/portless.py index 7e06b857..8f1a9d7d 100644 --- a/capellambse_context_diagrams/collectors/portless.py +++ b/capellambse_context_diagrams/collectors/portless.py @@ -11,7 +11,8 @@ import typing as t from itertools import chain -from capellambse.model import common, layers +import capellambse.model as m +from capellambse.metamodel import oa, sa from .. import _elkjs, context from . import generic, makers @@ -117,17 +118,15 @@ def collector( def collect_exchange_endpoints( - e: common.GenericElement, -) -> tuple[common.GenericElement, common.GenericElement]: + e: m.ModelElement, +) -> tuple[m.ModelElement, m.ModelElement]: """Safely collect exchange endpoints from `e`.""" - def _get( - e: common.GenericElement, attrs: t.FrozenSet[str] - ) -> common.GenericElement: + def _get(e: m.ModelElement, attrs: frozenset[str]) -> m.ModelElement: for attr in attrs: try: obj = getattr(e, attr) - assert isinstance(obj, common.GenericElement) + assert isinstance(obj, m.ModelElement) return obj except AttributeError: continue @@ -143,17 +142,17 @@ def _get( class ContextInfo(t.NamedTuple): """ContextInfo data.""" - element: common.GenericElement + element: m.ModelElement """An element of context.""" - connections: list[common.GenericElement] + connections: list[m.ModelElement] """The context element's relevant exchanges.""" side: t.Literal["input", "output"] """Whether this is an input or output to the element of interest.""" def context_collector( - exchanges: t.Iterable[common.GenericElement], - obj_oi: common.GenericElement, + exchanges: t.Iterable[m.ModelElement], + obj_oi: m.ModelElement, ) -> t.Iterator[ContextInfo]: ctx: dict[str, ContextInfo] = {} side: t.Literal["input", "output"] @@ -178,12 +177,12 @@ def context_collector( def get_exchanges( - obj: common.GenericElement, + obj: m.ModelElement, filter: cabc.Callable[ - [cabc.Iterable[common.GenericElement]], - cabc.Iterable[common.GenericElement], + [cabc.Iterable[m.ModelElement]], + cabc.Iterable[m.ModelElement], ] = lambda i: i, -) -> t.Iterator[common.GenericElement]: +) -> t.Iterator[t.Any]: """Yield exchanges safely. Yields exchanges from ``.related_exchanges`` or exclusively by @@ -202,8 +201,8 @@ def get_exchanges( * ``.involvements`` and * ``.exploitations``. """ - is_op_capability = isinstance(obj, layers.oa.OperationalCapability) - is_capability = isinstance(obj, layers.ctx.Capability) + is_op_capability = isinstance(obj, oa.OperationalCapability) + is_capability = isinstance(obj, sa.Capability) if is_op_capability or is_capability: exchanges = [ obj.includes, @@ -213,7 +212,7 @@ def get_exchanges( obj.extended_by, obj.generalized_by, ] - elif isinstance(obj, layers.ctx.Mission): + elif isinstance(obj, sa.Mission): exchanges = [obj.involvements, obj.exploitations] else: exchanges = [obj.related_exchanges] diff --git a/capellambse_context_diagrams/collectors/realization_view.py b/capellambse_context_diagrams/collectors/realization_view.py index 26f0d694..1cf90b3f 100644 --- a/capellambse_context_diagrams/collectors/realization_view.py +++ b/capellambse_context_diagrams/collectors/realization_view.py @@ -9,9 +9,8 @@ import re import typing as t -from capellambse.model import common, crosslayer -from capellambse.model.crosslayer import cs, fa -from capellambse.model.layers import oa +import capellambse.model as m +from capellambse.metamodel import cs, fa, oa from .. import _elkjs, context from . import makers @@ -117,21 +116,21 @@ def collector( def collect_realized( - start: common.GenericElement, depth: int + start: m.ModelElement, depth: int ) -> dict[LayerLiteral, list[dict[str, t.Any]]]: """Collect all elements from ``realized_`` attributes up to depth.""" return collect_elements(start, depth, "ABOVE", "realized") def collect_realizing( - start: common.GenericElement, depth: int + start: m.ModelElement, depth: int ) -> dict[LayerLiteral, list[dict[str, t.Any]]]: """Collect all elements from ``realizing_`` attributes down to depth.""" return collect_elements(start, depth, "BELOW", "realizing") def collect_all( - start: common.GenericElement, depth: int + start: m.ModelElement, depth: int ) -> dict[LayerLiteral, list[dict[str, t.Any]]]: """Collect all elements in both ABOVE and BELOW directions.""" above = collect_realized(start, depth) @@ -140,11 +139,11 @@ def collect_all( def collect_elements( - start: common.GenericElement, + start: m.ModelElement, depth: int, direction: str, attribute_prefix: str, - origin: common.GenericElement | None = None, + origin: m.ModelElement | None = None, ) -> dict[LayerLiteral, list[dict[str, t.Any]]]: """Collect elements based on the specified direction and attribute name.""" layer_obj, layer = find_layer(start) @@ -198,8 +197,8 @@ def collect_elements( def find_layer( - obj: common.GenericElement, -) -> tuple[crosslayer.BaseArchitectureLayer, LayerLiteral]: + obj: m.ModelElement, +) -> tuple[cs.ComponentArchitecture, LayerLiteral]: """Return the layer object and its literal. Return either one of the following: @@ -209,7 +208,7 @@ def find_layer( * ``Physical`` """ parent = obj - while not isinstance(parent, crosslayer.BaseArchitectureLayer): + while not isinstance(parent, cs.ComponentArchitecture): parent = parent.parent if not (match := RE_LAYER_PTRN.match(type(parent).__name__)): raise ValueError("No layer was found.") @@ -217,7 +216,7 @@ def find_layer( Collector = cabc.Callable[ - [common.GenericElement, int], dict[LayerLiteral, list[dict[str, t.Any]]] + [m.ModelElement, int], dict[LayerLiteral, list[dict[str, t.Any]]] ] COLLECTORS: dict[str, Collector] = { "ALL": collect_all, diff --git a/capellambse_context_diagrams/collectors/tree_view.py b/capellambse_context_diagrams/collectors/tree_view.py index be396189..ed3825c7 100644 --- a/capellambse_context_diagrams/collectors/tree_view.py +++ b/capellambse_context_diagrams/collectors/tree_view.py @@ -10,8 +10,8 @@ import math import typing as t -from capellambse.model import common -from capellambse.model.crosslayer import information +import capellambse.model as m +from capellambse.metamodel import information from .. import _elkjs, context from . import generic, makers @@ -54,7 +54,7 @@ def process_class(self, cls: ClassInfo, params: dict[str, t.Any]): edges = [ assoc for assoc in self.all_associations - if cls.prop in list(assoc.navigable_members) + if cls.prop in assoc.navigable_members # type: ignore[attr-defined] ] edge_id = edges[0].uuid if edge_id not in self.made_edges: @@ -92,13 +92,19 @@ def process_class(self, cls: ClassInfo, params: dict[str, t.Any]): ) def _process_box( - self, obj: information.Class, partition: int, params: dict[str, t.Any] + self, + obj: information.Class, + partition: int, + params: dict[str, t.Any], ) -> None: if obj.uuid not in self.made_boxes: self._make_box(obj, partition, params) def _make_box( - self, obj: information.Class, partition: int, params: dict[str, t.Any] + self, + obj: information.Class, + partition: int, + params: dict[str, t.Any], ) -> _elkjs.ELKInputChild: self.made_boxes.add(obj.uuid) box = makers.make_box( @@ -268,7 +274,7 @@ def get_all_classes( for prop in root.super.owned_properties: process_property( _PropertyInfo( - root.super, + root.super, # type: ignore[arg-type] prop, partition + 1, classes, @@ -282,11 +288,14 @@ def get_all_classes( edge_id = f"{root.uuid} {root.super.uuid}" if edge_id not in classes: classes[edge_id] = _make_class_info( - root.super, None, partition, generalizes=root + root.super, # type: ignore[arg-type] + None, + partition, + generalizes=root, ) classes.update( get_all_classes( - root.super, + root.super, # type: ignore[arg-type] partition, classes, max_partition, @@ -344,7 +353,7 @@ def _make_class_info( return ClassInfo( source=source, target=target, - prop=prop, + prop=prop, # type: ignore[arg-type] partition=partition, multiplicity=multiplicity, generalizes=generalizes, @@ -419,7 +428,7 @@ def _get_property_text(prop: information.Property) -> str: def _get_legend_labels( - obj: common.GenericElement, + obj: m.ModelElement, ) -> cabc.Iterator[makers._LabelBuilder]: yield { "text": obj.name, diff --git a/capellambse_context_diagrams/context.py b/capellambse_context_diagrams/context.py index 38e20c27..6e45d377 100644 --- a/capellambse_context_diagrams/context.py +++ b/capellambse_context_diagrams/context.py @@ -14,7 +14,7 @@ from capellambse import diagram as cdiagram from capellambse import helpers -from capellambse.model import common, diagram, modeltypes +from capellambse import model as m from . import _elkjs, filters, serializers, styling from .collectors import ( @@ -37,7 +37,7 @@ } -class ContextAccessor(common.Accessor): +class ContextAccessor(m.Accessor): """Provides access to the context diagrams.""" def __init__( @@ -49,29 +49,23 @@ def __init__( @t.overload def __get__(self, obj: None, objtype: type[t.Any]) -> ContextAccessor: ... - @t.overload def __get__( - self, - obj: common.T, - objtype: type[common.T] | None = None, + self, obj: m.T, objtype: type[m.T] | None = None ) -> ContextDiagram: ... - def __get__( - self, - obj: common.T | None, - objtype: type | None = None, - ) -> common.Accessor | ContextDiagram: + self, obj: m.T | None, objtype: type | None = None + ) -> m.Accessor | ContextDiagram: """Make a ContextDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self - assert isinstance(obj, common.GenericElement) + assert isinstance(obj, m.ModelElement) return self._get(obj, ContextDiagram) def _get( - self, obj: common.GenericElement, diagram_class: type[ContextDiagram] - ) -> common.Accessor | ContextDiagram: + self, obj: m.ModelElement, diagram_class: type[ContextDiagram] + ) -> m.Accessor | ContextDiagram: new_diagram = diagram_class( self._dgcls, obj, @@ -86,23 +80,21 @@ class InterfaceContextAccessor(ContextAccessor): def __init__( # pylint: disable=super-init-not-called self, - diagclass: dict[type[common.GenericElement], str], + diagclass: dict[type[m.ModelElement], str], render_params: dict[str, t.Any] | None = None, ) -> None: self.__dgclasses = diagclass self._default_render_params = render_params or {} def __get__( # type: ignore - self, - obj: common.T | None, - objtype: type | None = None, - ) -> common.Accessor | ContextDiagram: + self, obj: m.T | None, objtype: type | None = None + ) -> m.Accessor | ContextDiagram: """Make a ContextDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self - assert isinstance(obj, common.GenericElement) - assert isinstance(obj.parent, common.GenericElement) + assert isinstance(obj, m.ModelElement) + assert isinstance(obj.parent, m.ModelElement) self._dgcls = self.__dgclasses[obj.parent.__class__] return self._get(obj, InterfaceContextDiagram) @@ -110,14 +102,14 @@ def __get__( # type: ignore class FunctionalContextAccessor(ContextAccessor): def __get__( # type: ignore self, - obj: common.T | None, + obj: m.T | None, objtype: type | None = None, - ) -> common.Accessor | ContextDiagram: + ) -> m.Accessor | ContextDiagram: """Make a ContextDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self - assert isinstance(obj, common.GenericElement) + assert isinstance(obj, m.ModelElement) return self._get(obj, FunctionalContextDiagram) @@ -133,14 +125,14 @@ def __init__( def __get__( # type: ignore self, - obj: common.T | None, + obj: m.T | None, objtype: type | None = None, - ) -> common.Accessor | ContextDiagram: + ) -> m.Accessor | ContextDiagram: """Make a ClassTreeDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self - assert isinstance(obj, common.GenericElement) + assert isinstance(obj, m.ModelElement) return self._get(obj, ClassTreeDiagram) @@ -156,14 +148,14 @@ def __init__( def __get__( # type: ignore self, - obj: common.T | None, + obj: m.T | None, objtype: type | None = None, - ) -> common.Accessor | ContextDiagram: + ) -> m.Accessor | ContextDiagram: """Make a RealizationViewDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self - assert isinstance(obj, common.GenericElement) + assert isinstance(obj, m.ModelElement) return self._get(obj, RealizationViewDiagram) @@ -177,24 +169,24 @@ def __init__( def __get__( # type: ignore self, - obj: common.T | None, + obj: m.T | None, objtype: type | None = None, - ) -> common.Accessor | ContextDiagram: + ) -> m.Accessor | ContextDiagram: """Make a DataFlowViewDiagram for the given model object.""" del objtype if obj is None: # pragma: no cover return self - assert isinstance(obj, common.GenericElement) + assert isinstance(obj, m.ModelElement) return self._get(obj, DataFlowViewDiagram) -class ContextDiagram(diagram.AbstractDiagram): +class ContextDiagram(m.AbstractDiagram): """An automatically generated context diagram. Attributes ---------- target - The `common.GenericElement` from which the context is collected + The `m.ModelElement` from which the context is collected from. styleclass The diagram class (for e.g. [LAB]). @@ -203,11 +195,11 @@ class ContextDiagram(diagram.AbstractDiagram): `styling.Styler` functions as values. An example is given by: [`styling.BLUE_ACTOR_FNCS`][capellambse_context_diagrams.styling.BLUE_ACTOR_FNCS] serializer - The serializer builds a `diagram.Diagram` via + The serializer builds a `Diagram` via [`serializers.DiagramSerializer.make_diagram`][capellambse_context_diagrams.serializers.DiagramSerializer.make_diagram] by converting every [`_elkjs.ELKOutputChild`][capellambse_context_diagrams._elkjs.ELKOutputChild] - into a `diagram.Box`, `diagram.Edge` or `diagram.Circle`. + into a `Box`, `Edge` or `Circle`. filters A list of filter names that are applied during collection of context. Currently this is only done in @@ -239,13 +231,13 @@ class ContextDiagram(diagram.AbstractDiagram): def __init__( self, class_: str, - obj: common.GenericElement, + obj: m.ModelElement, *, render_styles: dict[str, styling.Styler] | None = None, default_render_parameters: dict[str, t.Any], ) -> None: super().__init__(obj._model) - self.target = obj + self.target = obj # type: ignore[misc] self.styleclass = class_ self.render_styles = render_styles or {} @@ -274,20 +266,20 @@ def name(self) -> str: # type: ignore return f"Context of {self.target.name.replace('/', '- or -')}" @property - def type(self) -> modeltypes.DiagramType: + def type(self) -> m.DiagramType: """Return the type of this diagram.""" try: - return modeltypes.DiagramType(self.styleclass) + return m.DiagramType(self.styleclass) except ValueError: # pragma: no cover logger.warning("Unknown diagram type %r", self.styleclass) - return modeltypes.DiagramType.UNKNOWN + return m.DiagramType.UNKNOWN class FilterSet(cabc.MutableSet): """A set that stores filter_names and invalidates diagram cache.""" def __init__( self, - diagram: diagram.AbstractDiagram, # pylint: disable=redefined-outer-name + diagram: m.AbstractDiagram, ) -> None: self._set: set[str] = set() self._diagram = diagram @@ -374,7 +366,7 @@ class InterfaceContextDiagram(ContextDiagram): def __init__( self, class_: str, - obj: common.GenericElement, + obj: m.ModelElement, *, render_styles: dict[str, styling.Styler] | None = None, default_render_parameters: dict[str, t.Any], @@ -418,7 +410,7 @@ def name(self) -> str: # type: ignore def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: params["elkdata"] = exchanges.get_elkdata_for_exchanges( - self, exchanges.FunctionalContextCollector, params # type: ignore + self, exchanges.FunctionalContextCollector, params ) return super()._create_diagram(params) @@ -434,7 +426,7 @@ class ClassTreeDiagram(ContextDiagram): def __init__( self, class_: str, - obj: common.GenericElement, + obj: m.ModelElement, *, render_styles: dict[str, styling.Styler] | None = None, default_render_parameters: dict[str, t.Any], @@ -484,6 +476,7 @@ def _create_diagram(self, params: dict[str, t.Any]) -> cdiagram.Diagram: data, legend = tree_view.collector(self, params) params["elkdata"] = data class_diagram = super()._create_diagram(params) + assert class_diagram.viewport is not None width, height = class_diagram.viewport.size axis: t.Literal["x", "y"] if params["elk.direction"] in {"DOWN", "UP"}: @@ -553,7 +546,7 @@ class RealizationViewDiagram(ContextDiagram): def __init__( self, class_: str, - obj: common.GenericElement, + obj: m.ModelElement, *, render_styles: dict[str, styling.Styler] | None = None, default_render_parameters: dict[str, t.Any], @@ -645,7 +638,7 @@ class DataFlowViewDiagram(ContextDiagram): def __init__( self, class_: str, - obj: common.GenericElement, + obj: m.ModelElement, *, render_styles: dict[str, styling.Styler] | None = None, default_render_parameters: dict[str, t.Any], diff --git a/capellambse_context_diagrams/filters.py b/capellambse_context_diagrams/filters.py index a019ed7e..d5eea536 100644 --- a/capellambse_context_diagrams/filters.py +++ b/capellambse_context_diagrams/filters.py @@ -8,7 +8,8 @@ import re import typing as t -from capellambse.model import common, fa +import capellambse.model as m +from capellambse.metamodel import fa SHOW_EX_ITEMS = "show.functional.exchanges.exchange.items.filter" """Show the name of `ComponentExchange` or `FunctionalExchange` and its @@ -52,7 +53,7 @@ """A map that for relabelling specific ModelObject types.""" -def exchange_items(obj: common.GenericElement) -> str: +def exchange_items(obj: m.ModelElement) -> str: """Return `obj`'s `ExchangeItem`s wrapped in [E1,...] and separated by ','. """ @@ -63,7 +64,7 @@ def exchange_items(obj: common.GenericElement) -> str: def exchange_name_and_items( - obj: common.GenericElement, label: str | None = None + obj: m.ModelElement, label: str | None = None ) -> str: """Return `obj`'s name and `ExchangeItem`s if there are any.""" label = label or obj.name @@ -72,16 +73,14 @@ def exchange_name_and_items( return label -def uuid_filter(obj: common.GenericElement, label: str | None = None) -> str: +def uuid_filter(obj: m.ModelElement, label: str | None = None) -> str: """Return `obj`'s name or `obj` if string w/o UUIDs in it.""" filtered_label = label if label is not None else obj.name assert isinstance(filtered_label, str) return UUID_PTRN.sub("", filtered_label) -def relabel_system_exchange( - obj: common.GenericElement, label: str | None -) -> str: +def relabel_system_exchange(obj: m.ModelElement, label: str | None) -> str: """Return converted label from obj, a system exchanges.""" label_map = LABEL_CONVERSION if patch := label_map.get(type(obj).__name__): @@ -90,7 +89,7 @@ def relabel_system_exchange( FILTER_LABEL_ADJUSTERS: dict[ - str, cabc.Callable[[common.GenericElement, str | None], str] + str, cabc.Callable[[m.ModelElement, str | None], str] ] = { EX_ITEMS: lambda obj, _: exchange_items(obj), SHOW_EX_ITEMS: exchange_name_and_items, @@ -107,7 +106,7 @@ def relabel_system_exchange( def sort_exchange_items_label( value: bool, - exchange: common.GenericElement, + exchange: m.ModelElement, adjustments: dict[str, t.Any], ) -> None: """Sort the exchange items in the exchange label if value is true.""" @@ -118,6 +117,6 @@ def sort_exchange_items_label( RENDER_ADJUSTERS: dict[ - str, cabc.Callable[[bool, common.GenericElement, dict[str, t.Any]], None] + str, cabc.Callable[[bool, m.ModelElement, dict[str, t.Any]], None] ] = {"sorted_exchangedItems": sort_exchange_items_label} """Available custom render parameter-solvers registry.""" diff --git a/capellambse_context_diagrams/serializers.py b/capellambse_context_diagrams/serializers.py index 4df27002..269b2c7e 100644 --- a/capellambse_context_diagrams/serializers.py +++ b/capellambse_context_diagrams/serializers.py @@ -14,7 +14,7 @@ import logging import typing as t -from capellambse import diagram +from capellambse import diagram as cdiagram from capellambse.svg import decorations from . import _elkjs, context @@ -34,8 +34,8 @@ """ EdgeContext = tuple[ _elkjs.ELKOutputEdge, - diagram.Vector2D, - diagram.Box | diagram.Edge | None, + cdiagram.Vector2D, + cdiagram.DiagramElement | None, ] REMAP_STYLECLASS: dict[t.Any, str | None] = {"Unset": "Association"} @@ -54,12 +54,12 @@ class DiagramSerializer: instance. """ - diagram: diagram.Diagram + diagram: cdiagram.Diagram def __init__(self, elk_diagram: context.ContextDiagram) -> None: self.model = elk_diagram.target._model self._diagram = elk_diagram - self._cache: dict[str, diagram.Box | diagram.Edge] = {} + self._cache: dict[str, cdiagram.DiagramElement] = {} self._edges: dict[str, EdgeContext] = {} self._junctions: dict[str, EdgeContext] = {} @@ -67,8 +67,8 @@ def make_diagram( self, data: _elkjs.ELKOutputData, **kwargs: dict[str, t.Any], - ) -> diagram.Diagram: - """Transform a layouted diagram into an `diagram.Diagram`. + ) -> cdiagram.Diagram: + """Transform a layouted diagram into a `diagram.Diagram`. Parameters ---------- @@ -81,13 +81,13 @@ def make_diagram( A [`diagram.Diagram`][capellambse.diagram.Diagram] constructed from the input data. """ - self.diagram = diagram.Diagram( + self.diagram = cdiagram.Diagram( self._diagram.name.replace("/", "\\"), styleclass=self._diagram.styleclass, params=kwargs, ) for child in data.children: - self.deserialize_child(child, diagram.Vector2D(), None) + self.deserialize_child(child, cdiagram.Vector2D(), None) for edge, ref, parent in self._edges.values(): self.deserialize_child(edge, ref, parent) @@ -104,8 +104,8 @@ def make_diagram( def deserialize_child( self, child: _elkjs.ELKOutputChild, - ref: diagram.Vector2D, - parent: diagram.Box | diagram.Edge | None, + ref: cdiagram.Vector2D, + parent: cdiagram.DiagramElement | None, ) -> None: """Converts a `child` into aird elements and adds it to the diagram. @@ -144,9 +144,9 @@ class type that stores all previously named classes. uuid = child.id styleoverrides = self.get_styleoverrides(uuid, child, derived=derived) - element: diagram.Box | diagram.Edge | diagram.Circle + element: cdiagram.Box | cdiagram.Edge | cdiagram.Circle if child.type in {"node", "port"}: - assert parent is None or isinstance(parent, diagram.Box) + assert parent is None or isinstance(parent, cdiagram.Box) has_symbol_cls = makers.is_symbol(styleclass) is_port = child.type == "port" box_type = ("box", "symbol")[ @@ -166,7 +166,7 @@ class type that stores all previously named classes. assert isinstance(child, _elkjs.ELKOutputNode) features = handle_features(child) - element = diagram.Box( + element = cdiagram.Box( ref, size, uuid=uuid, @@ -200,9 +200,11 @@ class type that stores all previously named classes. else: source = self._cache[source_id] target = self._cache[target_id] + assert isinstance(source, cdiagram.Box) + assert isinstance(target, cdiagram.Box) refpoints = route_shortest_connection(source, target) - element = diagram.Edge( + element = cdiagram.Edge( refpoints, uuid=child.id, source=self.diagram[source_id], @@ -217,9 +219,9 @@ class type that stores all previously named classes. assert parent is not None if not parent.port: if parent.JSON_TYPE != "symbol": - parent.styleoverrides |= styleoverrides + parent.styleoverrides.update(styleoverrides) - if isinstance(parent, diagram.Box): + if isinstance(parent, cdiagram.Box): attr_name = "floating_labels" else: attr_name = "labels" @@ -227,17 +229,17 @@ class type that stores all previously named classes. if labels := getattr(parent, attr_name): label_box = labels[-1] label_box.label += " " + child.text - label_box.size = diagram.Vector2D( + label_box.size = cdiagram.Vector2D( max(label_box.size.x, child.size.width), label_box.size.y + child.size.height, ) - label_box.pos = diagram.Vector2D( + label_box.pos = cdiagram.Vector2D( min(label_box.pos.x, ref.x + child.position.x), label_box.pos.y, ) else: labels.append( - diagram.Box( + cdiagram.Box( ref + (child.position.x, child.position.y), (child.size.width, child.size.height), label=child.text, @@ -248,12 +250,14 @@ class type that stores all previously named classes. element = parent elif child.type == "junction": uuid = uuid.rsplit("_", maxsplit=1)[0] - pos = diagram.Vector2D(child.position.x, child.position.y) + pos = cdiagram.Vector2D(child.position.x, child.position.y) if self._is_hierarchical(uuid): # FIXME should this use `parent` instead? - pos += self.diagram[self._diagram.target.uuid].pos + target_box = self.diagram[self._diagram.target.uuid] + assert isinstance(target_box, cdiagram.Box) + pos += target_box.pos - element = diagram.Circle( + element = cdiagram.Circle( ref + pos, 5, uuid=child.id, @@ -276,15 +280,18 @@ class type that stores all previously named classes. self.deserialize_child(i, ref, element) def _is_hierarchical(self, uuid: str) -> bool: - def is_contained(obj: diagram.Box) -> bool: - if obj.port and obj.parent and obj.parent.parent: - parent = obj.parent.parent - else: - parent = obj.parent + def is_contained(obj: cdiagram.Box) -> bool: + parent: cdiagram.DiagramElement | None = obj.parent + assert isinstance(parent, cdiagram.Box) + if obj.port and parent.parent: + parent = parent.parent return parent.uuid == self._diagram.target.uuid exchange = self.diagram[uuid] + assert isinstance(exchange, cdiagram.Edge) + assert isinstance(exchange.source, cdiagram.Box) + assert isinstance(exchange.target, cdiagram.Box) return is_contained(exchange.source) and is_contained(exchange.target) def get_styleclass(self, uuid: str) -> str | None: @@ -298,18 +305,18 @@ def get_styleclass(self, uuid: str) -> str | None: return None return uuid[2:].split(":", 1)[0] else: - return diagram.get_styleclass(melodyobj) + return melodyobj._get_styleclass() def get_styleoverrides( self, uuid: str, child: _elkjs.ELKOutputChild, *, derived: bool = False - ) -> diagram.StyleOverrides: + ) -> cdiagram.StyleOverrides: """Return [`styling.CSSStyles`][capellambse_context_diagrams.styling.CSSStyles] from a given [`_elkjs.ELKOutputChild`][capellambse_context_diagrams._elkjs.ELKOutputChild]. """ style_condition = self._diagram.render_styles.get(child.type) - styleoverrides: dict[str, t.Any] = {} + styleoverrides: cdiagram.StyleOverrides = {} if style_condition is not None: if child.type != "junction": obj = self._diagram._model.by_uuid(uuid) @@ -326,17 +333,17 @@ def get_styleoverrides( style: dict[str, t.Any] if style := child.style: - styleoverrides |= style + styleoverrides.update(style) return styleoverrides def order_children(self) -> None: """Reorder diagram elements such that symbols are drawn last.""" - new_diagram = diagram.Diagram( + new_diagram = cdiagram.Diagram( self.diagram.name, styleclass=self.diagram.styleclass, params=self.diagram.params, ) - draw_last = list[diagram.DiagramElement]() + draw_last = list[cdiagram.DiagramElement]() for element in self.diagram: if element.JSON_TYPE in {"symbol", "circle"}: draw_last.append(element) @@ -363,9 +370,8 @@ def handle_features(child: _elkjs.ELKOutputNode) -> list[str]: def route_shortest_connection( - source: diagram.Box, - target: diagram.Box, -) -> list[diagram.Vector2D]: + source: cdiagram.Box, target: cdiagram.Box +) -> list[cdiagram.Vector2D]: """Calculate shortest path between boxes with 'Oblique' style. Calculate the intersection points of the line from source.center to @@ -375,10 +381,10 @@ def route_shortest_connection( line_end = target.center source_intersection = source.vector_snap( - line_start, source=line_end, style=diagram.RoutingStyle.OBLIQUE + line_start, source=line_end, style=cdiagram.RoutingStyle.OBLIQUE ) target_intersection = target.vector_snap( - line_end, source=line_start, style=diagram.RoutingStyle.OBLIQUE + line_end, source=line_start, style=cdiagram.RoutingStyle.OBLIQUE ) return [source_intersection, target_intersection] diff --git a/capellambse_context_diagrams/styling.py b/capellambse_context_diagrams/styling.py index 77eec69f..9ecc3eba 100644 --- a/capellambse_context_diagrams/styling.py +++ b/capellambse_context_diagrams/styling.py @@ -6,8 +6,8 @@ import typing as t from capellambse import diagram +from capellambse import model as m from capellambse.diagram import capstyle -from capellambse.model import common if t.TYPE_CHECKING: from . import serializers @@ -24,14 +24,14 @@ [parent_is_actor_fills_blue][capellambse_context_diagrams.styling.parent_is_actor_fills_blue] """ Styler = t.Callable[ - [common.GenericElement, "serializers.DiagramSerializer"], + [m.ModelElement, "serializers.DiagramSerializer"], t.Union[diagram.StyleOverrides, None], ] """Function that produces `CSSStyles` for given obj.""" def parent_is_actor_fills_blue( - obj: common.GenericElement, serializer: serializers.DiagramSerializer + obj: m.ModelElement, serializer: serializers.DiagramSerializer ) -> CSSStyles: """Return ``CSSStyles`` for given ``obj`` rendering it blue.""" del serializer @@ -51,7 +51,7 @@ def parent_is_actor_fills_blue( def style_center_symbol( - obj: common.GenericElement, serializer: serializers.DiagramSerializer + obj: m.ModelElement, serializer: serializers.DiagramSerializer ) -> CSSStyles: """Return ``CSSStyles`` for given ``obj``.""" if obj != serializer._diagram.target: # type: ignore[has-type] @@ -59,7 +59,7 @@ def style_center_symbol( return { "fill": capstyle.COLORS["white"], "stroke": capstyle.COLORS["gray"], - "stroke-dasharray": 3, + "stroke-dasharray": "3", } diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index a1f553d8..714272fd 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -24,7 +24,7 @@ if filename == "__main__": continue - nav[parts] = doc_path.as_posix() + nav[tuple(parts)] = doc_path.as_posix() with mkdocs_gen_files.open(full_doc_path, "w") as fd: identifier = ".".join(parts) diff --git a/pyproject.toml b/pyproject.toml index 499ebb5e..55b11838 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,10 +9,10 @@ build-backend = "setuptools.build_meta" dynamic = ["version"] name = "capellambse-context-diagrams" -description = "Extension for python-capella-mbse that adds automatically generated context diagrams for arbitrary model elements." +description = "Extension for py-capellambse that adds automatically generated context diagrams for arbitrary model elements." readme = "README.md" requires-python = ">=3.10" -license = { file = "LICENSES/Apache-2.0.txt" } +license.text = "Apache-2.0" authors = [ { name = "DB InfraGO AG" }, ] @@ -32,7 +32,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "capellambse>=0.5.9,<0.6", + "capellambse>=0.6.6,<0.7", "typing_extensions", "pydantic>=2.7.3" ] @@ -78,8 +78,8 @@ python_version = "3.11" [[tool.mypy.overrides]] module = ["tests.*"] -allow_incomplete_defs = true -allow_untyped_defs = true +disallow_incomplete_defs = false +disallow_untyped_defs = false [tool.pydocstyle] convention = "numpy" diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 57055a6b..00000000 --- a/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Copyright DB InfraGO AG and the capellambse-context-diagrams contributors -# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/test_capability_diagrams.py b/tests/test_capability_diagrams.py index 2449a0fd..27219b5b 100644 --- a/tests/test_capability_diagrams.py +++ b/tests/test_capability_diagrams.py @@ -2,13 +2,13 @@ # SPDX-License-Identifier: Apache-2.0 import capellambse +import capellambse.metamodel as mm import pytest -from capellambse.model.layers import ctx, oa # pylint: disable-next=relative-beyond-top-level, useless-suppression from .conftest import SYSTEM_ANALYSIS_PARAMS # type: ignore[import] -TEST_TYPES = (oa.OperationalCapability, ctx.Capability, ctx.Mission) +TEST_TYPES = (mm.oa.OperationalCapability, mm.sa.Capability, mm.sa.Mission) @pytest.mark.parametrize("uuid", SYSTEM_ANALYSIS_PARAMS) diff --git a/tests/test_filters.py b/tests/test_filters.py index 64acf6ba..af9c5101 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -6,9 +6,9 @@ import re import typing as t +import capellambse.metamodel as mm import pytest from capellambse import MelodyModel, diagram -from capellambse.model import crosslayer from capellambse_context_diagrams import context, filters @@ -20,9 +20,7 @@ FNC_UUID = "a5642060-c9cc-4d49-af09-defaa3024bae" INTERF_UUID = "9cbdd233-aff5-47dd-9bef-9be1277c77c3" -Types = list[ - t.Union[crosslayer.fa.FunctionalExchange, crosslayer.fa.ComponentExchange] -] +Types = list[t.Union[mm.fa.FunctionalExchange, mm.fa.ComponentExchange]] @pytest.mark.parametrize( @@ -58,10 +56,7 @@ def start_filter_apply_test( for elt in diag.nodes if isinstance( elt, - ( - crosslayer.fa.FunctionalExchange, - crosslayer.fa.ComponentExchange, - ), + (mm.fa.FunctionalExchange, mm.fa.ComponentExchange), ) ] return edges, diag.render(None, **render_params) diff --git a/tests/test_labels.py b/tests/test_labels.py index f227d078..b48fa029 100644 --- a/tests/test_labels.py +++ b/tests/test_labels.py @@ -13,10 +13,9 @@ pytest.param( "d817767f-68b7-49a5-aa47-13419d41df0a", [ - "Really long label that", - "needs wrapping else", - "its parent box is also", - "very long!", + "Really long label that needs", + "wrapping else its parent box is", + "also very long!", ], id="LogicalFunction", ), @@ -29,4 +28,5 @@ def test_context_diagrams( labels = makers.make_label(obj.name, max_width=makers.MAX_LABEL_WIDTH) - assert [label.text for label in labels] == expected_labels + actual = [label.text for label in labels] + assert actual == expected_labels