Skip to content

Conversation

@qkaiser
Copy link
Contributor

@qkaiser qkaiser commented Oct 20, 2025

Since cyclonedx-python-lib has the tendency to use SortedSets to store deserialized instances, this can lead to errors like these:

TypeError: '<' not supported between instances of 'ComponentEvidence' and 'ComponentEvidence'

When loading a CycloneDX JSON SBOM using Bom.from_json().

This is due to the lack of 'less-than' operator, which is now implemented with __lt__ for all models that were still missing it.

We implemented it this way since that's how it's done for all the existing models, but an easier implementation would propably to add functools' @total_ordering decorator to all classes.

@qkaiser qkaiser requested a review from a team as a code owner October 20, 2025 09:15
Since cyclonedx-python-lib has the tendency to use SortedSets to store
deserialized instances, this can lead to errors like these:

TypeError: '<' not supported between instances of 'ComponentEvidence'
and 'ComponentEvidence'

When loading a CycloneDX JSON SBOM using Bom.from_json().

This is due to the lack of 'less-than' operator, which is now
implemented with __lt__ for all models that were still missing it.

We implemented it this way since that's how it's done for all the
existing models, but an easier implementation would propably to add
functools' @total_ordering decorator to those classes.

Signed-off-by: Quentin Kaiser <quentin.kaiser@onekey.com>
@qkaiser qkaiser force-pushed the fix-component-evidence-comparison branch from a84710c to 548d66e Compare October 20, 2025 09:17
@jkowalleck
Copy link
Member

thanks for your work.

Could you elaborate why the implementation of __lt__ in the following models were needed?

  • BomMetaData
  • Pedigree
  • Swid
  • CallStackFrame
  • ComponentEvidence
  • AlgorithmProperties
  • CertificateProperties
  • RelatedCryptoMaterialSecuredBy
  • RelatedCryptoMaterialProperties
  • Ikev2TransformTypes
  • ProtocolProperties
  • ReleaseNotes
  • ToolRepository

@jkowalleck
Copy link
Member

jkowalleck commented Oct 21, 2025

Since cyclonedx-python-lib has the tendency to use SortedSets to store deserialized instances, this can lead to errors like these:

TypeError: '<' not supported between instances of 'ComponentEvidence' and 'ComponentEvidence'

When loading a CycloneDX JSON SBOM using Bom.from_json().

which JSON did you use?

Anyway, is there a place where ComponentEvidence is about to be used in a SortedSet?
I am unable to find it, could you help me?

@jkowalleck jkowalleck changed the title fix: implement __lt__ for models still missing it feat: implement __lt__ for models still missing it Oct 21, 2025
@qkaiser
Copy link
Contributor Author

qkaiser commented Oct 21, 2025

Could you elaborate why the implementation of lt in the following models were needed?

I figured I might as well do it everywhere it was missing, regardless of whether it was already triggering a bug.

@qkaiser
Copy link
Contributor Author

qkaiser commented Oct 21, 2025

which JSON did you use?
Anyway, is there a place where ComponentEvidence is about to be used in a SortedSet?
I am unable to find it, could you help me?

I'll send a reduced test case, just a moment.

@qkaiser
Copy link
Contributor Author

qkaiser commented Oct 21, 2025

@jkowalleck you can run the repro-899.py script to trigger the exception when parsing bom-899.json
, or run the following code in the Python REPL using the attached bom.json.

from cyclonedx.model.bom import Bom
import json
from pathlib import Path
Bom.from_json(json.loads(Path("bom-899.json").read_text()))

This is what you should see:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/quentin/.cache/pypoetry/virtualenvs/cyclonedx-python-lib-cJUOMjqy-py3.10/lib/python3.10/site-packages/py_serializable/__init__.py", line 390, in from_json
    return cls(**_data)
  File "/home/quentin/cyclonedx-python-lib/cyclonedx/model/bom.py", line 353, in __init__
    self.components = components or []
  File "/home/quentin/cyclonedx-python-lib/cyclonedx/model/bom.py", line 432, in components
    self._components = SortedSet(components)
  File "/home/quentin/.cache/pypoetry/virtualenvs/cyclonedx-python-lib-cJUOMjqy-py3.10/lib/python3.10/site-packages/sortedcontainers/sortedset.py", line 168, in __init__
    self._update(iterable)
  File "/home/quentin/.cache/pypoetry/virtualenvs/cyclonedx-python-lib-cJUOMjqy-py3.10/lib/python3.10/site-packages/sortedcontainers/sortedset.py", line 687, in update
    _list.update(_set)
  File "/home/quentin/.cache/pypoetry/virtualenvs/cyclonedx-python-lib-cJUOMjqy-py3.10/lib/python3.10/site-packages/sortedcontainers/sortedlist.py", line 338, in update
    values = sorted(iterable)
  File "/home/quentin/cyclonedx-python-lib/cyclonedx/model/component.py", line 1677, in __lt__
    return self.__comparable_tuple() < other.__comparable_tuple()
  File "/home/quentin/cyclonedx-python-lib/cyclonedx/_internal/compare.py", line 45, in __lt__
    return bool(s < o)
TypeError: '<' not supported between instances of 'ComponentEvidence' and 'ComponentEvidence'

The SBOM was generated by https://github.com/CycloneDX/cdxgen, and the comparison seems to be triggered by the fact that two components with the same name and version but difference occurrences is present in the SBOM (jsonwebtoken@9.0.2).

@jkowalleck
Copy link
Member

tested deserializing the bom-899.json

did not see error message you posted. instead, i see

