From d356f73e67fd6a8630f605f6f512b60ae6f790db Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 22 Jul 2025 12:48:02 +0200 Subject: [PATCH] feat: bom-refs for known models Signed-off-by: Jan Kowalleck --- cyclonedx/model/contact.py | 54 +++++++++++++++++++++++++++----- cyclonedx/model/license.py | 64 +++++++++++++++++++++++++------------- 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/cyclonedx/model/contact.py b/cyclonedx/model/contact.py index 8ee7898d..83c5b9d2 100644 --- a/cyclonedx/model/contact.py +++ b/cyclonedx/model/contact.py @@ -24,7 +24,7 @@ from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparableTuple as _ComparableTuple -from ..schema.schema import SchemaVersion1Dot6 +from ..schema.schema import SchemaVersion1Dot5, SchemaVersion1Dot6 from . import XsUri from .bom_ref import BomRef @@ -49,7 +49,7 @@ def __init__( postal_code: Optional[str] = None, street_address: Optional[str] = None, ) -> None: - self._bom_ref = _bom_ref_from_str(bom_ref, optional=True) + self._bom_ref = _bom_ref_from_str(bom_ref) self.country = country self.region = region self.locality = locality @@ -58,11 +58,11 @@ def __init__( self.street_address = street_address @property - @serializable.json_name('bom-ref') @serializable.type_mapping(BomRef) @serializable.xml_attribute() @serializable.xml_name('bom-ref') - def bom_ref(self) -> Optional[BomRef]: + @serializable.json_name('bom-ref') + def bom_ref(self) -> BomRef: """ An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be unique within the BOM. @@ -167,7 +167,7 @@ def __comparable_tuple(self) -> _ComparableTuple: self.country, self.region, self.locality, self.postal_code, self.post_office_box_number, self.street_address, - None if self.bom_ref is None else self.bom_ref.value, + self._bom_ref.value, )) def __eq__(self, other: object) -> bool: @@ -199,14 +199,33 @@ class OrganizationalContact: def __init__( self, *, + bom_ref: Optional[Union[str, BomRef]] = None, name: Optional[str] = None, phone: Optional[str] = None, email: Optional[str] = None, ) -> None: + self._bom_ref = _bom_ref_from_str(bom_ref) self.name = name self.email = email self.phone = phone + @property + @serializable.view(SchemaVersion1Dot5) + @serializable.view(SchemaVersion1Dot6) + @serializable.type_mapping(BomRef) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') + @serializable.json_name('bom-ref') + def bom_ref(self) -> BomRef: + """ + An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be + unique within the BOM. + + Returns: + `BomRef` + """ + return self._bom_ref + @property @serializable.xml_sequence(1) @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) @@ -257,7 +276,8 @@ def phone(self, phone: Optional[str]) -> None: def __comparable_tuple(self) -> _ComparableTuple: return _ComparableTuple(( - self.name, self.email, self.phone + self.name, self.email, self.phone, + self._bom_ref.value, )) def __eq__(self, other: object) -> bool: @@ -289,16 +309,35 @@ class OrganizationalEntity: def __init__( self, *, + bom_ref: Optional[Union[str, BomRef]] = None, name: Optional[str] = None, urls: Optional[Iterable[XsUri]] = None, contacts: Optional[Iterable[OrganizationalContact]] = None, address: Optional[PostalAddress] = None, ) -> None: + self._bom_ref = _bom_ref_from_str(bom_ref) self.name = name self.address = address self.urls = urls or [] self.contacts = contacts or [] + @property + @serializable.view(SchemaVersion1Dot5) + @serializable.view(SchemaVersion1Dot6) + @serializable.type_mapping(BomRef) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') + @serializable.json_name('bom-ref') + def bom_ref(self) -> BomRef: + """ + An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be + unique within the BOM. + + Returns: + `BomRef` + """ + return self._bom_ref + @property @serializable.xml_sequence(10) @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) @@ -367,7 +406,8 @@ def contacts(self, contacts: Iterable[OrganizationalContact]) -> None: def __comparable_tuple(self) -> _ComparableTuple: return _ComparableTuple(( - self.name, _ComparableTuple(self.urls), _ComparableTuple(self.contacts) + self.name, _ComparableTuple(self.urls), _ComparableTuple(self.contacts), + self._bom_ref.value, )) def __eq__(self, other: object) -> bool: diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py index dddf70fb..49b9fd3d 100644 --- a/cyclonedx/model/license.py +++ b/cyclonedx/model/license.py @@ -29,11 +29,13 @@ import py_serializable as serializable from sortedcontainers import SortedSet +from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparableTuple as _ComparableTuple from ..exception.model import MutuallyExclusivePropertiesException from ..exception.serialization import CycloneDxDeserializationException -from ..schema.schema import SchemaVersion1Dot6 +from ..schema.schema import SchemaVersion1Dot5, SchemaVersion1Dot6 from . import AttachedText, XsUri +from .bom_ref import BomRef @serializable.serializable_enum @@ -75,6 +77,7 @@ class DisjunctiveLicense: def __init__( self, *, + bom_ref: Optional[Union[str, BomRef]] = None, id: Optional[str] = None, name: Optional[str] = None, text: Optional[AttachedText] = None, url: Optional[XsUri] = None, acknowledgement: Optional[LicenseAcknowledgement] = None, @@ -86,12 +89,30 @@ def __init__( 'Both `id` and `name` have been supplied - `name` will be ignored!', category=RuntimeWarning, stacklevel=1 ) + self._bom_ref = _bom_ref_from_str(bom_ref) self._id = id self._name = name if not id else None self._text = text self._url = url self._acknowledgement = acknowledgement + @property + @serializable.view(SchemaVersion1Dot5) + @serializable.view(SchemaVersion1Dot6) + @serializable.type_mapping(BomRef) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') + @serializable.json_name('bom-ref') + def bom_ref(self) -> BomRef: + """ + An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be + unique within the BOM. + + Returns: + `BomRef` + """ + return self._bom_ref + @property @serializable.xml_sequence(1) def id(self) -> Optional[str]: @@ -186,16 +207,6 @@ def url(self, url: Optional[XsUri]) -> None: # def properties(self, ...) -> None: # ... # TODO since CDX1.5 - # @property - # @serializable.json_name('bom-ref') - # @serializable.type_mapping(BomRefHelper) - # @serializable.view(SchemaVersion1Dot5) - # @serializable.view(SchemaVersion1Dot6) - # @serializable.xml_attribute() - # @serializable.xml_name('bom-ref') - # def bom_ref(self) -> BomRef: - # ... # TODO since CDX1.5 - @property @serializable.view(SchemaVersion1Dot6) @serializable.xml_attribute() @@ -227,6 +238,7 @@ def __comparable_tuple(self) -> _ComparableTuple: self._id, self._name, self._url, self._text, + self._bom_ref.value, )) def __eq__(self, other: object) -> bool: @@ -264,11 +276,30 @@ class LicenseExpression: def __init__( self, value: str, *, + bom_ref: Optional[Union[str, BomRef]] = None, acknowledgement: Optional[LicenseAcknowledgement] = None, ) -> None: + self._bom_ref = _bom_ref_from_str(bom_ref) self._value = value self._acknowledgement = acknowledgement + @property + @serializable.view(SchemaVersion1Dot5) + @serializable.view(SchemaVersion1Dot6) + @serializable.type_mapping(BomRef) + @serializable.xml_attribute() + @serializable.xml_name('bom-ref') + @serializable.json_name('bom-ref') + def bom_ref(self) -> BomRef: + """ + An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be + unique within the BOM. + + Returns: + `BomRef` + """ + return self._bom_ref + @property @serializable.xml_name('.') @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) @@ -286,16 +317,6 @@ def value(self) -> str: def value(self, value: str) -> None: self._value = value - # @property - # @serializable.json_name('bom-ref') - # @serializable.type_mapping(BomRefHelper) - # @serializable.view(SchemaVersion1Dot5) - # @serializable.view(SchemaVersion1Dot6) - # @serializable.xml_attribute() - # @serializable.xml_name('bom-ref') - # def bom_ref(self) -> BomRef: - # ... # TODO since CDX1.5 - @property @serializable.view(SchemaVersion1Dot6) @serializable.xml_attribute() @@ -325,6 +346,7 @@ def __comparable_tuple(self) -> _ComparableTuple: return _ComparableTuple(( self._acknowledgement, self._value, + self._bom_ref.value, )) def __hash__(self) -> int: