Skip to content

Commit

Permalink
Merge branch 'master' into capability-exchanges
Browse files Browse the repository at this point in the history
  • Loading branch information
Wuestengecko committed Jun 24, 2022
2 parents 8f29e77 + 3fb7496 commit d36b82d
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 45 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
python -m pip install -U pip
- name: Install pylint
run: |-
python -m pip install pylint==2.13.9
python -m pip install pylint
- name: Run pylint
run: |-
pylint -dfixme -- capellambse || exit $(($? & ~24))
pylint -dfixme capellambse || exit $(($? & ~24))
4 changes: 3 additions & 1 deletion capellambse/aird/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
Capella projects. The JSON output it produces can be fed for example
into the :mod:`capellambse.svg` module for conversion to SVG.
"""
from .vector2d import * # isort: skip
# isort: off
from .vector2d import *

from .capstyle import *
from .diagram import *
from .json_enc import *
Expand Down
25 changes: 8 additions & 17 deletions capellambse/aird/parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,13 @@ class DiagramDescriptor(t.NamedTuple):

def enumerate_diagrams(
model: loader.MelodyLoader,
*,
err_abort: bool = False,
) -> cabc.Iterator[DiagramDescriptor]:
"""Enumerate the diagrams in the model.
Parameters
----------
model
The MelodyLoader instance
err_abort
Abort when encountering any error. If False, enumerate all
diagrams that are usable and ignore the others.
"""
raw_views = model.xpath2(C.XP_VIEWS)
views: list[tuple[pathlib.PurePosixPath, etree._Element, str]] = []
Expand Down Expand Up @@ -95,7 +90,7 @@ def enumerate_diagrams(
name = descriptor[1].attrib["name"]
uid = descriptor[1].attrib["repPath"]
if not uid.startswith("#"):
raise ValueError("Invalid diagram reference: {uid!r}")
raise ValueError("Malformed diagram reference: {uid!r}")

diag_root = model[uid]
if diag_root.tag not in DIAGRAM_ROOTS:
Expand All @@ -117,13 +112,13 @@ def enumerate_diagrams(
styleclass = urllib.parse.unquote(styleclass_match.group(1))

target_anchors = list(descriptor[1].iterchildren("target"))
if not target_anchors:
raise RuntimeError("Invalid XML: No <target> anchors found")
if len(target_anchors) != 1:
raise RuntimeError("Invalid XML: More than 1 <target> anchor")
raise RuntimeError(
f"Expected 1 <target> anchor, found {len(target_anchors)}"
)
target_href = target_anchors[0].get("href")
if not target_href:
raise RuntimeError("Invalid XML: <target> has no href")
raise RuntimeError("<target> anchor has no href")
target = model.follow_link(descriptor[1], target_href)

yield DiagramDescriptor(
Expand All @@ -134,14 +129,10 @@ def enumerate_diagrams(
viewpoint=descriptor[2],
target=target,
)
except Exception:
C.LOGGER.exception(
"Error parsing descriptor for diagram with uid %r and name %r",
uid,
name,
except Exception as err:
C.LOGGER.warning(
"Ignoring invalid diagram %s (%r): %s", uid, name, err
)
if err_abort:
raise


def parse_diagrams(
Expand Down
2 changes: 0 additions & 2 deletions capellambse/aird/parser/_box_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,8 +566,6 @@ def _make_snapped_floating_label(


def _is_collapsed(seb: C.SemanticElementBuilder) -> bool:
# pylint: disable=undefined-loop-variable
# <https://github.com/PyCQA/pylint/issues/1175>
for data_container in seb.data_element.iterchildren():
if data_container.get("type") == "7002":
break
Expand Down
3 changes: 0 additions & 3 deletions capellambse/loader/filehandler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,6 @@ def write_transaction(
alternative branch name to push to (which may not yet exist
on the remote).
"""
# pylint: disable=no-self-use
# This must be a regular method (not static) in order to avoid type
# checking issues in subclasses.

