diff --git a/capella2polarion/converters/converter_config.py b/capella2polarion/converters/converter_config.py index 5d5aff1..af08fd6 100644 --- a/capella2polarion/converters/converter_config.py +++ b/capella2polarion/converters/converter_config.py @@ -52,7 +52,50 @@ class CapellaTypeConfig: def __post_init__(self): """Post processing for the initialization.""" - self.converters = _force_dict(self.converters) + self.converters = self._force_dict() + + def _force_dict(self) -> dict[str, dict[str, t.Any]]: + match self.converters: + case None: + return {} + case str(): + return {self.converters: {}} + case list(): + return {c: {} for c in self.converters} + case dict(): + return self._filter_converter_config() + case _: + raise TypeError("Unsupported Type") + + def _filter_converter_config(self) -> dict[str, dict[str, t.Any]]: + custom_converters = ( + "include_pre_and_post_condition", + "linked_text_as_description", + "add_attributes", + "add_context_diagram", + "add_tree_diagram", + "add_jinja_fields", + "jinja_as_description", + ) + filtered_config = {} + assert isinstance(self.converters, dict) + for name, params in self.converters.items(): + params = params or {} + if name not in custom_converters: + logger.error("Unknown converter in config %r", name) + continue + + if name in ("add_context_diagram", "add_tree_diagram"): + assert isinstance(params, dict) + params = _filter_context_diagram_config(params) + + if name in ("add_attributes"): + assert isinstance(params, list) # type: ignore[unreachable] + params = {"attributes": params} # type: ignore[unreachable] + + filtered_config[name] = params + + return filtered_config def _default_type_conversion(c_type: str) -> str: @@ -283,7 +326,7 @@ def config_matches(config: CapellaTypeConfig | None, **kwargs: t.Any) -> bool: def _read_capella_type_configs( - conf: dict[str, t.Any] | list[dict[str, t.Any]] | None + conf: dict[str, t.Any] | list[dict[str, t.Any]] | None, ) -> list[dict]: if conf is None: return [{}] @@ -299,22 +342,6 @@ def _read_capella_type_configs( ) -def _force_dict( - config: str | list[str] | dict[str, dict[str, t.Any]] | None -) -> dict[str, dict[str, t.Any]]: - match config: - case None: - return {} - case str(): - return {config: {}} - case list(): - return {c: {} for c in config} - case dict(): - return _filter_converter_config(config) - case _: - raise TypeError("Unsupported Type") - - def add_prefix(polarion_type: str, prefix: str) -> str: """Add a prefix to the given ``polarion_type``.""" if prefix: @@ -322,32 +349,6 @@ def add_prefix(polarion_type: str, prefix: str) -> str: return polarion_type -def _filter_converter_config( - config: dict[str, dict[str, t.Any]] -) -> dict[str, dict[str, t.Any]]: - custom_converters = ( - "include_pre_and_post_condition", - "linked_text_as_description", - "add_context_diagram", - "add_tree_diagram", - "add_jinja_fields", - "jinja_as_description", - ) - filtered_config = {} - for name, params in config.items(): - params = params or {} - if name not in custom_converters: - logger.error("Unknown converter in config %r", name) - continue - - if name in ("add_context_diagram", "add_tree_diagram"): - params = _filter_context_diagram_config(params) - - filtered_config[name] = params - - return filtered_config - - def _filter_context_diagram_config( config: dict[str, t.Any] ) -> dict[str, t.Any]: diff --git a/capella2polarion/converters/element_converter.py b/capella2polarion/converters/element_converter.py index 476d691..90eeb92 100644 --- a/capella2polarion/converters/element_converter.py +++ b/capella2polarion/converters/element_converter.py @@ -5,6 +5,7 @@ from __future__ import annotations import collections +import enum import hashlib import logging import mimetypes @@ -66,6 +67,16 @@ def _format(texts: list[str]) -> dict[str, str]: return requirement_types +def _resolve_capella_attribute( + element: m.ModelElement | m.Diagram, attribute: str +) -> polarion_api.TextContent: + value = getattr(element, attribute) + if isinstance(value, enum.Enum): + return polarion_api.TextContent(type="string", value=value.name) + + raise ValueError("Unsupported attribute type: %r", value) + + class CapellaWorkItemSerializer(polarion_html_helper.JinjaRendererMixin): """The general serializer class for CapellaWorkItems.""" @@ -455,6 +466,32 @@ def __generic_work_item( return converter_data.work_item + def _add_attributes( + self, + converter_data: data_session.ConverterData, + attributes: list[dict[str, t.Any]], + ): + assert converter_data.work_item is not None + for attribute in attributes: + try: + value = _resolve_capella_attribute( + converter_data.capella_element, attribute["capella_attr"] + ) + setattr( + converter_data.work_item, attribute["polarion_id"], value + ) + except AttributeError: + logger.error( + "Attribute %r not found on %r", + attribute["capella_attr"], + converter_data.type_config.p_type, + ) + continue + except ValueError as error: + logger.error(error.args[0]) + + return converter_data.work_item + def _diagram( self, converter_data: data_session.ConverterData, diff --git a/docs/source/features/sync.rst b/docs/source/features/sync.rst index 1c2c247..24ce3a9 100644 --- a/docs/source/features/sync.rst +++ b/docs/source/features/sync.rst @@ -59,6 +59,10 @@ specific serializer alone: | linked_text_as_description | A serializer resolving ``Constraint`` s and their | | | linked text. | +--------------------------------------+------------------------------------------------------+ +| add_attributes | A serializer adding arbitrary attributes as custom | +| | fields to the work item. For now only supports enum | +| | attributes! | ++--------------------------------------+------------------------------------------------------+ | add_context_diagram | A serializer adding a context diagram to the work | | | item. This requires node.js to be installed. | | | The Capella objects where ``context_diagram`` is | diff --git a/tests/test_elements.py b/tests/test_elements.py index 08672d6..74c5203 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -1860,6 +1860,35 @@ def test_generic_work_item( assert work_item == data_model.CapellaWorkItem(**expected) assert status == "open" + def test_add_attributes(self, model: capellambse.MelodyModel): + converters = { + "add_attributes": [ + {"capella_attr": "nature", "polarion_id": "nature"}, + {"capella_attr": "kind", "polarion_id": "kind"}, + ] + } + type_config = converter_config.CapellaTypeConfig( + "PhysicalComponent", converters, [] + ) + serializer = element_converter.CapellaWorkItemSerializer( + model, + polarion_repo.PolarionDataRepository(), + { + TEST_PHYS_COMP: data_session.ConverterData( + "pa", + type_config, + model.by_uuid(TEST_PHYS_COMP), + ) + }, + True, + ) + + work_item = serializer.serialize(TEST_PHYS_COMP) + + assert work_item is not None + assert work_item.nature == {"type": "string", "value": "UNSET"} + assert work_item.kind == {"type": "string", "value": "UNSET"} + @staticmethod def test_add_context_diagram(model: capellambse.MelodyModel): type_config = converter_config.CapellaTypeConfig(