Traceback (most recent call last):
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 365, in from_json
    items.append(prop_info.concrete_type.from_json(data=j))
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 322, in from_json
    for k, v in data.items():
                ^^^^^^^^^^
AttributeError: 'str' object has no attribute 'items'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 357, in from_json
    _data[k] = prop_info.custom_type.json_denormalize(
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        v, prop_info=prop_info, ctx=klass)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../cyclonedx-python-lib/cyclonedx/model/component_evidence.py", line 771, in json_denormalize
    return ComponentEvidence.from_json(o)  # type:ignore[attr-defined]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 384, in from_json
    raise AttributeError(
        f'There was an AttributeError deserializing JSON to {cls} the Property {prop_info}: {e}'
    ) from e
AttributeError: There was an AttributeError deserializing JSON to <class 'cyclonedx.model.component_evidence.ComponentEvidence'> the Property <s.oml.SerializableProperty name=identity, custom_names={}, array=True, enum=False, optional=False, c_type=<class 'cyclonedx.model.component_evidence.Identity'>, type=typing.Set[cyclonedx.model.component_evidence.Identity], custom_type=None, xml_attr=False, xml_sequence=1>: 'str' object has no attribute 'items'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 365, in from_json
    items.append(prop_info.concrete_type.from_json(data=j))
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 384, in from_json
    raise AttributeError(
        f'There was an AttributeError deserializing JSON to {cls} the Property {prop_info}: {e}'
    ) from e
AttributeError: There was an AttributeError deserializing JSON to <class 'cyclonedx.model.component.Component'> the Property <s.oml.SerializableProperty name=evidence, custom_names={}, array=False, enum=False, optional=True, c_type=<class 'cyclonedx.model.component_evidence.ComponentEvidence'>, type=cyclonedx.model.component_evidence.ComponentEvidence | None, custom_type=<class 'cyclonedx.model.component_evidence._ComponentEvidenceSerializationHelper'>, xml_attr=False, xml_sequence=24>: There was an AttributeError deserializing JSON to <class 'cyclonedx.model.component_evidence.ComponentEvidence'> the Property <s.oml.SerializableProperty name=identity, custom_names={}, array=True, enum=False, optional=False, c_type=<class 'cyclonedx.model.component_evidence.Identity'>, type=typing.Set[cyclonedx.model.component_evidence.Identity], custom_type=None, xml_attr=False, xml_sequence=1>: 'str' object has no attribute 'items'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ".../cyclonedx-python-lib/_foo/t899.py", line 4, in <module>
    Bom.from_json(json.loads(Path('.../cyclonedx-python-lib/tests/_data/own/json/1.6/pr899.json').read_text()))
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../cyclonedx-python-lib/.venv/lib/python3.14/site-packages/py_serializable/__init__.py", line 384, in from_json
    raise AttributeError(
        f'There was an AttributeError deserializing JSON to {cls} the Property {prop_info}: {e}'
    ) from e
AttributeError: There was an AttributeError deserializing JSON to <class 'cyclonedx.model.bom.Bom'> the Property <s.oml.SerializableProperty name=components, custom_names={}, array=True, enum=False, optional=False, c_type=<class 'cyclonedx.model.component.Component'>, type=typing.Set[cyclonedx.model.component.Component], custom_type=None, xml_attr=False, xml_sequence=20>: There was an AttributeError deserializing JSON to <class 'cyclonedx.model.component.Component'> the Property <s.oml.SerializableProperty name=evidence, custom_names={}, array=False, enum=False, optional=True, c_type=<class 'cyclonedx.model.component_evidence.ComponentEvidence'>, type=cyclonedx.model.component_evidence.ComponentEvidence | None, custom_type=<class 'cyclonedx.model.component_evidence._ComponentEvidenceSerializationHelper'>, xml_attr=False, xml_sequence=24>: There was an AttributeError deserializing JSON to <class 'cyclonedx.model.component_evidence.ComponentEvidence'> the Property <s.oml.SerializableProperty name=identity, custom_names={}, array=True, enum=False, optional=False, c_type=<class 'cyclonedx.model.component_evidence.Identity'>, type=typing.Set[cyclonedx.model.component_evidence.Identity], custom_type=None, xml_attr=False, xml_sequence=1>: 'str' object has no attribute 'items'

the reason for the shown error will be solved with #900

anyway, i still see no prove why any of the changes in this PR are needed.

@qkaiser
Copy link
Contributor Author

qkaiser commented Oct 21, 2025

did not see error message you posted. instead, i see

yes, that's because that error is reached first. If you run the reduced test case in the branch from #900 you will see the error I'm mentionning. You need the fix in #900 to observe the bug in #899.

@jkowalleck
Copy link
Member

did not see error message you posted. instead, i see

yes, that's because that error is reached first. If you run the reduced test case in the branch from #900 you will see the error I'm mentionning. You need the fix in #900 to observe the bug in #899.

#900 was merged, and this branch was updated.
tested again. And finally found the reason why you've added the __lt__ to so many classes:
the __lt__ runs a comparison on potentially each child element - so better have the function implemented.

Understood. thanks for the report and the fix.

@jkowalleck jkowalleck changed the title feat: implement __lt__ for models still missing it fix: implement __lt__ for models still missing it Oct 22, 2025
@jkowalleck jkowalleck added the bug Something isn't working label Oct 22, 2025
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
@jkowalleck jkowalleck merged commit bebda4f into CycloneDX:main Oct 22, 2025
49 checks passed
@qkaiser
Copy link
Contributor Author

qkaiser commented Oct 22, 2025

Yay ! Thanks for being so quick with this @jkowalleck, highly appreciated.

@jkowalleck
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants