From 3a3b5e9f200d4808258db96921b43556df413d09 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 11 Oct 2024 01:25:33 +0200 Subject: [PATCH 1/6] rework `cyclonedx.model.lifecycle._LifecycleRepositoryHelper.xml_denormalize` Signed-off-by: Jan Kowalleck --- cyclonedx/model/lifecycle.py | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/cyclonedx/model/lifecycle.py b/cyclonedx/model/lifecycle.py index d13a60f9..6b9c2ef6 100644 --- a/cyclonedx/model/lifecycle.py +++ b/cyclonedx/model/lifecycle.py @@ -182,7 +182,6 @@ def json_normalize(cls, o: LifecycleRepository, *, **__: Any) -> Any: if len(o) == 0: return None - return [json_loads(li.as_json( # type:ignore[union-attr] view_=view)) for li in o] @@ -192,12 +191,13 @@ def json_denormalize(cls, o: List[Dict[str, Any]], repo = LifecycleRepository() for li in o: if 'phase' in li: - repo.add(PredefinedLifecycle.from_json(li)) # type:ignore[attr-defined] + repo.add(PredefinedLifecycle.from_json( # type:ignore[attr-defined] + li)) elif 'name' in li: - repo.add(NamedLifecycle.from_json(li)) # type:ignore[attr-defined] + repo.add(NamedLifecycle.from_json( # type:ignore[attr-defined] + li)) else: raise CycloneDxDeserializationException(f'unexpected: {li!r}') - return repo @classmethod @@ -208,12 +208,10 @@ def xml_normalize(cls, o: LifecycleRepository, *, **__: Any) -> Optional[Element]: if len(o) == 0: return None - elem = Element(element_name) for li in o: elem.append(li.as_xml( # type:ignore[union-attr] view_=view, as_string=False, element_name='lifecycle', xmlns=xmlns)) - return elem @classmethod @@ -221,20 +219,16 @@ def xml_denormalize(cls, o: Element, default_ns: Optional[str], **__: Any) -> LifecycleRepository: repo = LifecycleRepository() - - for li in o: - tag = li.tag if default_ns is None else li.tag.replace(f'{{{default_ns}}}', '') - - if tag == 'lifecycle': - stages = list(li) - - predefined_lifecycle = next((el for el in stages if 'phase' in el.tag), None) - named_lifecycle = next((el for el in stages if 'name' in el.tag), None) - if predefined_lifecycle is not None: - repo.add(PredefinedLifecycle.from_xml(li, default_ns)) # type:ignore[attr-defined] - elif named_lifecycle is not None: - repo.add(NamedLifecycle.from_xml(li, default_ns)) # type:ignore[attr-defined] + ns_map = {'bom': default_ns or ''} + # Do not iterate over `o` and do not check for expected `.tag` of items. + # This check could have been done by schema validators before even deserializing. + for li in o.iterfind('bom:lifecycle', ns_map): + if li.find('bom:phase', ns_map) is not None: + repo.add(PredefinedLifecycle.from_xml( # type:ignore[attr-defined] + li, default_ns)) + elif li.find('bom:name', ns_map) is not None: + repo.add(NamedLifecycle.from_xml( # type:ignore[attr-defined] + li, default_ns)) else: - raise CycloneDxDeserializationException(f'unexpected: {li!r}') - + raise CycloneDxDeserializationException(f'unexpected content: {li!r}') return repo From 2cf3dd8a3bb9fcbb3283499992548ceb6ee19244 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 11 Oct 2024 01:45:46 +0200 Subject: [PATCH 2/6] fix repr Signed-off-by: Jan Kowalleck --- cyclonedx/model/lifecycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclonedx/model/lifecycle.py b/cyclonedx/model/lifecycle.py index 6b9c2ef6..cc729b25 100644 --- a/cyclonedx/model/lifecycle.py +++ b/cyclonedx/model/lifecycle.py @@ -88,7 +88,7 @@ def __lt__(self, other: Any) -> bool: return NotImplemented def __repr__(self) -> str: - return f'' + return f'' @serializable.serializable_class From 17f34459e93cdda5d2d813c12eb17118295e77ae Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 11 Oct 2024 01:51:52 +0200 Subject: [PATCH 3/6] typos and docs Signed-off-by: Jan Kowalleck --- cyclonedx/model/lifecycle.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cyclonedx/model/lifecycle.py b/cyclonedx/model/lifecycle.py index cc729b25..780b7d95 100644 --- a/cyclonedx/model/lifecycle.py +++ b/cyclonedx/model/lifecycle.py @@ -43,13 +43,19 @@ @serializable.serializable_enum class LifecyclePhase(str, Enum): + """ + Enum object that defines the permissible 'phase' for a Lifecycle according to the CycloneDX schema. + + .. note:: + See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.3/#type_classification + """ DESIGN = 'design' - PREBUILD = 'pre-build' + PRE_BUILD = 'pre-build' BUILD = 'build' - POSTBUILD = 'post-build' + POST_BUILD = 'post-build' OPERATIONS = 'operations' DISCOVERY = 'discovery' - DECOMISSION = 'decommission' + DECOMMISSION = 'decommission' @serializable.serializable_class @@ -93,6 +99,13 @@ def __repr__(self) -> str: @serializable.serializable_class class NamedLifecycle: + """ + Object that defines custom state in the product lifecycle. + + .. note:: + See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.5/#metadata_lifecycles + """ + def __init__(self, name: str, *, description: Optional[str] = None) -> None: self._name = name self._description = description From 52f65794963c439564bc7f79150b68a73a08cf27 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 11 Oct 2024 02:01:03 +0200 Subject: [PATCH 4/6] fix setter Signed-off-by: Jan Kowalleck --- cyclonedx/model/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 0a29c703..73eaab43 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -125,7 +125,7 @@ def lifecycles(self) -> LifecycleRepository: return self._lifecycles @lifecycles.setter - def lifecycles(self, lifecycles: Optional[Iterable[Lifecycle]]) -> None: + def lifecycles(self, lifecycles: Iterable[Lifecycle]) -> None: self._lifecycles = LifecycleRepository(lifecycles) @property From 0a44190646c8a57ab32ecae3ea92913c3699456c Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 11 Oct 2024 02:07:28 +0200 Subject: [PATCH 5/6] condense code Signed-off-by: Jan Kowalleck --- cyclonedx/model/lifecycle.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cyclonedx/model/lifecycle.py b/cyclonedx/model/lifecycle.py index 780b7d95..24082f5d 100644 --- a/cyclonedx/model/lifecycle.py +++ b/cyclonedx/model/lifecycle.py @@ -178,9 +178,7 @@ class LifecycleRepository(SortedSet[Lifecycle]): This is a `set`, not a `list`. Order MUST NOT matter here. """ - else: - class LifecycleRepository(SortedSet): """Collection of :class:`Lifecycle`. From 0e842ad7cf874ee23be4c39224ef4c2adfde6370 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 11 Oct 2024 02:11:26 +0200 Subject: [PATCH 6/6] fix test Signed-off-by: Jan Kowalleck --- tests/_data/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/_data/models.py b/tests/_data/models.py index dff9280f..32770841 100644 --- a/tests/_data/models.py +++ b/tests/_data/models.py @@ -1130,7 +1130,7 @@ def get_bom_with_lifecycles() -> Bom: metadata=BomMetaData( lifecycles=[ PredefinedLifecycle(LifecyclePhase.BUILD), - PredefinedLifecycle(LifecyclePhase.POSTBUILD), + PredefinedLifecycle(LifecyclePhase.POST_BUILD), NamedLifecycle(name='platform-integration-testing', description='Integration testing specific to the runtime platform'), ],