Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix-dependency-handling #89

Merged
merged 19 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ default_install_hook_types: [commit-msg, pre-commit]
default_stages: [commit, merge-commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-added-large-files
- id: check-ast
Expand All @@ -26,7 +26,7 @@ repos:
- id: fix-byte-order-marker
- id: trailing-whitespace
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.2.0
rev: 24.8.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
Expand All @@ -47,12 +47,20 @@ repos:
additional_dependencies:
- pydocstyle[toml]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies:
- bidict
- cairosvg
- capellambse==0.5.69
- click
- jinja2
- polarion-rest-api-client==1.1.2
- pydantic
- types-requests
- types-PyYAML
exclude: tests
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
hooks:
Expand Down Expand Up @@ -97,7 +105,7 @@ repos:
- --comment-style
- "..| |"
- repo: https://github.com/fsfe/reuse-tool
rev: v3.0.1
rev: v4.0.3
hooks:
- id: reuse
- repo: https://github.com/qoomon/git-conventional-commits
Expand Down
2 changes: 2 additions & 0 deletions capella2polarion/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ def setup_logger(self) -> None:
)
logging.getLogger("httpx").setLevel("WARNING")
logging.getLogger("httpcore").setLevel("WARNING")
logging.getLogger("capellambse").setLevel("WARNING")
logging.getLogger("capellambse_context_diagrams").setLevel("WARNING")

def load_synchronize_config(
self,
Expand Down
30 changes: 17 additions & 13 deletions capella2polarion/connectors/polarion_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ def __init__(
):
if polarion_work_items is None:
polarion_work_items = []

check_work_items(polarion_work_items)
self._id_mapping = bidict.bidict(
{
work_item.uuid_capella: work_item.id
for work_item in polarion_work_items
},
}, # type: ignore[arg-type]
)
self._id_mapping.on_dup = bidict.OnDup(
key=bidict.DROP_OLD, val=bidict.DROP_OLD
Expand All @@ -50,12 +52,6 @@ def __sizeof__(self) -> int:
"""Return the amount of registered Capella UUIDs."""
return len(self._id_mapping)

def __getitem__(
self, item: str
) -> tuple[str, data_models.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) -> cabc.Iterator[str]:
"""Iterate all Capella UUIDs."""
return self._id_mapping.__iter__()
Expand Down Expand Up @@ -89,22 +85,20 @@ def get_work_item_by_polarion_id(
self.get_capella_uuid(work_item_id) # type: ignore
)

def update_work_items(
self,
work_items: list[data_models.CapellaWorkItem],
):
def update_work_items(self, work_items: list[data_models.CapellaWorkItem]):
"""Update all mappings for the given Work Items."""
for work_item in work_items:
assert work_item.id is not None
if uuid_capella := self._id_mapping.inverse.get(work_item.id):
del self._id_mapping[uuid_capella]
del self._work_items[uuid_capella]

check_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
}
} # type: ignore[arg-type]
)
self._work_items.update(
{work_item.uuid_capella: work_item for work_item in work_items}
Expand All @@ -127,3 +121,13 @@ def remove_work_items_by_capella_uuid(self, uuids: cabc.Iterable[str]):
workitems) as value. The project can be None and the None value means
that the document is in the same project as the model sync work items.
"""


def check_work_items(work_items: cabc.Iterable[data_models.CapellaWorkItem]):
"""Raise a ``ValueError`` if any work item has no ID."""
if work_item_without_id := next(
(wi for wi in work_items if wi.id is None), None
):
raise ValueError(
f"Found Work Item without ID: {work_item_without_id.title!r}"
)
72 changes: 41 additions & 31 deletions capella2polarion/connectors/polarion_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ def delete_orphaned_work_items(
If the delete flag is set to ``False`` in the context work items
are marked as ``to be deleted`` via the status attribute.
"""

