diff --git a/capella2polarion/__main__.py b/capella2polarion/__main__.py
index 06c9e2ba..3a47e75e 100644
--- a/capella2polarion/__main__.py
+++ b/capella2polarion/__main__.py
@@ -11,9 +11,10 @@
import click
from capellambse import cli_helpers
+from capella2polarion import capella_work_item
from capella2polarion import polarion_worker as pw
from capella2polarion.capella2polarioncli import Capella2PolarionCli
-from capella2polarion.elements import serialize
+from capella2polarion.capella_polarion_conversion import element_converter
logger = logging.getLogger(__name__)
@@ -108,21 +109,21 @@ def synchronize(ctx: click.core.Context) -> None:
capella_to_polarion_cli.load_capella_diagramm_cache_index()
polarion_worker = pw.PolarionWorker(
capella_to_polarion_cli.polarion_params,
- serialize.resolve_element_type,
+ capella_to_polarion_cli.capella_model,
+ element_converter.resolve_element_type,
)
assert (
capella_to_polarion_cli.capella_diagram_cache_index_content is not None
)
polarion_worker.load_elements_and_type_map(
capella_to_polarion_cli.synchronize_config_content,
- capella_to_polarion_cli.capella_model,
capella_to_polarion_cli.capella_diagram_cache_index_content,
)
polarion_worker.fill_xtypes()
polarion_worker.load_polarion_work_item_map()
description_references: typing.Any = {}
- new_work_items: dict[str, serialize.CapellaWorkItem]
+ new_work_items: dict[str, capella_work_item.CapellaWorkItem]
new_work_items = polarion_worker.create_work_items(
capella_to_polarion_cli.capella_diagram_cache_folder_path,
capella_to_polarion_cli.capella_model,
@@ -136,7 +137,6 @@ def synchronize(ctx: click.core.Context) -> None:
description_references,
)
polarion_worker.patch_work_items(
- capella_to_polarion_cli.capella_model,
new_work_items,
description_references,
capella_to_polarion_cli.synchronize_config_roles,
diff --git a/capella2polarion/elements/__init__.py b/capella2polarion/capella_polarion_conversion/__init__.py
similarity index 100%
rename from capella2polarion/elements/__init__.py
rename to capella2polarion/capella_polarion_conversion/__init__.py
diff --git a/capella2polarion/elements/serialize.py b/capella2polarion/capella_polarion_conversion/element_converter.py
similarity index 90%
rename from capella2polarion/elements/serialize.py
rename to capella2polarion/capella_polarion_conversion/element_converter.py
index 767eeae0..78d389e0 100644
--- a/capella2polarion/elements/serialize.py
+++ b/capella2polarion/capella_polarion_conversion/element_converter.py
@@ -14,7 +14,6 @@
import capellambse
import markupsafe
-import polarion_rest_api_client as polarion_api
from capellambse import helpers as chelpers
from capellambse.model import common
from capellambse.model import diagram as diagr
@@ -22,6 +21,10 @@
from capellambse.model.layers import oa, pa
from lxml import etree
+from capella2polarion.polarion_connector import polarion_repo
+
+from .. import capella_work_item
+
RE_DESCR_LINK_PATTERN = re.compile(r"([^<]+)")
RE_DESCR_DELETED_PATTERN = re.compile(
f""
@@ -54,20 +57,6 @@
logger = logging.getLogger(__name__)
-class CapellaWorkItem(polarion_api.WorkItem):
- """A custom WorkItem class with additional capella related attributes."""
-
- class Condition(t.TypedDict):
- """A class to describe a pre or post condition."""
-
- type: str
- value: str
-
- uuid_capella: str
- preCondition: Condition | None
- postCondition: Condition | None
-
-
def resolve_element_type(type_: str) -> str:
"""Return a valid Type ID for polarion for a given ``obj``."""
return type_[0].lower() + type_[1:]
@@ -135,7 +124,9 @@ def _get_requirement_types_text(
return _format_texts(type_texts)
-def _condition(html: bool, value: str) -> CapellaWorkItem.Condition:
+def _condition(
+ html: bool, value: str
+) -> capella_work_item.CapellaWorkItem.Condition:
_type = "text/html" if html else "text/plain"
return {"type": _type, "value": value}
@@ -145,11 +136,15 @@ class CapellaWorkItemSerializer:
diagram_cache_path: pathlib.Path
polarion_type_map: dict[str, str]
+ capella_polarion_mapping: polarion_repo.PolarionDataRepository
model: capellambse.MelodyModel
- polarion_id_map: dict[str, str]
descr_references: dict[str, list[str]]
+
serializers: dict[
- str, cabc.Callable[[common.GenericElement], CapellaWorkItem]
+ str,
+ cabc.Callable[
+ [common.GenericElement], capella_work_item.CapellaWorkItem
+ ],
]
serializer_mapping: dict[str, str]
@@ -158,14 +153,14 @@ def __init__(
diagram_cache_path: pathlib.Path,
polarion_type_map: dict[str, str],
model: capellambse.MelodyModel,
- polarion_id_map: dict[str, str],
+ capella_polarion_mapping: polarion_repo.PolarionDataRepository,
descr_references: dict[str, list[str]],
serializer_mapping: dict[str, str] | None = None,
):
self.diagram_cache_path = diagram_cache_path
self.polarion_type_map = polarion_type_map
self.model = model
- self.polarion_id_map = polarion_id_map
+ self.capella_polarion_mapping = capella_polarion_mapping
self.descr_references = descr_references
self.serializers = {
"include_pre_and_post_condition": self.include_pre_and_post_condition,
@@ -177,7 +172,7 @@ def __init__(
def serialize(
self, obj: diagr.Diagram | common.GenericElement
- ) -> CapellaWorkItem | None:
+ ) -> capella_work_item.CapellaWorkItem | None:
"""Return a CapellaWorkItem for the given diagram or element."""
try:
if isinstance(obj, diagr.Diagram):
@@ -195,7 +190,9 @@ def serialize(
logger.error("Serializing model element failed. %s", error.args[0])
return None
- def diagram(self, diag: diagr.Diagram) -> CapellaWorkItem:
+ def diagram(
+ self, diag: diagr.Diagram
+ ) -> capella_work_item.CapellaWorkItem:
"""Serialize a diagram for Polarion."""
diagram_path = self.diagram_cache_path / f"{diag.uuid}.svg"
src = _decode_diagram(diagram_path)
@@ -205,7 +202,7 @@ def diagram(self, diag: diagr.Diagram) -> CapellaWorkItem:
description = (
f''
)
- return CapellaWorkItem(
+ return capella_work_item.CapellaWorkItem(
type="diagram",
title=diag.name,
description_type="text/html",
@@ -216,13 +213,13 @@ def diagram(self, diag: diagr.Diagram) -> CapellaWorkItem:
def _generic_work_item(
self, obj: common.GenericElement
- ) -> CapellaWorkItem:
+ ) -> capella_work_item.CapellaWorkItem:
xtype = self.polarion_type_map.get(obj.uuid, type(obj).__name__)
raw_description = getattr(obj, "description", markupsafe.Markup(""))
uuids, value = self._sanitize_description(obj, raw_description)
self.descr_references[obj.uuid] = uuids
requirement_types = _get_requirement_types_text(obj)
- return CapellaWorkItem(
+ return capella_work_item.CapellaWorkItem(
type=resolve_element_type(xtype),
title=obj.name,
description_type="text/html",
@@ -286,7 +283,7 @@ def replace_markup(
except KeyError:
logger.error("Found link to non-existing model element: %r", uuid)
return strike_through(match.group(default_group))
- if pid := self.polarion_id_map.get(uuid):
+ if pid := self.capella_polarion_mapping.get_work_item_id(uuid):
referenced_uuids.append(uuid)
return POLARION_WORK_ITEM_URL.format(pid=pid)
logger.warning("Found reference to non-existing work item: %r", uuid)
@@ -294,7 +291,7 @@ def replace_markup(
def include_pre_and_post_condition(
self, obj: PrePostConditionElement
- ) -> CapellaWorkItem:
+ ) -> capella_work_item.CapellaWorkItem:
"""Return generic attributes and pre- and post-condition."""
def get_condition(cap: PrePostConditionElement, name: str) -> str:
@@ -328,14 +325,18 @@ def get_linked_text(
self.descr_references[obj.uuid] = uuids
return value
- def constraint(self, obj: capellacore.Constraint) -> CapellaWorkItem:
+ def constraint(
+ self, obj: capellacore.Constraint
+ ) -> capella_work_item.CapellaWorkItem:
"""Return attributes for a ``Constraint``."""
work_item = self._generic_work_item(obj)
# pylint: disable-next=attribute-defined-outside-init
work_item.description = self.get_linked_text(obj)
return work_item
- def _include_actor_in_type(self, obj: cs.Component) -> CapellaWorkItem:
+ def _include_actor_in_type(
+ self, obj: cs.Component
+ ) -> capella_work_item.CapellaWorkItem:
"""Return attributes for a ``Component``."""
work_item = self._generic_work_item(obj)
if obj.is_actor:
@@ -348,7 +349,7 @@ def _include_actor_in_type(self, obj: cs.Component) -> CapellaWorkItem:
def _include_nature_in_type(
self, obj: pa.PhysicalComponent
- ) -> CapellaWorkItem:
+ ) -> capella_work_item.CapellaWorkItem:
"""Return attributes for a ``PhysicalComponent``."""
work_item = self._include_actor_in_type(obj)
xtype = work_item.type
diff --git a/capella2polarion/capella_polarion_conversion/link_converter.py b/capella2polarion/capella_polarion_conversion/link_converter.py
new file mode 100644
index 00000000..ab73c5f5
--- /dev/null
+++ b/capella2polarion/capella_polarion_conversion/link_converter.py
@@ -0,0 +1,295 @@
+# Copyright DB Netz AG and contributors
+# SPDX-License-Identifier: Apache-2.0
+"""Objects for synchronization of Capella model objects to Polarion."""
+from __future__ import annotations
+
+import collections.abc as cabc
+import logging
+from collections import defaultdict
+
+import capellambse
+import polarion_rest_api_client as polarion_api
+from capellambse.model import common
+from capellambse.model import diagram as diag
+from capellambse.model.crosslayer import fa
+
+from capella2polarion import capella_work_item
+from capella2polarion.capella_polarion_conversion import element_converter
+from capella2polarion.polarion_connector import polarion_repo
+
+logger = logging.getLogger(__name__)
+
+TYPE_RESOLVERS = {"Part": lambda obj: obj.type.uuid}
+
+
+class LinkSerializer:
+ """A converter for capella element links and description references."""
+
+ def __init__(
+ self,
+ capella_polarion_mapping: polarion_repo.PolarionDataRepository,
+ description_references: dict[str, list[str]],
+ project_id: str,
+ model: capellambse.MelodyModel,
+ ):
+ self.capella_polarion_mapping = capella_polarion_mapping
+ self.description_references = description_references
+ self.project_id = project_id
+ self.model = model
+
+ def create_links_for_work_item(
+ self,
+ obj: common.GenericElement | diag.Diagram,
+ roles,
+ ) -> list[polarion_api.WorkItemLink]:
+ """Create work item links for a given Capella object."""
+ if isinstance(obj, diag.Diagram):
+ repres = f""
+ else:
+ repres = obj._short_repr_()
+
+ work_item = (
+ self.capella_polarion_mapping.get_work_item_by_capella_uuid(
+ obj.uuid
+ )
+ )
+ assert work_item is not None
+ new_links: list[polarion_api.WorkItemLink] = []
+ typ = work_item.type[0].upper() + work_item.type[1:]
+ for role_id in roles.get(typ, []):
+ if role_id == "description_reference":
+ new_links.extend(
+ self._handle_description_reference_links(
+ obj,
+ work_item.id,
+ role_id,
+ {},
+ )
+ )
+ elif role_id == "diagram_elements":
+ new_links.extend(
+ self._handle_diagram_reference_links(
+ obj, work_item.id, role_id, {}
+ )
+ )
+ elif role_id == "input_exchanges":
+ new_links.extend(
+ self._handle_exchanges(
+ obj,
+ work_item.id,
+ role_id,
+ {},
+ "inputs",
+ )
+ )
+ elif role_id == "output_exchanges":
+ new_links.extend(
+ self._handle_exchanges(
+ obj,
+ work_item.id,
+ role_id,
+ {},
+ "outputs",
+ )
+ )
+ else:
+ if (refs := getattr(obj, role_id, None)) is None:
+ logger.info(
+ "Unable to create work item link %r for [%s]. "
+ "There is no %r attribute on %s",
+ role_id,
+ work_item.id,
+ role_id,
+ repres,
+ )
+ continue
+
+ if isinstance(refs, common.ElementList):
+ new: cabc.Iterable[str] = refs.by_uuid # type: ignore[assignment]
+ else:
+ assert hasattr(refs, "uuid")
+ new = [refs.uuid]
+
+ new = set(self._get_work_item_ids(work_item.id, new, role_id))
+ new_links.extend(self._create(work_item.id, role_id, new, {}))
+ return new_links
+
+ def _get_work_item_ids(
+ self,
+ primary_id: str,
+ uuids: cabc.Iterable[str],
+ role_id: str,
+ ) -> cabc.Iterator[str]:
+ for uuid in uuids:
+ if wid := self.capella_polarion_mapping.get_work_item_id(uuid):
+ yield wid
+ else:
+ obj = self.model.by_uuid(uuid)
+ logger.info(
+ "Unable to create work item link %r for [%s]. "
+ "Couldn't identify work item for %r",
+ role_id,
+ primary_id,
+ obj._short_repr_(),
+ )
+
+ def _handle_description_reference_links(
+ self,
+ obj: common.GenericElement,
+ work_item_id: str,
+ role_id: str,
+ links: dict[str, polarion_api.WorkItemLink],
+ ) -> list[polarion_api.WorkItemLink]:
+ refs = self.description_references.get(obj.uuid, [])
+ ref_set = set(self._get_work_item_ids(work_item_id, refs, role_id))
+ return self._create(work_item_id, role_id, ref_set, links)
+
+ def _handle_diagram_reference_links(
+ self,
+ obj: diag.Diagram,
+ work_item_id: str,
+ role_id: str,
+ links: dict[str, polarion_api.WorkItemLink],
+ ) -> list[polarion_api.WorkItemLink]:
+ try:
+ refs = set(self._collect_uuids(obj.nodes))
+ refs = set(self._get_work_item_ids(work_item_id, refs, role_id))
+ ref_links = self._create(work_item_id, role_id, refs, links)
+ except StopIteration:
+ logger.exception(
+ "Could not create links for diagram %r", obj._short_repr_()
+ )
+ ref_links = []
+ return ref_links
+
+ def _collect_uuids(
+ self,
+ nodes: cabc.Iterable[common.GenericElement],
+ ) -> cabc.Iterator[str]:
+ type_resolvers = TYPE_RESOLVERS
+ for node in nodes:
+ uuid = node.uuid
+ if resolver := type_resolvers.get(type(node).__name__):
+ uuid = resolver(node)
+
+ yield uuid
+
+ def _create(
+ self,
+ primary_id: str,
+ role_id: str,
+ new: cabc.Iterable[str],
+ old: cabc.Iterable[str],
+ ) -> list[polarion_api.WorkItemLink]:
+ new = set(new) - set(old)
+ _new_links = [
+ polarion_api.WorkItemLink(
+ primary_id,
+ id,
+ role_id,
+ secondary_work_item_project=self.project_id,
+ )
+ for id in new
+ ]
+ return list(filter(None, _new_links))
+
+ def _handle_exchanges(
+ self,
+ obj: fa.Function,
+ work_item_id: str,
+ role_id: str,
+ links: dict[str, polarion_api.WorkItemLink],
+ attr: str = "inputs",
+ ) -> list[polarion_api.WorkItemLink]:
+ exchanges: list[str] = []
+ for element in getattr(obj, attr):
+ uuids = element.exchanges.by_uuid
+ exs = self._get_work_item_ids(work_item_id, uuids, role_id)
+ exchanges.extend(set(exs))
+ return self._create(work_item_id, role_id, exchanges, links)
+
+
+def create_grouped_link_fields(
+ work_item: capella_work_item.CapellaWorkItem,
+ back_links: dict[str, list[polarion_api.WorkItemLink]] | None = None,
+):
+ """Create the grouped link work items fields from the primary work item.
+
+ Parameters
+ ----------
+ work_item
+ WorkItem to create the fields for.
+ back_links
+ A dictionary of secondary WorkItem IDs to links to create
+ backlinks later.
+ """
+ wi = f"[{work_item.id}]({work_item.type} {work_item.title})"
+ logger.debug("Building grouped links for work item %r...", wi)
+ for role, grouped_links in _group_by(
+ "role", work_item.linked_work_items
+ ).items():
+ if back_links is not None:
+ for link in grouped_links:
+ key = link.secondary_work_item_id
+ back_links.setdefault(key, []).append(link)
+
+ _create_link_fields(work_item, role, grouped_links)
+
+
+def create_grouped_back_link_fields(
+ work_item: capella_work_item.CapellaWorkItem,
+ links: list[polarion_api.WorkItemLink],
+):
+ """Create backlinks for the given WorkItem using a list of backlinks.
+
+ Parameters
+ ----------
+ work_item
+ WorkItem to create the fields for
+ links
+ List of links referencing work_item as secondary
+ """
+ for role, grouped_links in _group_by("role", links).items():
+ _create_link_fields(work_item, role, grouped_links, True)
+
+
+def _group_by(
+ attr: str,
+ links: cabc.Iterable[polarion_api.WorkItemLink],
+) -> dict[str, list[polarion_api.WorkItemLink]]:
+ group = defaultdict(list)
+ for link in links:
+ key = getattr(link, attr)
+ group[key].append(link)
+ return group
+
+
+def _make_url_list(
+ links: cabc.Iterable[polarion_api.WorkItemLink], reverse: bool = False
+) -> str:
+ urls: list[str] = []
+ for link in links:
+ if reverse:
+ pid = link.primary_work_item_id
+ else:
+ pid = link.secondary_work_item_id
+
+ url = element_converter.POLARION_WORK_ITEM_URL.format(pid=pid)
+ urls.append(f"{url}")
+
+ urls.sort()
+ url_list = "\n".join(urls)
+ return f""
+
+
+def _create_link_fields(
+ work_item: capella_work_item.CapellaWorkItem,
+ role: str,
+ links: list[polarion_api.WorkItemLink],
+ reverse: bool = False,
+):
+ role = f"{role}_reverse" if reverse else role
+ work_item.additional_attributes[role] = {
+ "type": "text/html",
+ "value": _make_url_list(links, reverse),
+ }
diff --git a/capella2polarion/capella_work_item.py b/capella2polarion/capella_work_item.py
new file mode 100644
index 00000000..485c1f21
--- /dev/null
+++ b/capella2polarion/capella_work_item.py
@@ -0,0 +1,22 @@
+# Copyright DB Netz AG and contributors
+# SPDX-License-Identifier: Apache-2.0
+"""Module providing the CapellaWorkItem class."""
+from __future__ import annotations
+
+import typing as t
+
+import polarion_rest_api_client as polarion_api
+
+
+class CapellaWorkItem(polarion_api.WorkItem):
+ """A custom WorkItem class with additional capella related attributes."""
+
+ class Condition(t.TypedDict):
+ """A class to describe a pre or post condition."""
+
+ type: str
+ value: str
+
+ uuid_capella: str
+ preCondition: Condition | None
+ postCondition: Condition | None
diff --git a/capella2polarion/elements/capella_polarion_mapping.py b/capella2polarion/elements/capella_polarion_mapping.py
deleted file mode 100644
index 38312231..00000000
--- a/capella2polarion/elements/capella_polarion_mapping.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright DB Netz AG and contributors
-# SPDX-License-Identifier: Apache-2.0
-"""Module providing a universal CapellaPolarionMapping class."""
-from __future__ import annotations
-
-import bidict
-import capellambse
-
-from capella2polarion.elements import serialize
-
-
-class CapellaPolarionMapping:
- """A mapping class to access all contents by Capella and Polarion IDs."""
-
- _description_references: dict[str, set[str]]
- _id_mapping: bidict.bidict[str, str]
- _work_items: dict[str, serialize.CapellaWorkItem]
- _model: capellambse.MelodyModel
-
- def __init__(
- self,
- model: capellambse.MelodyModel,
- polarion_work_items: list[serialize.CapellaWorkItem],
- ):
- self._description_references = {}
- self._model = model
- self._id_mapping = bidict.bidict(
- {
- work_item.uuid_capella: work_item.id
- for work_item in polarion_work_items
- }
- )
- self._work_items = {
- work_item.uuid_capella: work_item
- for work_item in polarion_work_items
- }
-
- def get_work_item_id(self, capella_uuid: str) -> str | None:
- """Return a Work Item ID for a given Capella UUID."""
- return self._id_mapping.get(capella_uuid)
-
- def get_capella_uuid(self, work_item_id: str) -> str | None:
- """Return a Capella UUID for a given Work Item ID."""
- return self._id_mapping.inverse.get(work_item_id)
-
- def get_work_item_by_capella_uuid(
- self, capella_uuid: str
- ) -> serialize.CapellaWorkItem | None:
- """Return a Work Item for a provided Capella UUID."""
- return self._work_items.get(capella_uuid)
-
- def get_work_item_by_polarion_id(
- self, work_item_id: str
- ) -> serialize.CapellaWorkItem | None:
- """Return a Work Item for a provided Work Item ID."""
- return self.get_work_item_by_capella_uuid(
- self.get_capella_uuid(work_item_id) # type: ignore
- )
-
- def get_model_element_by_capella_uuid(
- self, capella_uuid: str
- ) -> capellambse.model.GenericElement | None:
- """Return a model element for a given Capella UUID."""
- try:
- return self._model.by_uuid(capella_uuid)
- except KeyError:
- return None
-
- def get_model_element_by_polarion_id(
- self, work_item_id: str
- ) -> capellambse.model.GenericElement | None:
- """Return a model element for a given Work Item ID."""
- return self.get_model_element_by_capella_uuid(
- self.get_capella_uuid(work_item_id) # type: ignore
- )
-
- def get_description_references_by_capella_uuid(
- self, capella_uuid: str
- ) -> set[str] | None:
- """Return the description references for a given Capella UUID."""
- return self._description_references.get(capella_uuid)
-
- def get_description_references_by_polarion_id(
- self, work_item_id: str
- ) -> set[str] | None:
- """Return the description references for a given Work Item ID."""
- return self.get_description_references_by_capella_uuid(
- self.get_capella_uuid(work_item_id) # type: ignore
- )
-
- def update_work_items(
- self,
- work_items: serialize.CapellaWorkItem
- | list[serialize.CapellaWorkItem],
- ):
- """Update all mappings for the given Work Items."""
- if isinstance(work_items, serialize.CapellaWorkItem):
- work_items = [work_items]
-
- self._id_mapping.update(
- {
- work_item.uuid_capella: work_item.id
- for work_item in work_items
- if work_item.id is not None
- }
- )
- self._work_items.update(
- {work_item.uuid_capella: work_item for work_item in work_items}
- )
-
- def update_description_reference(
- self, capella_uuid: str, references: list[str]
- ):
- """Add or replace description references for a given capella UUID."""
- self._description_references.update({capella_uuid: set(references)})
diff --git a/capella2polarion/elements/element.py b/capella2polarion/elements/element.py
deleted file mode 100644
index 15932050..00000000
--- a/capella2polarion/elements/element.py
+++ /dev/null
@@ -1,304 +0,0 @@
-# Copyright DB Netz AG and contributors
-# SPDX-License-Identifier: Apache-2.0
-"""Objects for synchronization of Capella model objects to Polarion."""
-from __future__ import annotations
-
-import collections.abc as cabc
-import logging
-from collections import defaultdict
-
-import polarion_rest_api_client as polarion_api
-from capellambse.model import common
-from capellambse.model import diagram as diag
-from capellambse.model.crosslayer import fa
-
-from capella2polarion.elements import serialize
-
-logger = logging.getLogger(__name__)
-
-TYPE_RESOLVERS = {"Part": lambda obj: obj.type.uuid}
-
-
-def create_links(
- obj: common.GenericElement | diag.Diagram,
- polarion_id_map,
- work_item_map,
- descr_references,
- project_id,
- model,
- roles,
-) -> list[polarion_api.WorkItemLink]:
- """Create work item links for a given Capella object."""
- if isinstance(obj, diag.Diagram):
- repres = f""
- else:
- repres = obj._short_repr_()
-
- workitem = work_item_map[obj.uuid]
- new_links: list[polarion_api.WorkItemLink] = []
- typ = workitem.type[0].upper() + workitem.type[1:]
- for role_id in roles.get(typ, []):
- if role_id == "description_reference":
- new_links.extend(
- _handle_description_reference_links(
- polarion_id_map,
- descr_references,
- project_id,
- model,
- obj,
- role_id,
- {},
- )
- )
- elif role_id == "diagram_elements":
- new_links.extend(
- _handle_diagram_reference_links(
- polarion_id_map, model, project_id, obj, role_id, {}
- )
- )
- elif role_id == "input_exchanges":
- new_links.extend(
- _handle_exchanges(
- polarion_id_map,
- model,
- project_id,
- obj,
- role_id,
- {},
- "inputs",
- )
- )
- elif role_id == "output_exchanges":
- new_links.extend(
- _handle_exchanges(
- polarion_id_map,
- model,
- project_id,
- obj,
- role_id,
- {},
- "outputs",
- )
- )
- else:
- if (refs := getattr(obj, role_id, None)) is None:
- logger.info(
- "Unable to create work item link %r for [%s]. "
- "There is no %r attribute on %s",
- role_id,
- workitem.id,
- role_id,
- repres,
- )
- continue
-
- if isinstance(refs, common.ElementList):
- new: cabc.Iterable[str] = refs.by_uuid # type: ignore[assignment]
- else:
- assert hasattr(refs, "uuid")
- new = [refs.uuid]
-
- new = set(
- _get_work_item_ids(
- polarion_id_map, model, workitem.id, new, role_id
- )
- )
- new_links.extend(
- _create(project_id, workitem.id, role_id, new, {})
- )
- return new_links
-
-
-def _get_work_item_ids(
- polarion_id_map,
- model,
- primary_id: str,
- uuids: cabc.Iterable[str],
- role_id: str,
-) -> cabc.Iterator[str]:
- for uuid in uuids:
- if wid := polarion_id_map.get(uuid):
- yield wid
- else:
- obj = model.by_uuid(uuid)
- logger.info(
- "Unable to create work item link %r for [%s]. "
- "Couldn't identify work item for %r",
- role_id,
- primary_id,
- obj._short_repr_(),
- )
-
-
-def _handle_description_reference_links(
- polarion_id_map,
- descr_references,
- project_id,
- model,
- obj: common.GenericElement,
- role_id: str,
- links: dict[str, polarion_api.WorkItemLink],
-) -> list[polarion_api.WorkItemLink]:
- refs = descr_references.get(obj.uuid, [])
- wid = polarion_id_map[obj.uuid]
- refs = set(_get_work_item_ids(polarion_id_map, model, wid, refs, role_id))
- return _create(project_id, wid, role_id, refs, links)
-
-
-def _handle_diagram_reference_links(
- polarion_id_map,
- model,
- project_id,
- obj: diag.Diagram,
- role_id: str,
- links: dict[str, polarion_api.WorkItemLink],
-) -> list[polarion_api.WorkItemLink]:
- try:
- refs = set(_collect_uuids(obj.nodes))
- wid = polarion_id_map[obj.uuid]
- refs = set(
- _get_work_item_ids(polarion_id_map, model, wid, refs, role_id)
- )
- ref_links = _create(project_id, wid, role_id, refs, links)
- except StopIteration:
- logger.exception(
- "Could not create links for diagram %r", obj._short_repr_()
- )
- ref_links = []
- return ref_links
-
-
-def _collect_uuids(
- nodes: cabc.Iterable[common.GenericElement],
-) -> cabc.Iterator[str]:
- type_resolvers = TYPE_RESOLVERS
- for node in nodes:
- uuid = node.uuid
- if resolver := type_resolvers.get(type(node).__name__):
- uuid = resolver(node)
-
- yield uuid
-
-
-def _create(
- project_id,
- primary_id: str,
- role_id: str,
- new: cabc.Iterable[str],
- old: cabc.Iterable[str],
-) -> list[polarion_api.WorkItemLink]:
- new = set(new) - set(old)
- _new_links = [
- polarion_api.WorkItemLink(
- primary_id,
- id,
- role_id,
- secondary_work_item_project=project_id,
- )
- for id in new
- ]
- return list(filter(None, _new_links))
-
-
-def _handle_exchanges(
- polarion_id_map,
- model,
- project_id,
- obj: fa.Function,
- role_id: str,
- links: dict[str, polarion_api.WorkItemLink],
- attr: str = "inputs",
-) -> list[polarion_api.WorkItemLink]:
- wid = polarion_id_map[obj.uuid]
- exchanges: list[str] = []
- for element in getattr(obj, attr):
- uuids = element.exchanges.by_uuid
- exs = _get_work_item_ids(polarion_id_map, model, wid, uuids, role_id)
- exchanges.extend(set(exs))
- return _create(project_id, wid, role_id, exchanges, links)
-
-
-def create_grouped_link_fields(
- work_item: serialize.CapellaWorkItem,
- back_links: dict[str, list[polarion_api.WorkItemLink]] | None = None,
-):
- """Create the grouped link work items fields from the primary work item.
-
- Parameters
- ----------
- work_item
- WorkItem to create the fields for.
- back_links
- A dictionary of secondary WorkItem IDs to links to create
- backlinks later.
- """
- wi = f"[{work_item.id}]({work_item.type} {work_item.title})"
- logger.debug("Building grouped links for work item %r...", wi)
- for role, grouped_links in _group_by(
- "role", work_item.linked_work_items
- ).items():
- if back_links is not None:
- for link in grouped_links:
- key = link.secondary_work_item_id
- back_links.setdefault(key, []).append(link)
-
- _create_link_fields(work_item, role, grouped_links)
-
-
-def create_grouped_back_link_fields(
- work_item: serialize.CapellaWorkItem,
- links: list[polarion_api.WorkItemLink],
-):
- """Create backlinks for the given WorkItem using a list of backlinks.
-
- Parameters
- ----------
- work_item
- WorkItem to create the fields for
- links
- List of links referencing work_item as secondary
- """
- for role, grouped_links in _group_by("role", links).items():
- _create_link_fields(work_item, role, grouped_links, True)
-
-
-def _group_by(
- attr: str,
- links: cabc.Iterable[polarion_api.WorkItemLink],
-) -> dict[str, list[polarion_api.WorkItemLink]]:
- group = defaultdict(list)
- for link in links:
- key = getattr(link, attr)
- group[key].append(link)
- return group
-
-
-def _make_url_list(
- links: cabc.Iterable[polarion_api.WorkItemLink], reverse: bool = False
-) -> str:
- urls: list[str] = []
- for link in links:
- if reverse:
- pid = link.primary_work_item_id
- else:
- pid = link.secondary_work_item_id
-
- url = serialize.POLARION_WORK_ITEM_URL.format(pid=pid)
- urls.append(f"{url}")
-
- urls.sort()
- url_list = "\n".join(urls)
- return f""
-
-
-def _create_link_fields(
- work_item: serialize.CapellaWorkItem,
- role: str,
- links: list[polarion_api.WorkItemLink],
- reverse: bool = False,
-):
- role = f"{role}_reverse" if reverse else role
- work_item.additional_attributes[role] = {
- "type": "text/html",
- "value": _make_url_list(links, reverse),
- }
diff --git a/capella2polarion/polarion_connector/__init__.py b/capella2polarion/polarion_connector/__init__.py
new file mode 100644
index 00000000..e42395a2
--- /dev/null
+++ b/capella2polarion/polarion_connector/__init__.py
@@ -0,0 +1,3 @@
+# Copyright DB Netz AG and contributors
+# SPDX-License-Identifier: Apache-2.0
+"""Package for all conversion related activities."""
diff --git a/capella2polarion/polarion_connector/polarion_repo.py b/capella2polarion/polarion_connector/polarion_repo.py
new file mode 100644
index 00000000..aaac0381
--- /dev/null
+++ b/capella2polarion/polarion_connector/polarion_repo.py
@@ -0,0 +1,118 @@
+# Copyright DB Netz AG and contributors
+# SPDX-License-Identifier: Apache-2.0
+"""Module providing a universal PolarionDataRepository class."""
+from __future__ import annotations
+
+import typing
+
+import bidict
+
+from capella2polarion import capella_work_item
+
+
+class PolarionDataRepository:
+ """A mapping class to access all contents by Capella and Polarion IDs.
+
+ This class only holds data already present in the Polarion. It only
+ receives updates if data were written to Polarion. There shall be no
+ intermediate data stored here during serialization.
+ """
+
+ _id_mapping: bidict.bidict[str, str]
+ _work_items: dict[str, capella_work_item.CapellaWorkItem]
+
+ def __init__(
+ self,
+ polarion_work_items: typing.Optional[
+ list[capella_work_item.CapellaWorkItem]
+ ] = None,
+ ):
+ if polarion_work_items is None:
+ polarion_work_items = []
+ self._id_mapping = bidict.bidict(
+ {
+ work_item.uuid_capella: work_item.id
+ for work_item in polarion_work_items
+ },
+ )
+ self._id_mapping.on_dup = bidict.OnDup(
+ key=bidict.DROP_OLD, val=bidict.DROP_OLD, kv=bidict.DROP_OLD
+ )
+ self._work_items = {
+ work_item.uuid_capella: work_item
+ for work_item in polarion_work_items
+ }
+
+ def __contains__(self, item: str) -> bool:
+ """Return True, if the given capella UUID is in the repository."""
+ return item in self._id_mapping
+
+ def __sizeof__(self):
+ """Return the amount of registered Capella UUIDs."""
+ return len(self._id_mapping)
+
+ def __getitem__(
+ self, item: str
+ ) -> typing.Tuple[str, capella_work_item.CapellaWorkItem]:
+ """Return the polarion ID and work_item for a given Capella UUID."""
+ return self._id_mapping[item], self._work_items[item]
+
+ def __iter__(self) -> typing.Iterator[str]:
+ """Iterate all Capella UUIDs."""
+ return self._id_mapping.__iter__()
+
+ def items(
+ self,
+ ):
+ """Yield all Capella UUIDs, Work Item IDs and Work Items."""
+ for uuid, polarion_id in self._id_mapping.items():
+ yield uuid, polarion_id, self._work_items[uuid]
+
+ def get_work_item_id(self, capella_uuid: str) -> str | None:
+ """Return a Work Item ID for a given Capella UUID."""
+ return self._id_mapping.get(capella_uuid)
+
+ def get_capella_uuid(self, work_item_id: str) -> str | None:
+ """Return a Capella UUID for a given Work Item ID."""
+ return self._id_mapping.inverse.get(work_item_id)
+
+ def get_work_item_by_capella_uuid(
+ self, capella_uuid: str
+ ) -> capella_work_item.CapellaWorkItem | None:
+ """Return a Work Item for a provided Capella UUID."""
+ return self._work_items.get(capella_uuid)
+
+ def get_work_item_by_polarion_id(
+ self, work_item_id: str
+ ) -> capella_work_item.CapellaWorkItem | None:
+ """Return a Work Item for a provided Work Item ID."""
+ return self.get_work_item_by_capella_uuid(
+ self.get_capella_uuid(work_item_id) # type: ignore
+ )
+
+ def update_work_items(
+ self,
+ work_items: list[capella_work_item.CapellaWorkItem],
+ ):
+ """Update all mappings for the given Work Items."""
+ for work_item in work_items:
+ if uuid_capella := self._id_mapping.inverse.get(work_item.id):
+ del self._id_mapping[uuid_capella]
+ del self._work_items[uuid_capella]
+
+ self._id_mapping.update(
+ {
+ work_item.uuid_capella: work_item.id
+ for work_item in work_items
+ if work_item.id is not None
+ }
+ )
+ self._work_items.update(
+ {work_item.uuid_capella: work_item for work_item in work_items}
+ )
+
+ def remove_work_items_by_capella_uuid(self, uuids: typing.Iterable[str]):
+ """Remove entries for the given Capella UUIDs."""
+ for uuid in uuids:
+ del self._work_items[uuid]
+ del self._id_mapping[uuid]
diff --git a/capella2polarion/polarion_worker.py b/capella2polarion/polarion_worker.py
index 0a78e09a..50f270d8 100644
--- a/capella2polarion/polarion_worker.py
+++ b/capella2polarion/polarion_worker.py
@@ -14,7 +14,12 @@
import polarion_rest_api_client as polarion_api
from capellambse.model import common
-from capella2polarion.elements import element, serialize
+from capella2polarion import capella_work_item
+from capella2polarion.capella_polarion_conversion import (
+ element_converter,
+ link_converter,
+)
+from capella2polarion.polarion_connector import polarion_repo
logger = logging.getLogger(__name__)
@@ -62,15 +67,16 @@ class PolarionWorker:
def __init__(
self,
params: PolarionWorkerParams,
+ model: capellambse.MelodyModel,
make_type_id: typing.Any,
) -> None:
self.polarion_params: PolarionWorkerParams = params
self.elements: dict[str, list[common.GenericElement]] = {}
- self.polarion_type_map: dict[str, str] = {}
- self.capella_uuid_s: set[str] = set()
+ self.polarion_type_map: dict[str, str] = {} # TODO refactor
+ self.capella_uuid_s: set[str] = set() # TODO refactor
self.x_types: set[str] = set()
- self.polarion_id_map: dict[str, str] = {}
- self.polarion_work_item_map: dict[str, serialize.CapellaWorkItem] = {}
+ self.polarion_data_repo = polarion_repo.PolarionDataRepository()
+ self.model = model
self.make_type_id: typing.Any = make_type_id
if (self.polarion_params.project_id is None) or (
len(self.polarion_params.project_id) == 0
@@ -96,7 +102,7 @@ def __init__(
self.polarion_params.delete_work_items,
polarion_api_endpoint=f"{self.polarion_params.url}/rest/v1",
polarion_access_token=self.polarion_params.private_access_token,
- custom_work_item=serialize.CapellaWorkItem,
+ custom_work_item=capella_work_item.CapellaWorkItem,
add_work_item_checksum=True,
)
self.check_client()
@@ -116,7 +122,6 @@ def check_client(self) -> None:
def load_elements_and_type_map(
self,
config: dict[str, typing.Any],
- model: capellambse.MelodyModel,
diagram_idx: list[dict[str, typing.Any]],
) -> None:
"""Return an elements and UUID to Polarion type map."""
@@ -124,7 +129,7 @@ def load_elements_and_type_map(
type_map: dict[str, str] = {}
elements: dict[str, list[common.GenericElement]] = {}
for _below, pol_types in config.items():
- below = getattr(model, _below)
+ below = getattr(self.model, _below)
for typ in pol_types:
if isinstance(typ, dict):
typ = list(typ.keys())[0]
@@ -133,7 +138,7 @@ def load_elements_and_type_map(
continue
xtype = convert_type.get(typ, typ)
- objects = model.search(xtype, below=below)
+ objects = self.model.search(xtype, below=below)
elements.setdefault(typ, []).extend(objects)
for obj in objects:
type_map[obj.uuid] = typ
@@ -173,7 +178,7 @@ def load_elements_and_type_map(
diagrams_from_cache = {d["uuid"] for d in diagram_idx if d["success"]}
elements["Diagram"] = [
- d for d in model.diagrams if d.uuid in diagrams_from_cache
+ d for d in self.model.diagrams if d.uuid in diagrams_from_cache
]
for obj in elements["Diagram"]:
type_map[obj.uuid] = "Diagram"
@@ -199,29 +204,22 @@ def load_polarion_work_item_map(self):
{"workitems": "id,uuid_capella,checksum,status"},
)
- self.polarion_work_item_map = {
- wi.uuid_capella: wi
- for wi in work_items
- if wi.id and wi.uuid_capella
- }
- self.polarion_id_map = {
- uuid: wi.id for uuid, wi in self.polarion_work_item_map.items()
- }
+ self.polarion_data_repo.update_work_items(work_items)
def create_work_items(
self,
diagram_cache_path: pathlib.Path,
model,
descr_references: dict[str, list[str]],
- ) -> dict[str, serialize.CapellaWorkItem]:
+ ) -> dict[str, capella_work_item.CapellaWorkItem]:
"""Create a list of work items for Polarion."""
objects = chain.from_iterable(self.elements.values())
_work_items = []
- serializer = serialize.CapellaWorkItemSerializer(
+ serializer = element_converter.CapellaWorkItemSerializer(
diagram_cache_path,
self.polarion_type_map,
model,
- self.polarion_id_map,
+ self.polarion_data_repo,
descr_references,
)
for obj in objects:
@@ -229,13 +227,15 @@ def create_work_items(
_work_items = list(filter(None, _work_items))
valid_types = set(map(self.make_type_id, set(self.elements)))
- work_items: list[serialize.CapellaWorkItem] = []
+ work_items: list[capella_work_item.CapellaWorkItem] = []
missing_types: set[str] = set()
for work_item in _work_items:
assert work_item is not None
assert work_item.title is not None
assert work_item.type is not None
- if old := self.polarion_work_item_map.get(work_item.uuid_capella):
+ if old := self.polarion_data_repo.get_work_item_by_capella_uuid(
+ work_item.uuid_capella
+ ):
work_item.id = old.id
if work_item.type in valid_types:
work_items.append(work_item)
@@ -257,15 +257,13 @@ def delete_work_items(self) -> None:
"""
def serialize_for_delete(uuid: str) -> str:
- logger.info(
- "Delete work item %r...",
- workitem_id := self.polarion_id_map[uuid],
- )
- return workitem_id
+ work_item_id, _ = self.polarion_data_repo[uuid]
+ logger.info("Delete work item %r...", work_item_id)
+ return work_item_id
existing_work_items = {
uuid
- for uuid, work_item in self.polarion_work_item_map.items()
+ for uuid, _, work_item in self.polarion_data_repo.items()
if work_item.status != "deleted"
}
uuids: set[str] = existing_work_items - self.capella_uuid_s
@@ -273,19 +271,19 @@ def serialize_for_delete(uuid: str) -> str:
if work_item_ids:
try:
self.client.delete_work_items(work_item_ids)
- for uuid in uuids:
- del self.polarion_work_item_map[uuid]
- del self.polarion_id_map[uuid]
+ self.polarion_data_repo.remove_work_items_by_capella_uuid(
+ uuids
+ )
except polarion_api.PolarionApiException as error:
logger.error("Deleting work items failed. %s", error.args[0])
def post_work_items(
- self, new_work_items: dict[str, serialize.CapellaWorkItem]
+ self, new_work_items: dict[str, capella_work_item.CapellaWorkItem]
) -> None:
"""Post work items in a Polarion project."""
- missing_work_items: list[serialize.CapellaWorkItem] = []
+ missing_work_items: list[capella_work_item.CapellaWorkItem] = []
for work_item in new_work_items.values():
- if work_item.uuid_capella in self.polarion_id_map:
+ if work_item.uuid_capella in self.polarion_data_repo:
continue
assert work_item is not None
@@ -294,18 +292,14 @@ def post_work_items(
if missing_work_items:
try:
self.client.create_work_items(missing_work_items)
- for work_item in missing_work_items:
- self.polarion_id_map[work_item.uuid_capella] = work_item.id
- self.polarion_work_item_map[
- work_item.uuid_capella
- ] = work_item
+ self.polarion_data_repo.update_work_items(missing_work_items)
except polarion_api.PolarionApiException as error:
logger.error("Creating work items failed. %s", error.args[0])
def patch_work_item(
self,
- new: serialize.CapellaWorkItem,
- old: serialize.CapellaWorkItem,
+ new: capella_work_item.CapellaWorkItem,
+ old: capella_work_item.CapellaWorkItem,
):
"""Patch a given WorkItem.
@@ -395,44 +389,41 @@ def _get_link_id(link: polarion_api.WorkItemLink) -> str:
def patch_work_items(
self,
- model: capellambse.MelodyModel,
- new_work_items: dict[str, serialize.CapellaWorkItem],
+ new_work_items: dict[str, capella_work_item.CapellaWorkItem],
descr_references,
link_roles,
) -> None:
"""Update work items in a Polarion project."""
- self.polarion_id_map = {
- uuid: wi.id
- for uuid, wi in self.polarion_work_item_map.items()
- if wi.status == "open" and wi.uuid_capella and wi.id
- }
back_links: dict[str, list[polarion_api.WorkItemLink]] = {}
- for uuid in self.polarion_id_map:
- objects = model
+ link_serializer = link_converter.LinkSerializer(
+ self.polarion_data_repo,
+ descr_references,
+ self.polarion_params.project_id,
+ self.model,
+ )
+
+ for uuid in self.polarion_data_repo:
+ objects = self.model
if uuid.startswith("_"):
- objects = model.diagrams
+ objects = self.model.diagrams
obj = objects.by_uuid(uuid)
- links = element.create_links(
+ links = link_serializer.create_links_for_work_item(
obj,
- self.polarion_id_map,
- self.polarion_work_item_map,
- descr_references,
- self.polarion_params.project_id,
- model,
link_roles,
)
- work_item: serialize.CapellaWorkItem = new_work_items[uuid]
+ work_item: capella_work_item.CapellaWorkItem = new_work_items[uuid]
work_item.linked_work_items = links
- element.create_grouped_link_fields(work_item, back_links)
+ link_converter.create_grouped_link_fields(work_item, back_links)
- for uuid in self.polarion_id_map:
- new_work_item: serialize.CapellaWorkItem = new_work_items[uuid]
- old_work_item = self.polarion_work_item_map[uuid]
+ for uuid, _, old_work_item in self.polarion_data_repo.items():
+ new_work_item: capella_work_item.CapellaWorkItem = new_work_items[
+ uuid
+ ]
if old_work_item.id in back_links:
- element.create_grouped_back_link_fields(
+ link_converter.create_grouped_back_link_fields(
new_work_item, back_links[old_work_item.id]
)
diff --git a/tests/conftest.py b/tests/conftest.py
index 54580bba..a48f7d2c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -12,7 +12,7 @@
import polarion_rest_api_client as polarion_api
import pytest
-from capella2polarion.elements import serialize
+from capella2polarion import capella_work_item
TEST_DATA_ROOT = pathlib.Path(__file__).parent / "data"
TEST_DIAGRAM_CACHE = TEST_DATA_ROOT / "diagram_cache"
@@ -36,9 +36,9 @@ def model() -> capellambse.MelodyModel:
@pytest.fixture
-def dummy_work_items() -> dict[str, serialize.CapellaWorkItem]:
+def dummy_work_items() -> dict[str, capella_work_item.CapellaWorkItem]:
return {
- f"uuid{i}": serialize.CapellaWorkItem(
+ f"uuid{i}": capella_work_item.CapellaWorkItem(
id=f"Obj-{i}",
uuid_capella=f"uuid{i}",
title=f"Fake {i}",
diff --git a/tests/test_elements.py b/tests/test_elements.py
index 75795b1b..3f6e56bd 100644
--- a/tests/test_elements.py
+++ b/tests/test_elements.py
@@ -14,8 +14,13 @@
import pytest
from capellambse.model import common
+from capella2polarion import capella_work_item
from capella2polarion.capella2polarioncli import Capella2PolarionCli
-from capella2polarion.elements import element, serialize
+from capella2polarion.capella_polarion_conversion import (
+ element_converter,
+ link_converter,
+)
+from capella2polarion.polarion_connector import polarion_repo
from capella2polarion.polarion_worker import PolarionWorker
# pylint: disable-next=relative-beyond-top-level, useless-suppression
@@ -45,7 +50,6 @@
TEST_SCENARIO = "afdaa095-e2cd-4230-b5d3-6cb771a90f51"
TEST_CAP_REAL = "b80b3141-a7fc-48c7-84b2-1467dcef5fce"
TEST_CONSTRAINT = "95cbd4af-7224-43fe-98cb-f13dda540b8e"
-TEST_POL_ID_MAP = {TEST_E_UUID: "TEST"}
TEST_POL_TYPE_MAP = {
TEST_ELEMENT_UUID: "LogicalComponent",
TEST_OCAP_UUID: "OperationalCapability",
@@ -146,7 +150,9 @@ def write(self, text: str):
pass
uuid = diagram_cache_index[0]["uuid"]
- work_item = serialize.CapellaWorkItem(id="Diag-1", checksum="123")
+ work_item = capella_work_item.CapellaWorkItem(
+ id="Diag-1", checksum="123", uuid_capella=uuid
+ )
c2p_cli = Capella2PolarionCli(
debug=True,
polarion_project_id="project_id",
@@ -166,11 +172,13 @@ def write(self, text: str):
)
pw = PolarionWorker(
c2p_cli.polarion_params,
- serialize.resolve_element_type,
+ model,
+ element_converter.resolve_element_type,
)
pw.capella_uuid_s = {d["uuid"] for d in diagram_cache_index}
- pw.polarion_work_item_map = {uuid: work_item}
- pw.polarion_id_map = {uuid: "Diag-1"}
+ pw.polarion_data_repo = polarion_repo.PolarionDataRepository(
+ [work_item]
+ )
pw.elements = {"Diagram": c2p_cli.capella_model.diagrams}
return BaseObjectContainer(c2p_cli, pw)
@@ -179,7 +187,7 @@ def test_create_diagrams(base_object: BaseObjectContainer):
c2p_cli = base_object.c2pcli
pw = base_object.pw
description_reference: dict[str, list[str]] = {}
- new_work_items: dict[str, serialize.CapellaWorkItem]
+ new_work_items: dict[str, capella_work_item.CapellaWorkItem]
new_work_items = pw.create_work_items(
c2p_cli.capella_diagram_cache_folder_path,
c2p_cli.capella_model,
@@ -187,10 +195,12 @@ def test_create_diagrams(base_object: BaseObjectContainer):
)
assert len(new_work_items) == 1
work_item = new_work_items[TEST_DIAG_UUID]
- assert isinstance(work_item, serialize.CapellaWorkItem)
+ assert isinstance(work_item, capella_work_item.CapellaWorkItem)
description = work_item.description
work_item.description = None
- assert work_item == serialize.CapellaWorkItem(**TEST_SER_DIAGRAM)
+ assert work_item == capella_work_item.CapellaWorkItem(
+ **TEST_SER_DIAGRAM
+ )
assert isinstance(description, str)
assert description.startswith(TEST_DIAG_DESCR)
@@ -258,7 +268,7 @@ class MyIO(io.StringIO):
def write(self, text: str):
pass
- work_item = serialize.CapellaWorkItem(
+ work_item = capella_work_item.CapellaWorkItem(
id="Obj-1",
uuid_capella="uuid1",
status="open",
@@ -284,10 +294,12 @@ def write(self, text: str):
)
pw = PolarionWorker(
c2p_cli.polarion_params,
- serialize.resolve_element_type,
+ model,
+ element_converter.resolve_element_type,
+ )
+ pw.polarion_data_repo = polarion_repo.PolarionDataRepository(
+ [work_item]
)
- pw.polarion_work_item_map = {"uuid1": work_item}
- pw.polarion_id_map = {"uuid1": "Obj-1"}
pw.polarion_type_map = {"uuid1": "FakeModelObject"}
fake = FakeModelObject("uuid1", name="Fake 1")
pw.elements = {
@@ -311,19 +323,19 @@ def test_create_work_items(
base_object.pw.elements["FakeModelObject"]
)
monkeypatch.setattr(
- serialize.CapellaWorkItemSerializer,
+ element_converter.CapellaWorkItemSerializer,
"serialize",
mock_generic_work_item := mock.MagicMock(),
)
mock_generic_work_item.side_effect = [
- expected := serialize.CapellaWorkItem(
+ expected := capella_work_item.CapellaWorkItem(
uuid_capella="uuid1",
title="Fake 1",
type="fakeModelObject",
description_type="text/html",
description=markupsafe.Markup(""),
),
- expected1 := serialize.CapellaWorkItem(
+ expected1 := capella_work_item.CapellaWorkItem(
uuid_capella="uuid2",
title="Fake 2",
type="fakeModelObject",
@@ -361,7 +373,7 @@ def test_create_work_items_with_special_polarion_type(
base_object.pw.polarion_type_map[uuid] = _type
base_object.c2pcli.capella_model = model
- expected = serialize.CapellaWorkItem(
+ expected = capella_work_item.CapellaWorkItem(
uuid_capella=uuid,
type=_type[0].lower() + _type[1:],
description_type="text/html",
@@ -380,16 +392,17 @@ def test_create_work_items_with_special_polarion_type(
@staticmethod
def test_create_links_custom_resolver(base_object: BaseObjectContainer):
obj = base_object.pw.elements["FakeModelObject"][1]
- base_object.pw.polarion_id_map["uuid2"] = "Obj-2"
- base_object.pw.polarion_work_item_map[
- "uuid2"
- ] = serialize.CapellaWorkItem(
- id="Obj-2",
- uuid_capella="uuid2",
- type="fakeModelObject",
- description_type="text/html",
- description=markupsafe.Markup(""),
- status="open",
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-2",
+ uuid_capella="uuid2",
+ type="fakeModelObject",
+ description_type="text/html",
+ description=markupsafe.Markup(""),
+ status="open",
+ )
+ ]
)
base_object.c2pcli.synchronize_config_roles = {
"FakeModelObject": ["description_reference"]
@@ -401,13 +414,14 @@ def test_create_links_custom_resolver(base_object: BaseObjectContainer):
"description_reference",
secondary_work_item_project="project_id",
)
- links = element.create_links(
- obj,
- base_object.pw.polarion_id_map,
- base_object.pw.polarion_work_item_map,
+ link_serializer = link_converter.LinkSerializer(
+ base_object.pw.polarion_data_repo,
description_reference,
base_object.pw.polarion_params.project_id,
base_object.c2pcli.capella_model,
+ )
+ links = link_serializer.create_links_for_work_item(
+ obj,
base_object.c2pcli.synchronize_config_roles,
)
assert links == [expected]
@@ -421,27 +435,29 @@ def test_create_links_custom_exchanges_resolver(
obj = base_object.c2pcli.capella_model.by_uuid(function_uuid)
- base_object.pw.polarion_id_map[function_uuid] = "Obj-1"
- base_object.pw.polarion_work_item_map[
- function_uuid
- ] = serialize.CapellaWorkItem(
- id="Obj-1",
- uuid_capella=function_uuid,
- type=type(obj).__name__,
- description_type="text/html",
- description=markupsafe.Markup(""),
- status="open",
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-1",
+ uuid_capella=function_uuid,
+ type=type(obj).__name__,
+ description_type="text/html",
+ description=markupsafe.Markup(""),
+ status="open",
+ )
+ ]
)
- base_object.pw.polarion_id_map[uuid] = "Obj-2"
- base_object.pw.polarion_work_item_map[
- uuid
- ] = serialize.CapellaWorkItem(
- id="Obj-2",
- uuid_capella=uuid,
- type="functionalExchange",
- description_type="text/html",
- description=markupsafe.Markup(""),
- status="open",
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-2",
+ uuid_capella=uuid,
+ type="functionalExchange",
+ description_type="text/html",
+ description=markupsafe.Markup(""),
+ status="open",
+ )
+ ]
)
base_object.c2pcli.synchronize_config_roles = {
@@ -453,13 +469,14 @@ def test_create_links_custom_exchanges_resolver(
"input_exchanges",
secondary_work_item_project="project_id",
)
- links = element.create_links(
- obj,
- base_object.pw.polarion_id_map,
- base_object.pw.polarion_work_item_map,
+ link_serializer = link_converter.LinkSerializer(
+ base_object.pw.polarion_data_repo,
{},
base_object.pw.polarion_params.project_id,
base_object.c2pcli.capella_model,
+ )
+ links = link_serializer.create_links_for_work_item(
+ obj,
base_object.c2pcli.synchronize_config_roles,
)
assert links == [expected]
@@ -475,13 +492,14 @@ def test_create_links_missing_attribute(
""
)
with caplog.at_level(logging.DEBUG):
- links = element.create_links(
- obj,
- base_object.pw.polarion_id_map,
- base_object.pw.polarion_work_item_map,
+ link_serializer = link_converter.LinkSerializer(
+ base_object.pw.polarion_data_repo,
{},
base_object.pw.polarion_params.project_id,
base_object.c2pcli.capella_model,
+ )
+ links = link_serializer.create_links_for_work_item(
+ obj,
base_object.c2pcli.synchronize_config_roles,
)
assert not links
@@ -501,20 +519,19 @@ def test_create_links_from_ElementList(base_object: BaseObjectContainer):
),
)
base_object.pw.elements["FakeModelObject"].append(obj)
- base_object.pw.polarion_id_map |= {
- f"uuid{i}": f"Obj-{i}" for i in range(4, 7)
- }
- base_object.pw.polarion_work_item_map |= {
- f"uuid{i}": serialize.CapellaWorkItem(
- id=f"Obj-{i}",
- uuid_capella=f"uuid{i}",
- type="fakeModelObject",
- description_type="text/html",
- description=markupsafe.Markup(""),
- status="open",
- )
- for i in range(4, 7)
- }
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id=f"Obj-{i}",
+ uuid_capella=f"uuid{i}",
+ type="fakeModelObject",
+ description_type="text/html",
+ description=markupsafe.Markup(""),
+ status="open",
+ )
+ for i in range(4, 7)
+ ]
+ )
expected_link = polarion_api.WorkItemLink(
"Obj-6",
@@ -528,13 +545,14 @@ def test_create_links_from_ElementList(base_object: BaseObjectContainer):
"attribute",
secondary_work_item_project="project_id",
)
- links = element.create_links(
- obj,
- base_object.pw.polarion_id_map,
- base_object.pw.polarion_work_item_map,
+ link_serializer = link_converter.LinkSerializer(
+ base_object.pw.polarion_data_repo,
{},
base_object.pw.polarion_params.project_id,
base_object.c2pcli.capella_model,
+ )
+ links = link_serializer.create_links_for_work_item(
+ obj,
base_object.c2pcli.synchronize_config_roles,
)
# type: ignore[arg-type]
@@ -546,16 +564,17 @@ def test_create_link_from_single_attribute(
base_object: BaseObjectContainer,
):
obj = base_object.pw.elements["FakeModelObject"][1]
- base_object.pw.polarion_id_map["uuid2"] = "Obj-2"
- base_object.pw.polarion_work_item_map[
- "uuid2"
- ] = serialize.CapellaWorkItem(
- id="Obj-2",
- uuid_capella="uuid2",
- type="fakeModelObject",
- description_type="text/html",
- description=markupsafe.Markup(""),
- status="open",
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-2",
+ uuid_capella="uuid2",
+ type="fakeModelObject",
+ description_type="text/html",
+ description=markupsafe.Markup(""),
+ status="open",
+ )
+ ]
)
expected = polarion_api.WorkItemLink(
@@ -564,13 +583,14 @@ def test_create_link_from_single_attribute(
"attribute",
secondary_work_item_project="project_id",
)
- links = element.create_links(
- obj,
- base_object.pw.polarion_id_map,
- base_object.pw.polarion_work_item_map,
+ link_serializer = link_converter.LinkSerializer(
+ base_object.pw.polarion_data_repo,
{},
base_object.pw.polarion_params.project_id,
base_object.c2pcli.capella_model,
+ )
+ links = link_serializer.create_links_for_work_item(
+ obj,
base_object.c2pcli.synchronize_config_roles,
)
assert links == [expected]
@@ -579,8 +599,8 @@ def test_create_link_from_single_attribute(
def test_update_work_items(
monkeypatch: pytest.MonkeyPatch, base_object: BaseObjectContainer
):
- polarion_work_item_list: list[serialize.CapellaWorkItem] = [
- serialize.CapellaWorkItem(
+ polarion_work_item_list: list[capella_work_item.CapellaWorkItem] = [
+ capella_work_item.CapellaWorkItem(
id="Obj-1",
type="type",
uuid_capella="uuid1",
@@ -601,7 +621,7 @@ def test_update_work_items(
base_object.pw.load_polarion_work_item_map()
work_items = {
- "uuid1": serialize.CapellaWorkItem(
+ "uuid1": capella_work_item.CapellaWorkItem(
id="Obj-1",
uuid_capella="uuid1",
title="Fake 1",
@@ -610,14 +630,12 @@ def test_update_work_items(
)
}
base_object.c2pcli.capella_model = mock_model = mock.MagicMock()
+ base_object.pw.model = mock_model
mock_model.by_uuid.return_value = base_object.pw.elements[
"FakeModelObject"
][0]
base_object.pw.patch_work_items(
- base_object.c2pcli.capella_model,
- work_items,
- {},
- base_object.c2pcli.synchronize_config_roles,
+ work_items, {}, base_object.c2pcli.synchronize_config_roles
)
assert base_object.pw.client is not None
assert base_object.pw.client.get_all_work_item_links.call_count == 1
@@ -625,7 +643,7 @@ def test_update_work_items(
assert base_object.pw.client.create_work_item_links.call_count == 0
assert base_object.pw.client.update_work_item.call_count == 1
work_item = base_object.pw.client.update_work_item.call_args[0][0]
- assert isinstance(work_item, serialize.CapellaWorkItem)
+ assert isinstance(work_item, capella_work_item.CapellaWorkItem)
assert work_item.id == "Obj-1"
assert work_item.title == "Fake 1"
assert work_item.description_type == "text/html"
@@ -638,17 +656,19 @@ def test_update_work_items(
def test_update_work_items_filters_work_items_with_same_checksum(
base_object: BaseObjectContainer,
):
- base_object.pw.polarion_work_item_map[
- "uuid1"
- ] = serialize.CapellaWorkItem(
- id="Obj-1",
- uuid_capella="uuid1",
- status="open",
- checksum=TEST_WI_CHECKSUM,
- type="fakeModelObject",
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-1",
+ uuid_capella="uuid1",
+ status="open",
+ checksum=TEST_WI_CHECKSUM,
+ type="fakeModelObject",
+ )
+ ]
)
work_items = {
- "uuid1": serialize.CapellaWorkItem(
+ "uuid1": capella_work_item.CapellaWorkItem(
id="Obj-1",
uuid_capella="uuid1",
status="open",
@@ -660,11 +680,10 @@ def test_update_work_items_filters_work_items_with_same_checksum(
"uuid1", name="Fake 1"
)
+ base_object.pw.model = mock_model
+
base_object.pw.patch_work_items(
- mock_model,
- work_items,
- {},
- base_object.c2pcli.synchronize_config_roles,
+ work_items, {}, base_object.c2pcli.synchronize_config_roles
)
assert base_object.pw.client is not None
@@ -672,14 +691,12 @@ def test_update_work_items_filters_work_items_with_same_checksum(
@staticmethod
def test_update_links_with_no_elements(base_object: BaseObjectContainer):
- base_object.pw.polarion_work_item_map = {}
- base_object.pw.polarion_id_map = {}
- work_items: dict[str, serialize.CapellaWorkItem] = {}
+ base_object.pw.polarion_data_repo = (
+ polarion_repo.PolarionDataRepository()
+ )
+ work_items: dict[str, capella_work_item.CapellaWorkItem] = {}
base_object.pw.patch_work_items(
- base_object.c2pcli.capella_model,
- work_items,
- {},
- base_object.c2pcli.synchronize_config_roles,
+ work_items, {}, base_object.c2pcli.synchronize_config_roles
)
assert base_object.pw.client.get_all_work_item_links.call_count == 0
@@ -689,25 +706,26 @@ def test_update_links(base_object: BaseObjectContainer):
link = polarion_api.WorkItemLink(
"Obj-1", "Obj-2", "attribute", True, "project_id"
)
- base_object.pw.polarion_work_item_map["uuid1"].linked_work_items = [
- link
- ]
- base_object.pw.polarion_work_item_map[
- "uuid2"
- ] = serialize.CapellaWorkItem(
- id="Obj-2",
- uuid_capella="uuid2",
- status="open",
- type="fakeModelObject",
+ _, work_item = base_object.pw.polarion_data_repo["uuid1"]
+ work_item.linked_work_items = [link]
+ base_object.pw.polarion_data_repo.update_work_items(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-2",
+ uuid_capella="uuid2",
+ status="open",
+ type="fakeModelObject",
+ )
+ ]
)
work_items = {
- "uuid1": serialize.CapellaWorkItem(
+ "uuid1": capella_work_item.CapellaWorkItem(
id="Obj-1",
uuid_capella="uuid1",
status="open",
type="fakeModelObject",
),
- "uuid2": serialize.CapellaWorkItem(
+ "uuid2": capella_work_item.CapellaWorkItem(
id="Obj-2",
uuid_capella="uuid2",
status="open",
@@ -726,11 +744,9 @@ def test_update_links(base_object: BaseObjectContainer):
expected_new_link = polarion_api.WorkItemLink(
"Obj-2", "Obj-1", "attribute", None, "project_id"
)
+ base_object.pw.model = mock_model
base_object.pw.patch_work_items(
- base_object.c2pcli.capella_model,
- work_items,
- {},
- base_object.c2pcli.synchronize_config_roles,
+ work_items, {}, base_object.c2pcli.synchronize_config_roles
)
assert base_object.pw.client is not None
links = base_object.pw.client.get_all_work_item_links.call_args_list
@@ -750,22 +766,30 @@ def test_update_links(base_object: BaseObjectContainer):
def test_patch_work_item_grouped_links(
monkeypatch: pytest.MonkeyPatch,
base_object: BaseObjectContainer,
- dummy_work_items: dict[str, serialize.CapellaWorkItem],
+ dummy_work_items: dict[str, capella_work_item.CapellaWorkItem],
):
work_items = dummy_work_items
- base_object.pw.polarion_work_item_map = {
- "uuid0": serialize.CapellaWorkItem(
- id="Obj-0", uuid_capella="uuid0", status="open"
- ),
- "uuid1": serialize.CapellaWorkItem(
- id="Obj-1", uuid_capella="uuid1", status="open"
- ),
- "uuid2": serialize.CapellaWorkItem(
- id="Obj-2", uuid_capella="uuid2", status="open"
- ),
- }
+ base_object.pw.polarion_data_repo = (
+ polarion_repo.PolarionDataRepository(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="Obj-0", uuid_capella="uuid0", status="open"
+ ),
+ capella_work_item.CapellaWorkItem(
+ id="Obj-1", uuid_capella="uuid1", status="open"
+ ),
+ capella_work_item.CapellaWorkItem(
+ id="Obj-2", uuid_capella="uuid2", status="open"
+ ),
+ ]
+ )
+ )
mock_create_links = mock.MagicMock()
- monkeypatch.setattr(element, "create_links", mock_create_links)
+ monkeypatch.setattr(
+ link_converter.LinkSerializer,
+ "create_links_for_work_item",
+ mock_create_links,
+ )
mock_create_links.side_effect = lambda obj, *args: dummy_work_items[
obj.uuid
].linked_work_items
@@ -775,12 +799,12 @@ def mock_back_link(work_item, back_links):
mock_grouped_links = mock.MagicMock()
monkeypatch.setattr(
- element, "create_grouped_link_fields", mock_grouped_links
+ link_converter, "create_grouped_link_fields", mock_grouped_links
)
mock_grouped_links.side_effect = mock_back_link
mock_grouped_links_reverse = mock.MagicMock()
monkeypatch.setattr(
- element,
+ link_converter,
"create_grouped_back_link_fields",
mock_grouped_links_reverse,
)
@@ -788,11 +812,9 @@ def mock_back_link(work_item, back_links):
mock_model.by_uuid.side_effect = [
FakeModelObject(f"uuid{i}", name=f"Fake {i}") for i in range(3)
]
+ base_object.pw.model = mock_model
base_object.pw.patch_work_items(
- base_object.c2pcli.capella_model,
- work_items,
- {},
- base_object.c2pcli.synchronize_config_roles,
+ work_items, {}, base_object.c2pcli.synchronize_config_roles
)
assert base_object.pw.client is not None
update_work_item_calls = (
@@ -814,10 +836,10 @@ def mock_back_link(work_item, back_links):
@staticmethod
def test_maintain_grouped_links_attributes(
- dummy_work_items: dict[str, serialize.CapellaWorkItem]
+ dummy_work_items: dict[str, capella_work_item.CapellaWorkItem]
):
for work_item in dummy_work_items.values():
- element.create_grouped_link_fields(work_item)
+ link_converter.create_grouped_link_fields(work_item)
del dummy_work_items["uuid0"].additional_attributes["uuid_capella"]
del dummy_work_items["uuid1"].additional_attributes["uuid_capella"]
del dummy_work_items["uuid2"].additional_attributes["uuid_capella"]
@@ -839,15 +861,15 @@ def test_maintain_grouped_links_attributes(
@staticmethod
def test_maintain_reverse_grouped_links_attributes(
- dummy_work_items: dict[str, serialize.CapellaWorkItem]
+ dummy_work_items: dict[str, capella_work_item.CapellaWorkItem]
):
reverse_polarion_id_map = {v: k for k, v in POLARION_ID_MAP.items()}
back_links: dict[str, list[polarion_api.WorkItemLink]] = {}
for work_item in dummy_work_items.values():
- element.create_grouped_link_fields(work_item, back_links)
+ link_converter.create_grouped_link_fields(work_item, back_links)
for work_item_id, links in back_links.items():
work_item = dummy_work_items[reverse_polarion_id_map[work_item_id]]
- element.create_grouped_back_link_fields(work_item, links)
+ link_converter.create_grouped_back_link_fields(work_item, links)
del dummy_work_items["uuid0"].additional_attributes["uuid_capella"]
del dummy_work_items["uuid1"].additional_attributes["uuid_capella"]
del dummy_work_items["uuid2"].additional_attributes["uuid_capella"]
@@ -877,12 +899,12 @@ def test_maintain_reverse_grouped_links_attributes(
def test_grouped_linked_work_items_order_consistency():
- work_item = serialize.CapellaWorkItem("id", "Dummy")
+ work_item = capella_work_item.CapellaWorkItem("id", "Dummy")
links = [
polarion_api.WorkItemLink("prim1", "id", "role1"),
polarion_api.WorkItemLink("prim2", "id", "role1"),
]
- element.create_grouped_back_link_fields(work_item, links)
+ link_converter.create_grouped_back_link_fields(work_item, links)
check_sum = work_item.calculate_checksum()
@@ -890,7 +912,7 @@ def test_grouped_linked_work_items_order_consistency():
polarion_api.WorkItemLink("prim2", "id", "role1"),
polarion_api.WorkItemLink("prim1", "id", "role1"),
]
- element.create_grouped_back_link_fields(work_item, links)
+ link_converter.create_grouped_back_link_fields(work_item, links)
assert check_sum == work_item.calculate_checksum()
@@ -900,7 +922,7 @@ class TestHelpers:
def test_resolve_element_type():
xtype = "LogicalComponent"
- type = serialize.resolve_element_type(xtype)
+ type = element_converter.resolve_element_type(xtype)
assert type == "logicalComponent"
@@ -910,15 +932,19 @@ class TestSerializers:
def test_diagram(model: capellambse.MelodyModel):
diag = model.diagrams.by_uuid(TEST_DIAG_UUID)
- serializer = serialize.CapellaWorkItemSerializer(
- TEST_DIAGRAM_CACHE, {}, model, {}, {}
+ serializer = element_converter.CapellaWorkItemSerializer(
+ TEST_DIAGRAM_CACHE,
+ {},
+ model,
+ polarion_repo.PolarionDataRepository(),
+ {},
)
serialized_diagram = serializer.serialize(diag)
if serialized_diagram is not None:
serialized_diagram.description = None
- assert serialized_diagram == serialize.CapellaWorkItem(
+ assert serialized_diagram == capella_work_item.CapellaWorkItem(
type="diagram",
uuid_capella=TEST_DIAG_UUID,
title="[CC] Capability",
@@ -931,7 +957,7 @@ def test_diagram(model: capellambse.MelodyModel):
def test__decode_diagram():
diagram_path = TEST_DIAGRAM_CACHE / "_APMboAPhEeynfbzU12yy7w.svg"
- diagram = serialize._decode_diagram(diagram_path)
+ diagram = element_converter._decode_diagram(diagram_path)
assert diagram.startswith("data:image/svg+xml;base64,")
@@ -1068,11 +1094,17 @@ def test_generic_work_item(
):
obj = model.by_uuid(uuid)
- serializer = serialize.CapellaWorkItemSerializer(
+ serializer = element_converter.CapellaWorkItemSerializer(
pathlib.Path(""),
TEST_POL_TYPE_MAP,
model,
- TEST_POL_ID_MAP,
+ polarion_repo.PolarionDataRepository(
+ [
+ capella_work_item.CapellaWorkItem(
+ id="TEST", uuid_capella=TEST_E_UUID
+ )
+ ]
+ ),
{},
)
@@ -1081,5 +1113,5 @@ def test_generic_work_item(
status = work_item.status
work_item.status = None
- assert work_item == serialize.CapellaWorkItem(**expected)
+ assert work_item == capella_work_item.CapellaWorkItem(**expected)
assert status == "open"