class EmptyTransaction:
def __enter__(self):
Expand Down
4 changes: 2 additions & 2 deletions capellambse/loader/filehandler/gitfilehandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def __init__(
commit_msg: str = "Changes made with python-capellambse",
remote_branch: str | None = None,
push: bool = True,
push_options: cabc.Sequence[str] | None = None,
push_options: cabc.Sequence[str] = (),
**kw: t.Any,
) -> None:
"""Create a transaction that records all changes as a new commit.
Expand Down Expand Up @@ -239,7 +239,7 @@ def __init__(
self.__dry_run = dry_run
self.__commit_msg = commit_msg
self.__push = push
self.__push_opts = [f"--push-option={i}" for i in push_options or ()]
self.__push_opts = [f"--push-option={i}" for i in push_options]

self.__gitenv: dict[str, str] = {}
if author_name:
Expand Down
4 changes: 2 additions & 2 deletions capellambse/loader/filehandler/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ def read(self, n: int = -1) -> bytes:
self.__buffer = self.__buffer[n:]
return chunk

def readable(self) -> bool: # pylint: disable=no-self-use
def readable(self) -> bool:
return True

def write(self, s: bytes | bytearray) -> int:
raise TypeError("Cannot write to a read-only stream")

def writable(self) -> bool: # pylint: disable=no-self-use
def writable(self) -> bool:
return False

def close(self) -> None:
Expand Down
10 changes: 8 additions & 2 deletions capellambse/loader/xmltools.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,16 @@ def __set__(self, obj, value) -> None:
try:
roundtripped = self.returntype(stringified)
except (TypeError, ValueError) as err:
raise TypeError(f"Value is not round-trip safe: {value}") from err
raise TypeError(
"Value is not round-trip safe:"
f" Cannot read back {stringified} as {value}"
) from err

if roundtripped != value:
raise TypeError(f"Value is not round-trip safe: {value}")
raise TypeError(
"Value is not round-trip safe:"
f"{value} would be read back as {roundtripped}"
)

xml_element.attrib[self.attribute] = stringified

Expand Down
17 changes: 17 additions & 0 deletions capellambse/model/crosslayer/capellacommon.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ class StateTransition(c.GenericElement):
guard = c.AttrProxyAccessor(capellacore.Constraint, "guard")


@c.xtype_handler(None)
class GenericTrace(c.GenericElement):
"""A trace between two elements."""

source = c.AttrProxyAccessor(c.GenericElement, attr="sourceElement")
target = c.AttrProxyAccessor(c.GenericElement, attr="targetElement")

@property
def name(self) -> str: # type: ignore
return f"[{self.__class__.__name__}] to {self.target.name} ({self.target.uuid})"


c.set_accessor(
AbstractStateMode,
"realized_states",
Expand Down Expand Up @@ -140,3 +152,8 @@ class StateTransition(c.GenericElement):
"transitions",
c.ProxyAccessor(StateTransition, aslist=c.ElementList),
)
c.set_accessor(
c.GenericElement,
"traces",
c.ProxyAccessor(GenericTrace, aslist=c.ElementList),
)
26 changes: 17 additions & 9 deletions capellambse/model/crosslayer/fa.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright DB Netz AG and the capellambse contributors
# SPDX-License-Identifier: Apache-2.0

"""Implementation of objects and relations for Functional Analysis
"""Implementation of objects and relations for Functional Analysis.
Functional Analysis objects inheritance tree (taxonomy):
Expand Down Expand Up @@ -83,7 +83,7 @@ def __dir__(self) -> list[str]:

@c.xtype_handler(None)
class AbstractFunction(c.GenericElement):
"""An AbstractFunction"""
"""An AbstractFunction."""

available_in_states = c.AttrProxyAccessor(
capellacommon.State, "availableInStates", aslist=c.ElementList
Expand All @@ -92,7 +92,7 @@ class AbstractFunction(c.GenericElement):

@c.xtype_handler(None)
class FunctionPort(c.GenericElement):
"""A function port"""
"""A function port."""

owner = c.ParentAccessor(c.GenericElement)
exchanges: c.Accessor
Expand Down Expand Up @@ -150,8 +150,14 @@ def owner(self) -> ComponentExchange | None:
return self.allocating_component_exchange


class FunctionalChainInvolvement(interaction.AbstractInvolvement):
"""Abstract class for FunctionalChainInvolvementLink/Function."""

_xmltag = "ownedFunctionalChainInvolvements"


@c.xtype_handler(None)
class FunctionalChainInvolvementLink(interaction.AbstractInvolvement):
class FunctionalChainInvolvementLink(FunctionalChainInvolvement):
"""An element linking a FunctionalChain to an Exchange."""

exchanged_items = c.AttrProxyAccessor(
Expand All @@ -162,20 +168,22 @@ class FunctionalChainInvolvementLink(interaction.AbstractInvolvement):
)


@c.xtype_handler(None)
class FunctionalChainInvolvementFunction(FunctionalChainInvolvement):
"""An element linking a FunctionalChain to a Function."""


@c.xtype_handler(None)
class FunctionalChain(c.GenericElement):
"""A functional chain."""

_xmltag = "ownedFunctionalChains"

involved = c.ProxyAccessor(
c.GenericElement,
XT_FCI,
aslist=c.MixedElementList,
follow="involved",
c.GenericElement, XT_FCI, aslist=c.MixedElementList, follow="involved"
)
involvements = c.ProxyAccessor(
FunctionalChainInvolvementLink, XT_FCI, aslist=c.ElementList
c.GenericElement, XT_FCI, aslist=c.ElementList
)


Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ disable = [
"missing-final-newline", # auto-formatting
"missing-function-docstring",
"missing-kwoa", # handled by mypy
"missing-method-docstring",
"missing-module-docstring",
"mixed-line-endings", # auto-formatting
"multiple-imports", # auto-formatting
Expand Down
14 changes: 14 additions & 0 deletions tests/data/melodymodel/5_2/Melody Model Test.capella
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,20 @@ The predator is far away</bodies>
sourceElement="#0fef2887-04ce-4406-b1a1-a1b35e1ce0f3"/>
</ownedClasses>
</ownedDataPkgs>
<ownedDataPkgs xsi:type="org.polarsys.capella.core.data.information:DataPkg"
id="80b36e27-cf57-44e6-862b-37f52b77bcc8" name="TestTrace">
<ownedClasses xsi:type="org.polarsys.capella.core.data.information:Class"
id="ed272baf-43f2-4fa1-ad50-49c00563258b" name="Class TraceTarget"/>
<ownedClasses xsi:type="org.polarsys.capella.core.data.information:Class"
id="ad876857-33d3-4f2e-9fe2-71545a78352d" name="Class TraceSource">
<ownedTraces xsi:type="org.polarsys.capella.core.data.capellacommon:GenericTrace"
id="9f84f273-1af4-49c2-a9f1-143e94ab816b" targetElement="#ed272baf-43f2-4fa1-ad50-49c00563258b"
sourceElement="#ad876857-33d3-4f2e-9fe2-71545a78352d"/>
<ownedTraces xsi:type="org.polarsys.capella.core.data.capellacommon:GenericTrace"
id="0880af85-4f96-4a77-b588-2e7a0385629d" targetElement="#01788b49-ccef-4a37-93d2-119287f8dd53"
sourceElement="#ad876857-33d3-4f2e-9fe2-71545a78352d"/>
</ownedClasses>
</ownedDataPkgs>
<ownedClasses xsi:type="org.polarsys.capella.core.data.information:Class"
id="959b5222-7717-4ee9-bd3a-f8a209899464" name="1st Specialization of SuperClass"
summary="" description="&lt;p>This is a test description for a class.&lt;/p>&#xA;"
Expand Down
66 changes: 62 additions & 4 deletions tests/test_model_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ def test_CommunicationMean(model: capellambse.MelodyModel) -> None:


@pytest.mark.parametrize(
"fc_uuid,link_uuid,target_uuid",
"chain_uuid,link_uuid,target_uuid",
[
pytest.param(
"d588e41f-ec4d-4fa9-ad6d-056868c66274",
Expand All @@ -558,13 +558,13 @@ def test_CommunicationMean(model: capellambse.MelodyModel) -> None:
),
],
)
def test_FunctionalChainInvolvementLink_attributes(
def test_FunctionalChainInvolvementLink_has_exchanged_items_and_exchange_context(
model_5_2: capellambse.MelodyModel,
fc_uuid: str,
chain_uuid: str,
link_uuid: str,
target_uuid: str,
) -> None:
chain = model_5_2.by_uuid(fc_uuid)
chain = model_5_2.by_uuid(chain_uuid)
link = model_5_2.by_uuid(link_uuid)
target = model_5_2.by_uuid(target_uuid)
ex_item_uuids = [ex.uuid for ex in link.exchanged_items]
Expand All @@ -581,6 +581,64 @@ def test_FunctionalChainInvolvementLink_attributes(
assert link.name == f"[FunctionalChainInvolvementLink] to {expected_end}"


@pytest.mark.parametrize(
"trace_uuid,expected",
[
pytest.param(
"9f84f273-1af4-49c2-a9f1-143e94ab816b",
"[GenericTrace] to Class TraceTarget (ed272baf-43f2-4fa1-ad50-49c00563258b)",
),
pytest.param(
"0880af85-4f96-4a77-b588-2e7a0385629d",
"[GenericTrace] to Hunt (01788b49-ccef-4a37-93d2-119287f8dd53)",
),
],
)
def test_GenericElement_has_GenericTraces(
model_5_2: capellambse.MelodyModel, trace_uuid: str, expected: str
) -> None:
cls = model_5_2.by_uuid("ad876857-33d3-4f2e-9fe2-71545a78352d")
trace = model_5_2.by_uuid(trace_uuid)

assert trace in cls.traces
assert trace.name == expected


@pytest.mark.parametrize(
"chain_uuid,fnc_uuid,target_uuid",
[
pytest.param(
"d588e41f-ec4d-4fa9-ad6d-056868c66274",
"8e732f5a-a760-468c-b862-ba1a276206d1",
"8bcb11e6-443b-4b92-bec2-ff1d87a224e7",
id="OperationalProcess",
),
pytest.param(
"dfc4341d-253a-4ae9-8a30-63a9d9faca39",
"28208081-40b1-4030-b341-f75374095717",
"ceffa011-7b66-4b3c-9885-8e075e312ffa",
id="FunctionalChain",
),
],
)
def test_FunctionalChainInvolvementFunction_appears_in_chain_involvements(
model_5_2: capellambse.MelodyModel,
chain_uuid: str,
fnc_uuid: str,
target_uuid: str,
) -> None:
chain = model_5_2.by_uuid(chain_uuid)
fnc = model_5_2.by_uuid(fnc_uuid)
target = model_5_2.by_uuid(target_uuid)
expected_end = f"{target.name} ({target.uuid})"

assert fnc in chain.involvements
assert fnc.involved == target
assert (
fnc.name == f"[FunctionalChainInvolvementFunction] to {expected_end}"
)


class TestArchitectureLayers:
@pytest.mark.parametrize(
"layer,definitions",
Expand Down

0 comments on commit d36b82d

Please sign in to comment.