existing_work_items = {
uuid
for uuid, _, work_item in self.polarion_data_repo.items()
Expand Down Expand Up @@ -192,8 +191,9 @@ def compare_and_update_work_item(
new = converter_data.work_item
assert new is not None
uuid = new.uuid_capella
_, old = self.polarion_data_repo[uuid]
old = self.polarion_data_repo.get_work_item_by_capella_uuid(uuid)
assert old is not None
assert old.id is not None

new.calculate_checksum()
if not self.force_update and new == old:
Expand All @@ -204,12 +204,14 @@ def compare_and_update_work_item(
"Update work item %r for model element %s %r...", *log_args
)

if old.get_current_checksum()[0] != "{": # XXX: Remove in next release
old_checksums = {"__C2P__WORK_ITEM": old.get_current_checksum()}
else:
old_checksums = json.loads(old.get_current_checksum())
try:
old_checksums = json.loads(old.get_current_checksum() or "")
except json.JSONDecodeError:
old_checksums = {"__C2P__WORK_ITEM": ""}

new_checksums = json.loads(new.get_current_checksum())
new_checksum = new.get_current_checksum()
assert new_checksum is not None
new_checksums = json.loads(new_checksum)

new_work_item_check_sum = new_checksums.pop("__C2P__WORK_ITEM")
old_work_item_check_sum = old_checksums.pop("__C2P__WORK_ITEM")
Expand All @@ -220,6 +222,8 @@ def compare_and_update_work_item(
old = self.project_client.work_items.get(
old.id, work_item_cls=data_models.CapellaWorkItem
)
assert old is not None
assert old.id is not None
if old.attachments:
old_attachments = (
self.project_client.work_items.attachments.get_all(
Expand All @@ -238,7 +242,7 @@ def compare_and_update_work_item(
work_item_changed |= self.update_attachments(
new, old_checksums, new_checksums, old_attachments
)
except polarion_api.PolarionApiException as error:
except (polarion_api.PolarionApiException, ValueError) as error:
logger.error(
"Updating attachments for WorkItem %r (%s %s) failed. %s",
*log_args,
Expand Down Expand Up @@ -369,15 +373,25 @@ def update_attachments(
Returns True if new attachments were created. After execution
all attachments of the new work item should have IDs.
"""
new_attachment_dict = {
attachment.file_name: attachment for attachment in new.attachments
}
old_attachment_dict = {
attachment.file_name: attachment for attachment in old_attachments
}
new_attachment_dict: dict[str, polarion_api.WorkItemAttachment] = {}
for attachment in new.attachments:
if attachment.file_name is None:
raise ValueError(
f"Found new attachment without filename: {new.id!r} with "
+ attachment.id
)
new_attachment_dict[attachment.file_name] = attachment

created = False
old_attachment_dict: dict[str, polarion_api.WorkItemAttachment] = {}
for attachment in old_attachments:
if attachment.file_name is None:
raise ValueError(
f"Found old attachment without filename: {new!r} with"
+ attachment.id
)
old_attachment_dict[attachment.file_name] = attachment

created = False
for attachment in old_attachments:
if attachment not in old_attachment_dict.values():
logger.error(
Expand All @@ -396,22 +410,21 @@ def update_attachments(
old_attachment_dict[file_name]
)

if new_attachments := list(
map(
new_attachment_dict.get,
new_attachment_file_names - old_attachment_file_names,
)
):
new_attachments: cabc.Iterable[polarion_api.WorkItemAttachment] = map(
new_attachment_dict.get, # type:ignore[arg-type]
new_attachment_file_names - old_attachment_file_names,
)
if new_attachments := list(filter(None, new_attachments)):
self.project_client.work_items.attachments.create(new_attachments)
created = True

attachments_for_update = {}
attachments_for_update: dict[str, polarion_api.WorkItemAttachment] = {}
for common_attachment_file_name in (
old_attachment_file_names & new_attachment_file_names
):
attachment = new_attachment_dict[common_attachment_file_name]
attachment.id = old_attachment_dict[common_attachment_file_name].id
if (
if attachment.file_name is not None and (
new_checksums.get(attachment.file_name)
!= old_checksums.get(attachment.file_name)
or self.force_update
Expand Down Expand Up @@ -449,14 +462,11 @@ def get_missing_link_ids(

@staticmethod
def _get_link_id(link: polarion_api.WorkItemLink) -> str:
return "/".join(
(
link.primary_work_item_id,
link.role,
link.secondary_work_item_project,
link.secondary_work_item_id,
)
)
secondary_id = link.secondary_work_item_id
if link.secondary_work_item_project:
assert link.secondary_work_item_project is not None
secondary_id = f"{link.secondary_work_item_project}/{secondary_id}"
return "/".join((link.primary_work_item_id, link.role, secondary_id))

def compare_and_update_work_items(
self, converter_session: data_session.ConverterSession
Expand Down
1 change: 1 addition & 0 deletions capella2polarion/converters/converter_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ def _filter_context_diagram_config(
def _filter_links(
c_type: str, links: list[LinkConfig], is_global: bool = False
) -> list[LinkConfig]:
c_class: type[common.ModelObject | diagram.Diagram]
if c_type == "diagram":
c_class = diagram.Diagram
else:
Expand Down
13 changes: 7 additions & 6 deletions capella2polarion/converters/document_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0
"""Module with classes and a loader for document rendering configs."""
import collections.abc as cabc
import logging
import pathlib
import typing as t
Expand Down Expand Up @@ -56,7 +57,7 @@ class BaseDocumentRenderingConfig(pydantic.BaseModel):
work_item_layouts: dict[str, WorkItemLayout] = pydantic.Field(
default_factory=dict
)
instances: list[DocumentRenderingInstance]
instances: cabc.Sequence[DocumentRenderingInstance]


class FullAuthorityDocumentRenderingConfig(BaseDocumentRenderingConfig):
Expand All @@ -69,7 +70,7 @@ class MixedAuthorityDocumentRenderingConfig(BaseDocumentRenderingConfig):
"""Mixed authority document with multiple auto generated sections."""

sections: dict[str, str]
instances: list[SectionBasedDocumentRenderingInstance]
instances: cabc.Sequence[SectionBasedDocumentRenderingInstance]


class DocumentConfigs(pydantic.BaseModel):
Expand Down Expand Up @@ -117,23 +118,23 @@ def generate_work_item_layouts(
results = []
for _type, conf in configs.items():
if conf.show_title and conf.show_description:
layouter = polarion_api.data_models.Layouter.SECTION
layouter = polarion_api.Layouter.SECTION
elif conf.show_description:
layouter = polarion_api.data_models.Layouter.PARAGRAPH
layouter = polarion_api.Layouter.PARAGRAPH
else:
if not conf.show_title:
logger.warning(
"Either the title or the description must be shown."
"For that reason, the title will be shown for %s.",
_type,
)
layouter = polarion_api.data_models.Layouter.TITLE
layouter = polarion_api.Layouter.TITLE
results.append(
polarion_api.RenderingLayout(
type=_type,
layouter=layouter,
label=polarion_html_helper.camel_case_to_words(_type),
properties=polarion_api.data_models.RenderingProperties(
properties=polarion_api.RenderingProperties(
fields_at_start=conf.fields_at_start,
fields_at_end=conf.fields_at_end,
fields_at_end_as_table=conf.show_fields_as_table,
Expand Down
5 changes: 4 additions & 1 deletion capella2polarion/converters/document_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def __insert_work_item(

session.inserted_work_items.append(wi)
if self._is_external_document(session):
# pylint: disable-next=line-too-long
return polarion_html_helper.POLARION_WORK_ITEM_DOCUMENT_PROJECT.format(
pid=wi.id,
lid=layout_index,
Expand Down Expand Up @@ -470,7 +471,9 @@ def _render_mixed_authority_documents(

def _render_full_authority_documents(
self,
full_authority_configs,
full_authority_configs: list[
document_config.FullAuthorityDocumentRenderingConfig
],
):
for config in full_authority_configs:
rendering_layouts = document_config.generate_work_item_layouts(
Expand Down
Loading
Loading