diff --git a/cyclonedx/_internal/compare.py b/cyclonedx/_internal/compare.py index 226fa615..ca2d46d6 100644 --- a/cyclonedx/_internal/compare.py +++ b/cyclonedx/_internal/compare.py @@ -58,27 +58,13 @@ def __gt__(self, other: Any) -> bool: return False -class ComparableDict: +class ComparableDict(ComparableTuple): """ Allows comparison of dictionaries, allowing for missing/None values. """ - def __init__(self, dict_: Dict[Any, Any]) -> None: - self._dict = dict_ - - def __lt__(self, other: Any) -> bool: - if not isinstance(other, ComparableDict): - return True - keys = sorted(self._dict.keys() | other._dict.keys()) - return ComparableTuple(self._dict.get(k) for k in keys) \ - < ComparableTuple(other._dict.get(k) for k in keys) - - def __gt__(self, other: Any) -> bool: - if not isinstance(other, ComparableDict): - return False - keys = sorted(self._dict.keys() | other._dict.keys()) - return ComparableTuple(self._dict.get(k) for k in keys) \ - > ComparableTuple(other._dict.get(k) for k in keys) + def __new__(cls, d: Dict[Any, Any]) -> 'ComparableDict': + return super(ComparableDict, cls).__new__(cls, sorted(d.items())) class ComparablePackageURL(ComparableTuple): @@ -86,12 +72,11 @@ class ComparablePackageURL(ComparableTuple): Allows comparison of PackageURL, allowing for qualifiers. """ - def __new__(cls, purl: 'PackageURL') -> 'ComparablePackageURL': - return super().__new__( - ComparablePackageURL, ( - purl.type, - purl.namespace, - purl.version, - ComparableDict(purl.qualifiers) if isinstance(purl.qualifiers, dict) else purl.qualifiers, - purl.subpath - )) + def __new__(cls, p: 'PackageURL') -> 'ComparablePackageURL': + return super(ComparablePackageURL, cls).__new__(cls, ( + p.type, + p.namespace, + p.version, + ComparableDict(p.qualifiers) if isinstance(p.qualifiers, dict) else p.qualifiers, + p.subpath + )) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index a0e6e58c..e286b0fd 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -133,22 +133,23 @@ def classification(self) -> str: def classification(self, classification: str) -> None: self._classification = classification + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.flow, self.classification + )) + def __eq__(self, other: object) -> bool: if isinstance(other, DataClassification): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: object) -> bool: if isinstance(other, DataClassification): - return _ComparableTuple(( - self.flow, self.classification - )) < _ComparableTuple(( - other.flow, other.classification - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.flow, self.classification)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -236,22 +237,23 @@ def content(self) -> str: def content(self, content: str) -> None: self._content = content + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.content_type, self.encoding, self.content, + )) + def __eq__(self, other: object) -> bool: if isinstance(other, AttachedText): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, AttachedText): - return _ComparableTuple(( - self.content_type, self.content, self.encoding - )) < _ComparableTuple(( - other.content_type, other.content, other.encoding - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.content, self.content_type, self.encoding)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -515,22 +517,23 @@ def content(self) -> str: def content(self, content: str) -> None: self._content = content + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.alg, self.content + )) + def __eq__(self, other: object) -> bool: if isinstance(other, HashType): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, HashType): - return _ComparableTuple(( - self.alg, self.content - )) < _ComparableTuple(( - other.alg, other.content - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.alg, self.content)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -733,7 +736,7 @@ def __init__(self, uri: str) -> None: def __eq__(self, other: Any) -> bool: if isinstance(other, XsUri): - return hash(other) == hash(self) + return self._uri == other._uri return False def __lt__(self, other: Any) -> bool: @@ -892,25 +895,24 @@ def hashes(self) -> 'SortedSet[HashType]': def hashes(self, hashes: Iterable[HashType]) -> None: self._hashes = SortedSet(hashes) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self._type, self._url, self._comment, + _ComparableTuple(self._hashes) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, ExternalReference): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, ExternalReference): - return _ComparableTuple(( - self._type, self._url, self._comment - )) < _ComparableTuple(( - other._type, other._url, other._comment - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash(( - self._type, self._url, self._comment, - tuple(sorted(self._hashes, key=hash)) - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -969,22 +971,23 @@ def value(self) -> Optional[str]: def value(self, value: Optional[str]) -> None: self._value = value + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.name, self.value + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Property): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Property): - return _ComparableTuple(( - self.name, self.value - )) < _ComparableTuple(( - other.name, other.value - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.name, self.value)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1060,22 +1063,23 @@ def encoding(self) -> Optional[Encoding]: def encoding(self, encoding: Optional[Encoding]) -> None: self._encoding = encoding + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.content, self.content_type, self.encoding + )) + def __eq__(self, other: object) -> bool: if isinstance(other, NoteText): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, NoteText): - return _ComparableTuple(( - self.content, self.content_type, self.encoding - )) < _ComparableTuple(( - other.content, other.content_type, other.encoding - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.content, self.content_type, self.encoding)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1144,22 +1148,23 @@ def locale(self, locale: Optional[str]) -> None: " ISO-3166 (or higher) country code. according to ISO-639 format. Examples include: 'en', 'en-US'." ) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.locale, self.text + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Note): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Note): - return _ComparableTuple(( - self.locale, self.text - )) < _ComparableTuple(( - other.locale, other.text - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.text, self.locale)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1234,22 +1239,23 @@ def email(self) -> Optional[str]: def email(self, email: Optional[str]) -> None: self._email = email + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.timestamp, self.name, self.email + )) + def __eq__(self, other: object) -> bool: if isinstance(other, IdentifiableAction): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, IdentifiableAction): - return _ComparableTuple(( - self.timestamp, self.name, self.email - )) < _ComparableTuple(( - other.timestamp, other.name, other.email - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.timestamp, self.name, self.email)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1287,16 +1293,16 @@ def text(self, text: str) -> None: def __eq__(self, other: object) -> bool: if isinstance(other, Copyright): - return hash(other) == hash(self) + return self._text == other._text return False def __lt__(self, other: Any) -> bool: if isinstance(other, Copyright): - return self.text < other.text + return self._text < other._text return NotImplemented def __hash__(self) -> int: - return hash(self.text) + return hash(self._text) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 8a8d7405..a1f84e12 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -25,6 +25,7 @@ import serializable from sortedcontainers import SortedSet +from .._internal.compare import ComparableTuple as _ComparableTuple from .._internal.time import get_now_utc as _get_now_utc from ..exception.model import LicenseExpressionAlongWithOthersException, UnknownComponentDependencyException from ..schema.schema import ( @@ -293,16 +294,20 @@ def properties(self) -> 'SortedSet[Property]': def properties(self, properties: Iterable[Property]) -> None: self._properties = SortedSet(properties) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + _ComparableTuple(self.authors), self.component, _ComparableTuple(self.licenses), self.manufacture, + _ComparableTuple(self.properties), + _ComparableTuple(self.lifecycles), self.supplier, self.timestamp, self.tools, self.manufacturer + )) + def __eq__(self, other: object) -> bool: if isinstance(other, BomMetaData): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash(( - tuple(self.authors), self.component, tuple(self.licenses), self.manufacture, tuple(self.properties), - tuple(self.lifecycles), self.supplier, self.timestamp, self.tools, self.manufacturer - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -722,17 +727,22 @@ def validate(self) -> bool: return True + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.serial_number, self.version, self.metadata, _ComparableTuple( + self.components), _ComparableTuple(self.services), + _ComparableTuple(self.external_references), _ComparableTuple( + self.dependencies), _ComparableTuple(self.properties), + _ComparableTuple(self.vulnerabilities), + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Bom): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash(( - self.serial_number, self.version, self.metadata, tuple(self.components), tuple(self.services), - tuple(self.external_references), tuple(self.dependencies), tuple(self.properties), - tuple(self.vulnerabilities), - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 6356afd0..89c19b8a 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -171,22 +171,25 @@ def message(self) -> Optional[str]: def message(self, message: Optional[str]) -> None: self._message = message + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.uid, self.url, + self.author, self.committer, + self.message + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Commit): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Commit): - return _ComparableTuple(( - self.uid, self.url, self.author, self.committer, self.message - )) < _ComparableTuple(( - other.uid, other.url, other.author, other.committer, other.message - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.uid, self.url, self.author, self.committer, self.message)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -281,13 +284,19 @@ def copyright(self) -> 'SortedSet[Copyright]': def copyright(self, copyright: Iterable[Copyright]) -> None: self._copyright = SortedSet(copyright) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + _ComparableTuple(self.licenses), + _ComparableTuple(self.copyright), + )) + def __eq__(self, other: object) -> bool: if isinstance(other, ComponentEvidence): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((tuple(self.licenses), tuple(self.copyright))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -478,22 +487,24 @@ def url(self) -> Optional[XsUri]: def url(self, url: Optional[XsUri]) -> None: self._url = url + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.url, + self.text, + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Diff): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Diff): - return _ComparableTuple(( - self.url, self.text - )) < _ComparableTuple(( - other.url, other.text - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.text, self.url)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -580,22 +591,24 @@ def resolves(self) -> 'SortedSet[IssueType]': def resolves(self, resolves: Iterable[IssueType]) -> None: self._resolves = SortedSet(resolves) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.type, self.diff, + _ComparableTuple(self.resolves) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Patch): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Patch): - return _ComparableTuple(( - self.type, self.diff, _ComparableTuple(self.resolves) - )) < _ComparableTuple(( - other.type, other.diff, _ComparableTuple(other.resolves) - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.type, self.diff, tuple(self.resolves))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -748,16 +761,23 @@ def notes(self) -> Optional[str]: def notes(self, notes: Optional[str]) -> None: self._notes = notes + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + _ComparableTuple(self.ancestors), + _ComparableTuple(self.descendants), + _ComparableTuple(self.variants), + _ComparableTuple(self.commits), + _ComparableTuple(self.patches), + self.notes + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Pedigree): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash(( - tuple(self.ancestors), tuple(self.descendants), tuple(self.variants), tuple(self.commits), - tuple(self.patches), self.notes - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -893,13 +913,23 @@ def url(self) -> Optional[XsUri]: def url(self, url: Optional[XsUri]) -> None: self._url = url + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.tag_id, + self.name, self.version, + self.tag_version, + self.patch, + self.url, + self.text, + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Swid): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((self.tag_id, self.name, self.version, self.tag_version, self.patch, self.text, self.url)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -946,7 +976,7 @@ def deserialize(cls, o: Any) -> 'OmniborId': def __eq__(self, other: Any) -> bool: if isinstance(other, OmniborId): - return hash(other) == hash(self) + return self._id == other._id return False def __lt__(self, other: Any) -> bool: @@ -1005,7 +1035,7 @@ def deserialize(cls, o: Any) -> 'Swhid': def __eq__(self, other: Any) -> bool: if isinstance(other, Swhid): - return hash(other) == hash(self) + return self._id == other._id return False def __lt__(self, other: Any) -> bool: @@ -1741,51 +1771,34 @@ def get_pypi_url(self) -> str: else: return f'https://pypi.org/project/{self.name}' + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.type, self.group, self.name, self.version, + None if self.purl is None else _ComparablePackageURL(self.purl), + self.swid, self.cpe, _ComparableTuple(self.swhids), + self.supplier, self.author, self.publisher, + self.description, + self.mime_type, self.scope, _ComparableTuple(self.hashes), + _ComparableTuple(self.licenses), self.copyright, + self.pedigree, + _ComparableTuple(self.external_references), _ComparableTuple(self.properties), + _ComparableTuple(self.components), self.evidence, self.release_notes, self.modified, + _ComparableTuple(self.authors), _ComparableTuple(self.omnibor_ids), self.manufacturer, + self.crypto_properties, _ComparableTuple(self.tags), + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Component): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Component): - return _ComparableTuple(( - self.type, self.group, self.name, self.version, - self.mime_type, self.supplier, self.author, self.publisher, - self.description, self.scope, _ComparableTuple(self.hashes), - _ComparableTuple(self.licenses), self.copyright, self.cpe, - None if self.purl is None else _ComparablePackageURL(self.purl), - self.swid, self.pedigree, - _ComparableTuple(self.external_references), _ComparableTuple(self.properties), - _ComparableTuple(self.components), self.evidence, self.release_notes, self.modified, - _ComparableTuple(self.authors), _ComparableTuple(self.omnibor_ids), self.manufacturer, - _ComparableTuple(self.swhids), self.crypto_properties, _ComparableTuple(self.tags) - )) < _ComparableTuple(( - other.type, other.group, other.name, other.version, - other.mime_type, other.supplier, other.author, other.publisher, - other.description, other.scope, _ComparableTuple(other.hashes), - _ComparableTuple(other.licenses), other.copyright, other.cpe, - None if other.purl is None else _ComparablePackageURL(other.purl), - other.swid, other.pedigree, - _ComparableTuple(other.external_references), _ComparableTuple(other.properties), - _ComparableTuple(other.components), other.evidence, other.release_notes, other.modified, - _ComparableTuple(other.authors), _ComparableTuple(other.omnibor_ids), other.manufacturer, - _ComparableTuple(other.swhids), other.crypto_properties, _ComparableTuple(other.tags) - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash(( - self.type, self.group, self.name, self.version, - self.mime_type, self.supplier, self.author, self.publisher, - self.description, self.scope, tuple(self.hashes), - tuple(self.licenses), self.copyright, self.cpe, - self.purl, - self.swid, self.pedigree, - tuple(self.external_references), tuple(self.properties), - tuple(self.components), self.evidence, self.release_notes, self.modified, - tuple(self.authors), tuple(self.omnibor_ids), self.manufacturer, - tuple(self.swhids), self.crypto_properties, tuple(self.tags) - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f' Optional[str]: def street_address(self, street_address: Optional[str]) -> None: self._street_address = street_address + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.bom_ref, + self.country, self.region, self.locality, self.postal_code, + self.post_office_box_number, + self.street_address + )) + def __eq__(self, other: object) -> bool: if isinstance(other, PostalAddress): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, PostalAddress): - return _ComparableTuple(( - self.bom_ref, self.country, self.region, self.locality, self.post_office_box_number, self.postal_code, - self.street_address - )) < _ComparableTuple(( - other.bom_ref, other.country, other.region, other.locality, other.post_office_box_number, - other.postal_code, other.street_address - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.bom_ref, self.country, self.region, self.locality, self.post_office_box_number, - self.postal_code, self.street_address)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -268,6 +269,7 @@ def __lt__(self, other: Any) -> bool: return NotImplemented def __hash__(self) -> int: + # TODO return hash((self.name, self.phone, self.email)) def __repr__(self) -> str: @@ -373,6 +375,7 @@ def __lt__(self, other: Any) -> bool: return NotImplemented def __hash__(self) -> int: + # TODO return hash((self.name, tuple(self.urls), tuple(self.contacts))) def __repr__(self) -> str: diff --git a/cyclonedx/model/crypto.py b/cyclonedx/model/crypto.py index d9fd8106..d83dfd93 100644 --- a/cyclonedx/model/crypto.py +++ b/cyclonedx/model/crypto.py @@ -500,6 +500,7 @@ def __eq__(self, other: object) -> bool: return False def __hash__(self) -> int: + # TODO return hash((self.primitive, self._parameter_set_identifier, self.curve, self.execution_environment, self.implementation_platform, tuple(self.certification_levels), self.mode, self.padding, tuple(self.crypto_functions), self.classical_security_level, self.nist_quantum_security_level,)) @@ -666,14 +667,19 @@ def certificate_extension(self) -> Optional[str]: def certificate_extension(self, certificate_extension: Optional[str]) -> None: self._certificate_extension = certificate_extension + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.subject_name, self.issuer_name, self.not_valid_before, self.not_valid_after, + self.certificate_format, self.certificate_extension + )) + def __eq__(self, other: object) -> bool: if isinstance(other, CertificateProperties): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((self.subject_name, self.issuer_name, self.not_valid_before, self.not_valid_after, - self.certificate_format, self.certificate_extension)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -789,13 +795,18 @@ def algorithm_ref(self) -> Optional[BomRef]: def algorithm_ref(self, algorithm_ref: Optional[BomRef]) -> None: self._algorithm_ref = algorithm_ref + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.mechanism, self.algorithm_ref + )) + def __eq__(self, other: object) -> bool: if isinstance(other, RelatedCryptoMaterialSecuredBy): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((self.mechanism, self.algorithm_ref)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1028,14 +1039,19 @@ def secured_by(self) -> Optional[RelatedCryptoMaterialSecuredBy]: def secured_by(self, secured_by: Optional[RelatedCryptoMaterialSecuredBy]) -> None: self._secured_by = secured_by + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.type, self.id, self.state, self.algorithm_ref, self.creation_date, self.activation_date, + self.update_date, self.expiration_date, self.value, self.size, self.format, self.secured_by + )) + def __eq__(self, other: object) -> bool: if isinstance(other, RelatedCryptoMaterialProperties): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((self.type, self.id, self.state, self.algorithm_ref, self.creation_date, self.activation_date, - self.update_date, self.expiration_date, self.value, self.size, self.format, self.secured_by)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1136,22 +1152,23 @@ def identifiers(self) -> 'SortedSet[str]': def identifiers(self, identifiers: Iterable[str]) -> None: self._identifiers = SortedSet(identifiers) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.name, _ComparableTuple(self.algorithms), _ComparableTuple(self.identifiers) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, ProtocolPropertiesCipherSuite): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, ProtocolPropertiesCipherSuite): - return _ComparableTuple(( - self.name, _ComparableTuple(self.algorithms), _ComparableTuple(self.identifiers) - )) < _ComparableTuple(( - other.name, _ComparableTuple(other.algorithms), _ComparableTuple(other.identifiers) - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.name, tuple(self.algorithms), tuple(self.identifiers))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1277,13 +1294,23 @@ def auth(self) -> 'SortedSet[BomRef]': def auth(self, auth: Iterable[BomRef]) -> None: self._auth = SortedSet(auth) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + _ComparableTuple(self.encr), + _ComparableTuple(self.prf), + _ComparableTuple(self.integ), + _ComparableTuple(self.ke), + self.esn, + _ComparableTuple(self.auth) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Ikev2TransformTypes): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((tuple(self.encr), tuple(self.prf), tuple(self.integ), tuple(self.ke), self.esn, tuple(self.auth))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1376,13 +1403,18 @@ def ikev2_transform_types(self) -> Optional[Ikev2TransformTypes]: def ikev2_transform_types(self, ikev2_transform_types: Optional[Ikev2TransformTypes]) -> None: self._ikev2_transform_types = ikev2_transform_types + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.type, self.version, _ComparableTuple(self.cipher_suites), self.ikev2_transform_types + )) + def __eq__(self, other: object) -> bool: if isinstance(other, ProtocolProperties): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((self.type, self.version, tuple(self.cipher_suites), self.ikev2_transform_types)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1513,33 +1545,28 @@ def oid(self) -> Optional[str]: def oid(self, oid: Optional[str]) -> None: self._oid = oid + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.asset_type, + self.algorithm_properties, + self.certificate_properties, + self.related_crypto_material_properties, + self.protocol_properties, + self.oid, + )) + def __eq__(self, other: object) -> bool: if isinstance(other, CryptoProperties): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, CryptoProperties): - return _ComparableTuple(( - self.asset_type, - self.algorithm_properties, - self.certificate_properties, - self.related_crypto_material_properties, - self.protocol_properties, - self.oid, - )) < _ComparableTuple(( - other.asset_type, - other.algorithm_properties, - other.certificate_properties, - other.related_crypto_material_properties, - other.protocol_properties, - other.oid, - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.asset_type, self.algorithm_properties, self.certificate_properties, - self.related_crypto_material_properties, self.protocol_properties, self.oid)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/definition.py b/cyclonedx/model/definition.py index 90872e32..dda2fe6d 100644 --- a/cyclonedx/model/definition.py +++ b/cyclonedx/model/definition.py @@ -608,13 +608,13 @@ def __eq__(self, other: object) -> bool: return self.__comparable_tuple() == other.__comparable_tuple() return False - def __hash__(self) -> int: - return hash(self.__comparable_tuple()) - def __lt__(self, other: Any) -> bool: if isinstance(other, Definitions): return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented + def __hash__(self) -> int: + return hash(self.__comparable_tuple()) + def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/dependency.py b/cyclonedx/model/dependency.py index 0b054c37..fa1801ca 100644 --- a/cyclonedx/model/dependency.py +++ b/cyclonedx/model/dependency.py @@ -83,22 +83,23 @@ def dependencies(self, dependencies: Iterable['Dependency']) -> None: def dependencies_as_bom_refs(self) -> Set[BomRef]: return set(map(lambda d: d.ref, self.dependencies)) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.ref, _ComparableTuple(self.dependencies) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Dependency): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Dependency): - return _ComparableTuple(( - self.ref, _ComparableTuple(self.dependencies) - )) < _ComparableTuple(( - other.ref, _ComparableTuple(other.dependencies) - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.ref, tuple(self.dependencies))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/issue.py b/cyclonedx/model/issue.py index 14b35581..c0779454 100644 --- a/cyclonedx/model/issue.py +++ b/cyclonedx/model/issue.py @@ -90,22 +90,23 @@ def url(self) -> Optional[XsUri]: def url(self, url: Optional[XsUri]) -> None: self._url = url + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.name, self.url + )) + def __eq__(self, other: object) -> bool: if isinstance(other, IssueTypeSource): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, IssueTypeSource): - return _ComparableTuple(( - self.name, self.url - )) < _ComparableTuple(( - other.name, other.url - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.name, self.url)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -231,24 +232,24 @@ def references(self) -> 'SortedSet[XsUri]': def references(self, references: Iterable[XsUri]) -> None: self._references = SortedSet(references) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.type, self.id, self.name, self.description, self.source, + _ComparableTuple(self.references) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, IssueType): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, IssueType): - return _ComparableTuple(( - self.type, self.id, self.name, self.description, self.source - )) < _ComparableTuple(( - other.type, other.id, other.name, other.description, other.source - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash(( - self.type, self.id, self.name, self.description, self.source, tuple(self.references) - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py index 7c6a40e0..fc5d482a 100644 --- a/cyclonedx/model/license.py +++ b/cyclonedx/model/license.py @@ -218,24 +218,28 @@ def acknowledgement(self) -> Optional[LicenseAcknowledgement]: def acknowledgement(self, acknowledgement: Optional[LicenseAcknowledgement]) -> None: self._acknowledgement = acknowledgement + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self._acknowledgement, + self._id, self._name, + self._url, + self._text, + )) + def __eq__(self, other: object) -> bool: if isinstance(other, DisjunctiveLicense): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, DisjunctiveLicense): - return _ComparableTuple(( - self._id, self._name - )) < _ComparableTuple(( - other._id, other._name - )) + return self.__comparable_tuple() < other.__comparable_tuple() if isinstance(other, LicenseExpression): return False # self after any LicenseExpression return NotImplemented def __hash__(self) -> int: - return hash((self._id, self._name, self._text, self._url, self._acknowledgement)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -311,17 +315,23 @@ def acknowledgement(self) -> Optional[LicenseAcknowledgement]: def acknowledgement(self, acknowledgement: Optional[LicenseAcknowledgement]) -> None: self._acknowledgement = acknowledgement + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self._acknowledgement, + self._value, + )) + def __hash__(self) -> int: - return hash((self._value, self._acknowledgement)) + return hash(self.__comparable_tuple()) def __eq__(self, other: object) -> bool: if isinstance(other, LicenseExpression): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, LicenseExpression): - return self._value < other._value + return self.__comparable_tuple() < other.__comparable_tuple() if isinstance(other, DisjunctiveLicense): return True # self before any DisjunctiveLicense return NotImplemented diff --git a/cyclonedx/model/lifecycle.py b/cyclonedx/model/lifecycle.py index 7975e339..77cbd720 100644 --- a/cyclonedx/model/lifecycle.py +++ b/cyclonedx/model/lifecycle.py @@ -83,7 +83,7 @@ def __hash__(self) -> int: def __eq__(self, other: object) -> bool: if isinstance(other, PredefinedLifecycle): - return hash(other) == hash(self) + return self._phase == other._phase return False def __lt__(self, other: Any) -> bool: @@ -142,19 +142,22 @@ def description(self) -> Optional[str]: def description(self, description: Optional[str]) -> None: self._description = description + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self._name, self._description + )) + def __hash__(self) -> int: - return hash((self._name, self._description)) + return hash(self.__comparable_tuple()) def __eq__(self, other: object) -> bool: if isinstance(other, NamedLifecycle): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, NamedLifecycle): - return _ComparableTuple((self._name, self._description)) < _ComparableTuple( - (other._name, other._description) - ) + return self.__comparable_tuple() < other.__comparable_tuple() if isinstance(other, PredefinedLifecycle): return False # put NamedLifecycle after any PredefinedLifecycle return NotImplemented diff --git a/cyclonedx/model/release_note.py b/cyclonedx/model/release_note.py index c6591735..9d5fac7b 100644 --- a/cyclonedx/model/release_note.py +++ b/cyclonedx/model/release_note.py @@ -21,6 +21,7 @@ import serializable from sortedcontainers import SortedSet +from .._internal.compare import ComparableTuple as _ComparableTuple from ..model import Note, Property, XsUri from ..model.issue import IssueType @@ -233,16 +234,23 @@ def properties(self) -> 'SortedSet[Property]': def properties(self, properties: Iterable[Property]) -> None: self._properties = SortedSet(properties) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.type, self.title, self.featured_image, self.social_image, self.description, self.timestamp, + _ComparableTuple(self.aliases), + _ComparableTuple(self.tags), + _ComparableTuple(self.resolves), + _ComparableTuple(self.notes), + _ComparableTuple(self.properties) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, ReleaseNotes): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash(( - self.type, self.title, self.featured_image, self.social_image, self.description, self.timestamp, - tuple(self.aliases), tuple(self.tags), tuple(self.resolves), tuple(self.notes), tuple(self.properties) - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/service.py b/cyclonedx/model/service.py index f96ef1da..06b96373 100644 --- a/cyclonedx/model/service.py +++ b/cyclonedx/model/service.py @@ -352,26 +352,28 @@ def release_notes(self) -> Optional[ReleaseNotes]: def release_notes(self, release_notes: Optional[ReleaseNotes]) -> None: self._release_notes = release_notes + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.group, self.name, self.version, + self.provider, self.description, + self.authenticated, _ComparableTuple(self.data), _ComparableTuple(self.endpoints), + _ComparableTuple(self.external_references), _ComparableTuple(self.licenses), + _ComparableTuple(self.properties), self.release_notes, _ComparableTuple(self.services), + self.x_trust_boundary + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Service): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Service): - return _ComparableTuple(( - self.group, self.name, self.version - )) < _ComparableTuple(( - other.group, other.name, other.version - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash(( - self.authenticated, tuple(self.data), self.description, tuple(self.endpoints), - tuple(self.external_references), self.group, tuple(self.licenses), self.name, tuple(self.properties), - self.provider, self.release_notes, tuple(self.services), self.version, self.x_trust_boundary - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/cyclonedx/model/tool.py b/cyclonedx/model/tool.py index ff923bea..74f0d5de 100644 --- a/cyclonedx/model/tool.py +++ b/cyclonedx/model/tool.py @@ -148,22 +148,24 @@ def external_references(self) -> 'SortedSet[ExternalReference]': def external_references(self, external_references: Iterable[ExternalReference]) -> None: self._external_references = SortedSet(external_references) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.vendor, self.name, self.version, + _ComparableTuple(self.hashes), _ComparableTuple(self.external_references) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Tool): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Tool): - return _ComparableTuple(( - self.vendor, self.name, self.version - )) < _ComparableTuple(( - other.vendor, other.name, other.version - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.vendor, self.name, self.version, tuple(self.hashes), tuple(self.external_references))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -250,16 +252,20 @@ def __bool__(self) -> bool: or len(self._components) > 0 \ or len(self._services) > 0 - def __eq__(self, other: object) -> bool: - if not isinstance(other, ToolRepository): - return False + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + _ComparableTuple(self._tools), + _ComparableTuple(self._components), + _ComparableTuple(self._services) + )) - return self._tools == other._tools \ - and self._components == other._components \ - and self._services == other._services + def __eq__(self, other: object) -> bool: + if isinstance(other, ToolRepository): + return self.__comparable_tuple() == other.__comparable_tuple() + return False def __hash__(self) -> int: - return hash((tuple(self._tools), tuple(self._components), tuple(self._services))) + return hash(self.__comparable_tuple()) class _ToolRepositoryHelper(BaseHelper): diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 9b0e8a90..7b06c4be 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -125,22 +125,23 @@ def status(self) -> Optional[ImpactAnalysisAffectedStatus]: def status(self, status: Optional[ImpactAnalysisAffectedStatus]) -> None: self._status = status + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.version, self.range, self.status + )) + def __eq__(self, other: object) -> bool: if isinstance(other, BomTargetVersionRange): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, BomTargetVersionRange): - return _ComparableTuple(( - self.version, self.range, self.status - )) < _ComparableTuple(( - other.version, other.range, other.status - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.version, self.range, self.status)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -196,18 +197,24 @@ def versions(self) -> 'SortedSet[BomTargetVersionRange]': def versions(self, versions: Iterable[BomTargetVersionRange]) -> None: self._versions = SortedSet(versions) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.ref, + _ComparableTuple(self.versions) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, BomTarget): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, BomTarget): - return self.ref < other.ref + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.ref, tuple(self.versions))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -325,13 +332,18 @@ def detail(self, detail: Optional[str]) -> None: # def last_updated(self, ...) -> None: # ... # TODO since CDX 1.5 + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.state, self.justification, tuple(self.responses), self.detail + )) + def __eq__(self, other: object) -> bool: if isinstance(other, VulnerabilityAnalysis): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __hash__(self) -> int: - return hash((self.state, self.justification, tuple(self.responses), self.detail)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -379,22 +391,23 @@ def url(self) -> XsUri: def url(self, url: XsUri) -> None: self._url = url + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.title, self.url + )) + def __eq__(self, other: object) -> bool: if isinstance(other, VulnerabilityAdvisory): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, VulnerabilityAdvisory): - return _ComparableTuple(( - self.title, self.url - )) < _ComparableTuple(( - other.title, other.url - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.title, self.url)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -520,22 +533,23 @@ def source(self) -> Optional[VulnerabilitySource]: def source(self, source: Optional[VulnerabilitySource]) -> None: self._source = source + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.id, self.source + )) + def __eq__(self, other: object) -> bool: if isinstance(other, VulnerabilityReference): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, VulnerabilityReference): - return _ComparableTuple(( - self.id, self.source - )) < _ComparableTuple(( - other.id, other.source - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.id, self.source)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -831,25 +845,30 @@ def justification(self) -> Optional[str]: def justification(self, justification: Optional[str]) -> None: self._justification = justification + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.severity, self.score or 0, + self.source, self.method, self.vector, + self.justification + )) + def __eq__(self, other: object) -> bool: if isinstance(other, VulnerabilityRating): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, VulnerabilityRating): - return _ComparableTuple(( - self.severity, self.score, self.score or 0, self.method, self.vector, self.justification - )) < _ComparableTuple(( - other.severity, other.score, other.score or 0, other.method, other.vector, other.justification - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((self.source, self.score or 0, self.severity, self.method, self.vector, self.justification)) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: - return f'' + return f'' @serializable.serializable_class @@ -908,18 +927,24 @@ def individuals(self) -> 'SortedSet[OrganizationalContact]': def individuals(self, individuals: Iterable[OrganizationalContact]) -> None: self._individuals = SortedSet(individuals) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + _ComparableTuple(self.organizations), + _ComparableTuple(self.individuals) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, VulnerabilityCredits): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, VulnerabilityCredits): - return hash(self) < hash(other) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash((tuple(self.organizations), tuple(self.individuals))) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' @@ -1307,26 +1332,30 @@ def properties(self) -> 'SortedSet[Property]': def properties(self, properties: Iterable[Property]) -> None: self._properties = SortedSet(properties) + def __comparable_tuple(self) -> _ComparableTuple: + return _ComparableTuple(( + self.id, + self.source, _ComparableTuple(self.references), + _ComparableTuple(self.ratings), _ComparableTuple(self.cwes), self.description, + self.detail, self.recommendation, self.workaround, _ComparableTuple(self.advisories), + self.created, self.published, self.updated, + self.credits, self.tools, self.analysis, + _ComparableTuple(self.affects), + _ComparableTuple(self.properties) + )) + def __eq__(self, other: object) -> bool: if isinstance(other, Vulnerability): - return hash(other) == hash(self) + return self.__comparable_tuple() == other.__comparable_tuple() return False def __lt__(self, other: Any) -> bool: if isinstance(other, Vulnerability): - return _ComparableTuple(( - self.id, self.description, self.detail, self.source, self.created, self.published - )) < _ComparableTuple(( - other.id, other.description, other.detail, other.source, other.created, other.published - )) + return self.__comparable_tuple() < other.__comparable_tuple() return NotImplemented def __hash__(self) -> int: - return hash(( - self.id, self.source, tuple(self.references), tuple(self.ratings), tuple(self.cwes), self.description, - self.detail, self.recommendation, self.workaround, tuple(self.advisories), self.created, self.published, - self.updated, self.credits, self.tools, self.analysis, tuple(self.affects), tuple(self.properties) - )) + return hash(self.__comparable_tuple()) def __repr__(self) -> str: return f'' diff --git a/tests/test_model_component.py b/tests/test_model_component.py index c25fdc91..8212cf53 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -351,8 +351,8 @@ def test_sort(self) -> None: class TestModelAttachedText(TestCase): def test_sort(self) -> None: - # expected sort order: (content_type, content, encoding) - expected_order = [0, 4, 2, 1, 3] + # expected sort order: (content_type, encoding, content) + expected_order = [0, 2, 4, 1, 3] text = [ AttachedText(content='a', content_type='a', encoding=Encoding.BASE_64), AttachedText(content='a', content_type='b', encoding=Encoding.BASE_64), @@ -443,17 +443,17 @@ def test_no_params(self) -> None: def test_same_1(self) -> None: p1 = get_pedigree_1() p2 = get_pedigree_1() - self.assertNotEqual(id(p1), id(p2)) - self.assertEqual(hash(p1), hash(p2)) - self.assertTrue(p1 == p2) + self.assertNotEqual(id(p1), id(p2), 'id') + self.assertEqual(hash(p1), hash(p2), 'hash') + self.assertTrue(p1 == p2, 'equal') def test_not_same_1(self) -> None: p1 = get_pedigree_1() p2 = get_pedigree_1() p2.notes = 'Some other notes here' - self.assertNotEqual(id(p1), id(p2)) - self.assertNotEqual(hash(p1), hash(p2)) - self.assertFalse(p1 == p2) + self.assertNotEqual(id(p1), id(p2), 'id') + self.assertNotEqual(hash(p1), hash(p2), 'hash') + self.assertFalse(p1 == p2, 'equal') class TestModelSwid(TestCase): @@ -461,20 +461,20 @@ class TestModelSwid(TestCase): def test_same_1(self) -> None: sw_1 = get_swid_1() sw_2 = get_swid_1() - self.assertNotEqual(id(sw_1), id(sw_2)) - self.assertEqual(hash(sw_1), hash(sw_2)) - self.assertTrue(sw_1 == sw_2) + self.assertNotEqual(id(sw_1), id(sw_2), 'id') + self.assertEqual(hash(sw_1), hash(sw_2), 'hash') + self.assertTrue(sw_1 == sw_2, 'equal') def test_same_2(self) -> None: sw_1 = get_swid_2() sw_2 = get_swid_2() - self.assertNotEqual(id(sw_1), id(sw_2)) - self.assertEqual(hash(sw_1), hash(sw_2)) - self.assertTrue(sw_1 == sw_2) + self.assertNotEqual(id(sw_1), id(sw_2), 'id') + self.assertEqual(hash(sw_1), hash(sw_2), 'hash') + self.assertTrue(sw_1 == sw_2, 'equal') def test_not_same(self) -> None: sw_1 = get_swid_1() sw_2 = get_swid_2() - self.assertNotEqual(id(sw_1), id(sw_2)) - self.assertNotEqual(hash(sw_1), hash(sw_2)) - self.assertFalse(sw_1 == sw_2) + self.assertNotEqual(id(sw_1), id(sw_2), 'id') + self.assertNotEqual(hash(sw_1), hash(sw_2), 'hash') + self.assertFalse(sw_1 == sw_2, 'equal') diff --git a/tests/test_model_vulnerability.py b/tests/test_model_vulnerability.py index e18f6003..d4a556ac 100644 --- a/tests/test_model_vulnerability.py +++ b/tests/test_model_vulnerability.py @@ -188,7 +188,7 @@ def test_sort(self) -> None: datetime2 = datetime1 + timedelta(seconds=5) # expected sort order: (id, description, detail, source, created, published) - expected_order = [0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11] + expected_order = [0, 6, 1, 7, 2, 9, 10, 8, 3, 4, 5, 11] vulnerabilities = [ Vulnerability(bom_ref='0', id='a', description='a', detail='a', source=source1, created=datetime1, published=datetime1), @@ -281,7 +281,7 @@ def test_sort(self) -> None: method_a = VulnerabilityScoreSource.CVSS_V3_1 # expected sort order: ([severity], [score], [source], [method], [vector], [justification]) - expected_order = [0, 1, 2, 3, 4, 5, 6, 7] + expected_order = [5, 0, 1, 2, 3, 4, 6, 7] refs = [ VulnerabilityRating(severity=VulnerabilitySeverity.HIGH, score=Decimal(10), source=source_a, method=method_a, vector='a', justification='a'), @@ -298,6 +298,7 @@ def test_sort(self) -> None: ] sorted_refs = sorted(refs) expected_refs = reorder(refs, expected_order) + self.maxDiff = None # gimme all diff on error self.assertListEqual(sorted_refs, expected_refs)