From f6430ecfd52a3d3e55349045d53a0f3ebc44cf3e Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 26 Nov 2025 10:23:21 +0100 Subject: [PATCH 01/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/README.md | 20 ++++++++++++++++++++ cyclonedx/contrib/__init__.py | 27 +++++++++++++++++++++++++++ tests/test_contrib/.gitkeep | 0 3 files changed, 47 insertions(+) create mode 100644 cyclonedx/contrib/README.md create mode 100644 cyclonedx/contrib/__init__.py create mode 100644 tests/test_contrib/.gitkeep diff --git a/cyclonedx/contrib/README.md b/cyclonedx/contrib/README.md new file mode 100644 index 00000000..aff5230e --- /dev/null +++ b/cyclonedx/contrib/README.md @@ -0,0 +1,20 @@ +# CycloneDX Contrib Extensions + +This directory contains community-contributed functionality that extends the capabilities of the CycloneDX core library. +Unlike the modules in `../`, these features are not part of the official CycloneDX specification and may vary in stability, scope, or compatibility. + +## Contents +- Utilities, helpers, and experimental features developed by the community +- Optional add-ons that may facilitate or enhance use of the CycloneDX core library +- Code that evolves independently of the CycloneDX specification + +## Notes +- Contrib modules are optional and not required for strict compliance with the CycloneDX standard. +- They may change more frequently than the core and are not guaranteed to follow the same versioning rules. +- Users should evaluate these modules carefully and consult documentation or source comments for details. + +## Contributing +Contributions are welcome. To add an extension: +1. Follow the contribution guidelines in the main repository. +2. Place your code in a clearly named subfolder or file under `contrib/`. +3. Provide documentation and tests to ensure clarity and maintainability. diff --git a/cyclonedx/contrib/__init__.py b/cyclonedx/contrib/__init__.py new file mode 100644 index 00000000..c23b645f --- /dev/null +++ b/cyclonedx/contrib/__init__.py @@ -0,0 +1,27 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + + +""" +Some features in this library are marked as contrib. These are community-provided extensions and are not part of the +official standard. They are optional and may evolve independently from the core. +""" + +__all__ = [ + # there is no intention to export anything in here. + # +] diff --git a/tests/test_contrib/.gitkeep b/tests/test_contrib/.gitkeep new file mode 100644 index 00000000..e69de29b From a8a4190f0fb362cd04753038fb4e301d6a1c4e76 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 27 Nov 2025 13:48:48 +0100 Subject: [PATCH 02/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/builder/this.py | 81 +++++++++---------------------- cyclonedx/contrib/__init__.py | 1 - cyclonedx/contrib/license.py | 90 +++++++++++++++++++++++++++++++++++ cyclonedx/contrib/this.py | 84 ++++++++++++++++++++++++++++++++ cyclonedx/factory/license.py | 77 +++++------------------------- 5 files changed, 209 insertions(+), 124 deletions(-) create mode 100644 cyclonedx/contrib/license.py create mode 100644 cyclonedx/contrib/this.py diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 8f81a8ff..988d13a3 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -17,67 +17,32 @@ """Representation of this very python library.""" -__all__ = ['this_component', 'this_tool', ] +__all__ = [] -from .. import __version__ as __ThisVersion # noqa: N812 -from ..model import ExternalReference, ExternalReferenceType, XsUri -from ..model.component import Component, ComponentType -from ..model.license import DisjunctiveLicense, LicenseAcknowledgement -from ..model.tool import Tool +from ..contrib.this import this_tool as _this_tool, this_component as _this_component -# !!! keep this file in sync with `pyproject.toml` +# endregion deprecated re-export +this_component = _this_component +""" +Alias of :class:`_this_component`. -def this_component() -> Component: - """Representation of this very python library as a :class:`Component`.""" - return Component( - type=ComponentType.LIBRARY, - group='CycloneDX', - name='cyclonedx-python-lib', - version=__ThisVersion or 'UNKNOWN', - description='Python library for CycloneDX', - licenses=(DisjunctiveLicense(id='Apache-2.0', - acknowledgement=LicenseAcknowledgement.DECLARED),), - external_references=( - # let's assume this is not a fork - ExternalReference( - type=ExternalReferenceType.WEBSITE, - url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/#readme') - ), - ExternalReference( - type=ExternalReferenceType.DOCUMENTATION, - url=XsUri('https://cyclonedx-python-library.readthedocs.io/') - ), - ExternalReference( - type=ExternalReferenceType.VCS, - url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib') - ), - ExternalReference( - type=ExternalReferenceType.BUILD_SYSTEM, - url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/actions') - ), - ExternalReference( - type=ExternalReferenceType.ISSUE_TRACKER, - url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/issues') - ), - ExternalReference( - type=ExternalReferenceType.LICENSE, - url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE') - ), - ExternalReference( - type=ExternalReferenceType.RELEASE_NOTES, - url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md') - ), - # we cannot assert where the lib was fetched from, but we can give a hint - ExternalReference( - type=ExternalReferenceType.DISTRIBUTION, - url=XsUri('https://pypi.org/project/cyclonedx-python-lib/') - ), - ), - # to be extended... - ) +This re-export location is deprecated. +Use ``from ...contrib.this import this_component`` instead. +The exported symbol itself is NOT deprecated - only this import path. +.. deprecated:: next +""" -def this_tool() -> Tool: - """Representation of this very python library as a :class:`Tool`.""" - return Tool.from_component(this_component()) +this_tool = _this_tool +""" +Alias of :class:`_this_tool`. + +This re-export location is deprecated. +Use ``from ...contrib.this import this_tool`` instead. +The exported symbol itself is NOT deprecated - only this import path. + +.. deprecated:: next +""" + +# endregion deprecated re-export diff --git a/cyclonedx/contrib/__init__.py b/cyclonedx/contrib/__init__.py index c23b645f..d80b3a34 100644 --- a/cyclonedx/contrib/__init__.py +++ b/cyclonedx/contrib/__init__.py @@ -23,5 +23,4 @@ __all__ = [ # there is no intention to export anything in here. - # ] diff --git a/cyclonedx/contrib/license.py b/cyclonedx/contrib/license.py new file mode 100644 index 00000000..f9be5820 --- /dev/null +++ b/cyclonedx/contrib/license.py @@ -0,0 +1,90 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +__all__ = ['LicenseFactory'] + +from typing import TYPE_CHECKING, Optional + +from ..exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException +from ..model.license import DisjunctiveLicense, LicenseExpression +from ..spdx import fixup_id as spdx_fixup, is_expression as is_spdx_expression + +if TYPE_CHECKING: # pragma: no cover + from ..model import AttachedText, XsUri + from ..model.license import License, LicenseAcknowledgement + + +class LicenseFactory: + """Factory for :class:`cyclonedx.model.license.License`.""" + + def make_from_string(self, value: str, *, + license_text: Optional['AttachedText'] = None, + license_url: Optional['XsUri'] = None, + license_acknowledgement: Optional['LicenseAcknowledgement'] = None + ) -> 'License': + """Make a :class:`cyclonedx.model.license.License` from a string.""" + try: + return self.make_with_id(value, + text=license_text, + url=license_url, + acknowledgement=license_acknowledgement) + except InvalidSpdxLicenseException: + pass + try: + return self.make_with_expression(value, + acknowledgement=license_acknowledgement) + except InvalidLicenseExpressionException: + pass + return self.make_with_name(value, + text=license_text, + url=license_url, + acknowledgement=license_acknowledgement) + + def make_with_expression(self, expression: str, *, + acknowledgement: Optional['LicenseAcknowledgement'] = None + ) -> LicenseExpression: + """Make a :class:`cyclonedx.model.license.LicenseExpression` with a compound expression. + + Utilizes :func:`cyclonedx.spdx.is_expression`. + + :raises InvalidLicenseExpressionException: if param `value` is not known/supported license expression + """ + if is_spdx_expression(expression): + return LicenseExpression(expression, acknowledgement=acknowledgement) + raise InvalidLicenseExpressionException(expression) + + def make_with_id(self, spdx_id: str, *, + text: Optional['AttachedText'] = None, + url: Optional['XsUri'] = None, + acknowledgement: Optional['LicenseAcknowledgement'] = None + ) -> DisjunctiveLicense: + """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` from an SPDX-ID. + + :raises InvalidSpdxLicenseException: if param `spdx_id` was not known/supported SPDX-ID + """ + spdx_license_id = spdx_fixup(spdx_id) + if spdx_license_id is None: + raise InvalidSpdxLicenseException(spdx_id) + return DisjunctiveLicense(id=spdx_license_id, text=text, url=url, acknowledgement=acknowledgement) + + def make_with_name(self, name: str, *, + text: Optional['AttachedText'] = None, + url: Optional['XsUri'] = None, + acknowledgement: Optional['LicenseAcknowledgement'] = None + ) -> DisjunctiveLicense: + """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` with a name.""" + return DisjunctiveLicense(name=name, text=text, url=url, acknowledgement=acknowledgement) diff --git a/cyclonedx/contrib/this.py b/cyclonedx/contrib/this.py new file mode 100644 index 00000000..e37b6dac --- /dev/null +++ b/cyclonedx/contrib/this.py @@ -0,0 +1,84 @@ + +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""Representation of this very python library.""" + +__all__ = ['this_component', 'this_tool', ] + +from .. import __version__ as __ThisVersion # noqa: N812 +from ..model import ExternalReference, ExternalReferenceType, XsUri +from ..model.component import Component, ComponentType +from ..model.license import DisjunctiveLicense, LicenseAcknowledgement +from ..model.tool import Tool + +# !!! keep this file in sync with `pyproject.toml` + + +def this_component() -> Component: + """Representation of this very python library as a :class:`Component`.""" + return Component( + type=ComponentType.LIBRARY, + group='CycloneDX', + name='cyclonedx-python-lib', + version=__ThisVersion or 'UNKNOWN', + description='Python library for CycloneDX', + licenses=(DisjunctiveLicense(id='Apache-2.0', + acknowledgement=LicenseAcknowledgement.DECLARED),), + external_references=( + # let's assume this is not a fork + ExternalReference( + type=ExternalReferenceType.WEBSITE, + url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/#readme') + ), + ExternalReference( + type=ExternalReferenceType.DOCUMENTATION, + url=XsUri('https://cyclonedx-python-library.readthedocs.io/') + ), + ExternalReference( + type=ExternalReferenceType.VCS, + url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib') + ), + ExternalReference( + type=ExternalReferenceType.BUILD_SYSTEM, + url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/actions') + ), + ExternalReference( + type=ExternalReferenceType.ISSUE_TRACKER, + url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/issues') + ), + ExternalReference( + type=ExternalReferenceType.LICENSE, + url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE') + ), + ExternalReference( + type=ExternalReferenceType.RELEASE_NOTES, + url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md') + ), + # we cannot assert where the lib was fetched from, but we can give a hint + ExternalReference( + type=ExternalReferenceType.DISTRIBUTION, + url=XsUri('https://pypi.org/project/cyclonedx-python-lib/') + ), + ), + # to be extended... + ) + + +def this_tool() -> Tool: + """Representation of this very python library as a :class:`Tool`.""" + return Tool.from_component(this_component()) diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 40d4484d..6ecd0f9c 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -15,74 +15,21 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import TYPE_CHECKING, Optional +__all__ = [] -from ..exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException -from ..model.license import DisjunctiveLicense, LicenseExpression -from ..spdx import fixup_id as spdx_fixup, is_expression as is_spdx_expression +from ..contrib.license import LicenseFactory as _LicenseFactory -if TYPE_CHECKING: # pragma: no cover - from ..model import AttachedText, XsUri - from ..model.license import License, LicenseAcknowledgement +# region deprecated re-export +LicenseFactory = _LicenseFactory +""" +Alias of :class:`_LicenseFactory`. -class LicenseFactory: - """Factory for :class:`cyclonedx.model.license.License`.""" +This re-export location is deprecated. +Use ``from ...contrib.license import LicenseFactory`` instead. +The exported symbol itself is NOT deprecated - only this import path. - def make_from_string(self, value: str, *, - license_text: Optional['AttachedText'] = None, - license_url: Optional['XsUri'] = None, - license_acknowledgement: Optional['LicenseAcknowledgement'] = None - ) -> 'License': - """Make a :class:`cyclonedx.model.license.License` from a string.""" - try: - return self.make_with_id(value, - text=license_text, - url=license_url, - acknowledgement=license_acknowledgement) - except InvalidSpdxLicenseException: - pass - try: - return self.make_with_expression(value, - acknowledgement=license_acknowledgement) - except InvalidLicenseExpressionException: - pass - return self.make_with_name(value, - text=license_text, - url=license_url, - acknowledgement=license_acknowledgement) +.. deprecated:: next +""" - def make_with_expression(self, expression: str, *, - acknowledgement: Optional['LicenseAcknowledgement'] = None - ) -> LicenseExpression: - """Make a :class:`cyclonedx.model.license.LicenseExpression` with a compound expression. - - Utilizes :func:`cyclonedx.spdx.is_expression`. - - :raises InvalidLicenseExpressionException: if param `value` is not known/supported license expression - """ - if is_spdx_expression(expression): - return LicenseExpression(expression, acknowledgement=acknowledgement) - raise InvalidLicenseExpressionException(expression) - - def make_with_id(self, spdx_id: str, *, - text: Optional['AttachedText'] = None, - url: Optional['XsUri'] = None, - acknowledgement: Optional['LicenseAcknowledgement'] = None - ) -> DisjunctiveLicense: - """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` from an SPDX-ID. - - :raises InvalidSpdxLicenseException: if param `spdx_id` was not known/supported SPDX-ID - """ - spdx_license_id = spdx_fixup(spdx_id) - if spdx_license_id is None: - raise InvalidSpdxLicenseException(spdx_id) - return DisjunctiveLicense(id=spdx_license_id, text=text, url=url, acknowledgement=acknowledgement) - - def make_with_name(self, name: str, *, - text: Optional['AttachedText'] = None, - url: Optional['XsUri'] = None, - acknowledgement: Optional['LicenseAcknowledgement'] = None - ) -> DisjunctiveLicense: - """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` with a name.""" - return DisjunctiveLicense(name=name, text=text, url=url, acknowledgement=acknowledgement) +# endregion deprecated re-export From 0e68f6e75bc5768dc432691984ee879e9191ec78 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 27 Nov 2025 13:49:28 +0100 Subject: [PATCH 03/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/builder/this.py | 2 -- cyclonedx/factory/license.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 988d13a3..34f03969 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -17,8 +17,6 @@ """Representation of this very python library.""" -__all__ = [] - from ..contrib.this import this_tool as _this_tool, this_component as _this_component # endregion deprecated re-export diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 6ecd0f9c..e5f48ba2 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -15,8 +15,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -__all__ = [] - from ..contrib.license import LicenseFactory as _LicenseFactory # region deprecated re-export From 3c2ad7ddb8b5a6f61213edceab090945838d62ea Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 10:14:51 +0100 Subject: [PATCH 04/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/builder/this.py | 4 ++-- cyclonedx/factory/license.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 34f03969..7b51b3ed 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -23,7 +23,7 @@ this_component = _this_component """ -Alias of :class:`_this_component`. +Alias of :func:`cyclonedx..contrib.this.this_component`. This re-export location is deprecated. Use ``from ...contrib.this import this_component`` instead. @@ -34,7 +34,7 @@ this_tool = _this_tool """ -Alias of :class:`_this_tool`. +Alias of :func:`cyclonedx..contrib.this.this_tool`. This re-export location is deprecated. Use ``from ...contrib.this import this_tool`` instead. diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index e5f48ba2..8b05a953 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -21,7 +21,7 @@ LicenseFactory = _LicenseFactory """ -Alias of :class:`_LicenseFactory`. +Alias of :class:`cyclonedx.contrib.license.LicenseFactory`. This re-export location is deprecated. Use ``from ...contrib.license import LicenseFactory`` instead. From 3947eb7a7fa34fcdb8b08c338b40e096624ba211 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 11:17:10 +0100 Subject: [PATCH 05/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/__test.py | 5 +++++ cyclonedx/builder/this.py | 24 +++++++++++------------- cyclonedx/contrib/this.py | 1 - cyclonedx/factory/license.py | 12 ++++++------ 4 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 cyclonedx/__test.py diff --git a/cyclonedx/__test.py b/cyclonedx/__test.py new file mode 100644 index 00000000..58778fa0 --- /dev/null +++ b/cyclonedx/__test.py @@ -0,0 +1,5 @@ +from .builder.this import this_tool as o +from .contrib.this import this_tool as n + +a = o() +b = n() diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 7b51b3ed..565c683f 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -17,30 +17,28 @@ """Representation of this very python library.""" -from ..contrib.this import this_tool as _this_tool, this_component as _this_component +__all__ = ['this_component', 'this_tool', ] + +from ..contrib.this import this_component as _this_component, this_tool as _this_tool # endregion deprecated re-export this_component = _this_component -""" -Alias of :func:`cyclonedx..contrib.this.this_component`. - -This re-export location is deprecated. -Use ``from ...contrib.this import this_component`` instead. -The exported symbol itself is NOT deprecated - only this import path. +"""Deprecated — Alias of :func:`cyclonedx.contrib.this.this_component`. .. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.this import this_component`` instead. + The exported symbol itself is NOT deprecated - only this import path. """ this_tool = _this_tool -""" -Alias of :func:`cyclonedx..contrib.this.this_tool`. - -This re-export location is deprecated. -Use ``from ...contrib.this import this_tool`` instead. -The exported symbol itself is NOT deprecated - only this import path. +"""Deprecated — Alias of :func:`cyclonedx.contrib.this.this_tool`. .. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.this import this_tool`` instead. + The exported symbol itself is NOT deprecated - only this import path. """ # endregion deprecated re-export diff --git a/cyclonedx/contrib/this.py b/cyclonedx/contrib/this.py index e37b6dac..8f81a8ff 100644 --- a/cyclonedx/contrib/this.py +++ b/cyclonedx/contrib/this.py @@ -1,4 +1,3 @@ - # This file is part of CycloneDX Python Library # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 8b05a953..fdf65ac4 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -15,19 +15,19 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +__all__ = ['LicenseFactory'] + from ..contrib.license import LicenseFactory as _LicenseFactory # region deprecated re-export LicenseFactory = _LicenseFactory -""" -Alias of :class:`cyclonedx.contrib.license.LicenseFactory`. - -This re-export location is deprecated. -Use ``from ...contrib.license import LicenseFactory`` instead. -The exported symbol itself is NOT deprecated - only this import path. +"""Deprecated — Alias of :class:`cyclonedx.contrib.license.LicenseFactory`. .. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license import LicenseFactory`` instead. + The exported symbol itself is NOT deprecated - only this import path. """ # endregion deprecated re-export From 885d47e9b06a7c69007a731781aa369dcc28cdab Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 14:16:57 +0100 Subject: [PATCH 06/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/__test.py | 5 ---- cyclonedx/builder/this.py | 50 ++++++++++++++++++++++++------------ cyclonedx/factory/license.py | 23 ++++++++++++----- 3 files changed, 50 insertions(+), 28 deletions(-) delete mode 100644 cyclonedx/__test.py diff --git a/cyclonedx/__test.py b/cyclonedx/__test.py deleted file mode 100644 index 58778fa0..00000000 --- a/cyclonedx/__test.py +++ /dev/null @@ -1,5 +0,0 @@ -from .builder.this import this_tool as o -from .contrib.this import this_tool as n - -a = o() -b = n() diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 565c683f..24338ba6 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -17,28 +17,46 @@ """Representation of this very python library.""" -__all__ = ['this_component', 'this_tool', ] +__all__ = ['this_component', 'this_tool'] + +import sys +from typing import TYPE_CHECKING + +if sys.version_info >= (3, 13): + from warnings import deprecated +else: + from typing_extensions import deprecated from ..contrib.this import this_component as _this_component, this_tool as _this_tool -# endregion deprecated re-export +# region deprecated re-export + +if TYPE_CHECKING: + from ..model.component import Component + from ..model.tool import Tool + + +@deprecated('Deprecated re-export location - see docstring of "this_component" for details.') +def this_component() -> 'Component': + """Deprecated — alias of :func:`~cyclonedx.contrib.this.this_component`. -this_component = _this_component -"""Deprecated — Alias of :func:`cyclonedx.contrib.this.this_component`. + .. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.this import this_component`` instead. + The exported symbol itself is NOT deprecated — only this import path. + """ + return _this_component() -.. deprecated:: next - This re-export location is deprecated. - Use ``from cyclonedx.contrib.this import this_component`` instead. - The exported symbol itself is NOT deprecated - only this import path. -""" -this_tool = _this_tool -"""Deprecated — Alias of :func:`cyclonedx.contrib.this.this_tool`. +@deprecated('Deprecated re-export location - see docstring of "this_tool" for details.') +def this_tool() -> 'Tool': + """Deprecated — alias of :func:`~cyclonedx.contrib.this.this_tool`. -.. deprecated:: next - This re-export location is deprecated. - Use ``from cyclonedx.contrib.this import this_tool`` instead. - The exported symbol itself is NOT deprecated - only this import path. -""" + .. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.this import this_tool`` instead. + The exported symbol itself is NOT deprecated — only this import path. + """ + return _this_tool() # endregion deprecated re-export diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index fdf65ac4..915a81b5 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -17,17 +17,26 @@ __all__ = ['LicenseFactory'] +import sys + +if sys.version_info >= (3, 13): + from warnings import deprecated +else: + from typing_extensions import deprecated + from ..contrib.license import LicenseFactory as _LicenseFactory # region deprecated re-export -LicenseFactory = _LicenseFactory -"""Deprecated — Alias of :class:`cyclonedx.contrib.license.LicenseFactory`. -.. deprecated:: next - This re-export location is deprecated. - Use ``from cyclonedx.contrib.license import LicenseFactory`` instead. - The exported symbol itself is NOT deprecated - only this import path. -""" +@deprecated('Deprecated re-export location - see docstring of "LicenseFactory" for details.') +class LicenseFactory(_LicenseFactory): + """Deprecated — Alias of :class:`cyclonedx.contrib.license.LicenseFactory`. + + .. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license import LicenseFactory`` instead. + The exported symbol itself is NOT deprecated - only this import path. + """ # endregion deprecated re-export From bd37a9eb1eabe8df88eb604e08d0ef23d1bbe843 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 14:26:15 +0100 Subject: [PATCH 07/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/builder/this.py | 10 +++++----- cyclonedx/contrib/license/__init__.py | 16 ++++++++++++++++ .../contrib/{license.py => license/factories.py} | 10 +++++----- cyclonedx/contrib/this/__init__.py | 16 ++++++++++++++++ cyclonedx/contrib/{this.py => this/builders.py} | 10 +++++----- cyclonedx/factory/license.py | 6 +++--- 6 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 cyclonedx/contrib/license/__init__.py rename cyclonedx/contrib/{license.py => license/factories.py} (91%) create mode 100644 cyclonedx/contrib/this/__init__.py rename cyclonedx/contrib/{this.py => this/builders.py} (91%) diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 24338ba6..991d9f04 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -27,7 +27,7 @@ else: from typing_extensions import deprecated -from ..contrib.this import this_component as _this_component, this_tool as _this_tool +from ..contrib.this.builders import this_component as _this_component, this_tool as _this_tool # region deprecated re-export @@ -38,11 +38,11 @@ @deprecated('Deprecated re-export location - see docstring of "this_component" for details.') def this_component() -> 'Component': - """Deprecated — alias of :func:`~cyclonedx.contrib.this.this_component`. + """Deprecated — Alias of :func:`cyclonedx.contrib.this.builders.this_component`. .. deprecated:: next This re-export location is deprecated. - Use ``from cyclonedx.contrib.this import this_component`` instead. + Use ``from cyclonedx.contrib.this.builders import this_component`` instead. The exported symbol itself is NOT deprecated — only this import path. """ return _this_component() @@ -50,11 +50,11 @@ def this_component() -> 'Component': @deprecated('Deprecated re-export location - see docstring of "this_tool" for details.') def this_tool() -> 'Tool': - """Deprecated — alias of :func:`~cyclonedx.contrib.this.this_tool`. + """Deprecated — Alias of :func:`cyclonedx.contrib.this.builders.this_tool`. .. deprecated:: next This re-export location is deprecated. - Use ``from cyclonedx.contrib.this import this_tool`` instead. + Use ``from cyclonedx.contrib.this.builders import this_tool`` instead. The exported symbol itself is NOT deprecated — only this import path. """ return _this_tool() diff --git a/cyclonedx/contrib/license/__init__.py b/cyclonedx/contrib/license/__init__.py new file mode 100644 index 00000000..671a2188 --- /dev/null +++ b/cyclonedx/contrib/license/__init__.py @@ -0,0 +1,16 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. diff --git a/cyclonedx/contrib/license.py b/cyclonedx/contrib/license/factories.py similarity index 91% rename from cyclonedx/contrib/license.py rename to cyclonedx/contrib/license/factories.py index f9be5820..df989490 100644 --- a/cyclonedx/contrib/license.py +++ b/cyclonedx/contrib/license/factories.py @@ -19,13 +19,13 @@ from typing import TYPE_CHECKING, Optional -from ..exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException -from ..model.license import DisjunctiveLicense, LicenseExpression -from ..spdx import fixup_id as spdx_fixup, is_expression as is_spdx_expression +from ...exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException +from ...model.license import DisjunctiveLicense, LicenseExpression +from ...spdx import fixup_id as spdx_fixup, is_expression as is_spdx_expression if TYPE_CHECKING: # pragma: no cover - from ..model import AttachedText, XsUri - from ..model.license import License, LicenseAcknowledgement + from ...model import AttachedText, XsUri + from ...model.license import License, LicenseAcknowledgement class LicenseFactory: diff --git a/cyclonedx/contrib/this/__init__.py b/cyclonedx/contrib/this/__init__.py new file mode 100644 index 00000000..671a2188 --- /dev/null +++ b/cyclonedx/contrib/this/__init__.py @@ -0,0 +1,16 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. diff --git a/cyclonedx/contrib/this.py b/cyclonedx/contrib/this/builders.py similarity index 91% rename from cyclonedx/contrib/this.py rename to cyclonedx/contrib/this/builders.py index 8f81a8ff..37f0159f 100644 --- a/cyclonedx/contrib/this.py +++ b/cyclonedx/contrib/this/builders.py @@ -19,11 +19,11 @@ __all__ = ['this_component', 'this_tool', ] -from .. import __version__ as __ThisVersion # noqa: N812 -from ..model import ExternalReference, ExternalReferenceType, XsUri -from ..model.component import Component, ComponentType -from ..model.license import DisjunctiveLicense, LicenseAcknowledgement -from ..model.tool import Tool +from ... import __version__ as __ThisVersion # noqa: N812 +from ...model import ExternalReference, ExternalReferenceType, XsUri +from ...model.component import Component, ComponentType +from ...model.license import DisjunctiveLicense, LicenseAcknowledgement +from ...model.tool import Tool # !!! keep this file in sync with `pyproject.toml` diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 915a81b5..d394abec 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -24,18 +24,18 @@ else: from typing_extensions import deprecated -from ..contrib.license import LicenseFactory as _LicenseFactory +from ..contrib.license.factories import LicenseFactory as _LicenseFactory # region deprecated re-export @deprecated('Deprecated re-export location - see docstring of "LicenseFactory" for details.') class LicenseFactory(_LicenseFactory): - """Deprecated — Alias of :class:`cyclonedx.contrib.license.LicenseFactory`. + """Deprecated — Alias of :class:`cyclonedx.contrib.license.factories.LicenseFactory`. .. deprecated:: next This re-export location is deprecated. - Use ``from cyclonedx.contrib.license import LicenseFactory`` instead. + Use ``from cyclonedx.contrib.license.factories import LicenseFactory`` instead. The exported symbol itself is NOT deprecated - only this import path. """ From 119690d7d70c6e04c3209a994b1f85544473e139 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 14:48:51 +0100 Subject: [PATCH 08/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- tests/test_deserialize_json.py | 2 +- tests/test_deserialize_xml.py | 2 +- tests/test_factory_license.py | 20 ++++++++++---------- tests/test_output_json.py | 2 +- tests/test_output_xml.py | 2 +- tests/test_real_world_examples.py | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_deserialize_json.py b/tests/test_deserialize_json.py index 14fcb953..85f8b11a 100644 --- a/tests/test_deserialize_json.py +++ b/tests/test_deserialize_json.py @@ -43,7 +43,7 @@ class TestDeserializeJson(TestCase, SnapshotMixin, DeepCompareMixin): @named_data(*all_get_bom_funct_valid_immut, *all_get_bom_funct_valid_reversible_migrate) - @patch('cyclonedx.builder.this.__ThisVersion', 'TESTING') + @patch('cyclonedx.contrib.this.builders.__ThisVersion', 'TESTING') def test_prepared(self, get_bom: Callable[[], Bom], *_: Any, **__: Any) -> None: # only latest schema will have all data populated in serialized form snapshot_name = mksname(get_bom, _LATEST_SCHEMA, OutputFormat.JSON) diff --git a/tests/test_deserialize_xml.py b/tests/test_deserialize_xml.py index 12e32e5f..8ad0f31c 100644 --- a/tests/test_deserialize_xml.py +++ b/tests/test_deserialize_xml.py @@ -40,7 +40,7 @@ class TestDeserializeXml(TestCase, SnapshotMixin, DeepCompareMixin): @named_data(*all_get_bom_funct_valid_immut, *all_get_bom_funct_valid_reversible_migrate) - @patch('cyclonedx.builder.this.__ThisVersion', 'TESTING') + @patch('cyclonedx.contrib.this.builders.__ThisVersion', 'TESTING') def test_prepared(self, get_bom: Callable[[], Bom], *_: Any, **__: Any) -> None: # only latest schema will have all data populated in serialized form snapshot_name = mksname(get_bom, _LATEST_SCHEMA, OutputFormat.XML) diff --git a/tests/test_factory_license.py b/tests/test_factory_license.py index 051a88b3..d381347b 100644 --- a/tests/test_factory_license.py +++ b/tests/test_factory_license.py @@ -32,8 +32,8 @@ def test_make_from_string_with_id(self) -> None: acknowledgement = unittest.mock.NonCallableMock(spec=LicenseAcknowledgement) expected = DisjunctiveLicense(id='bar', text=text, url=url, acknowledgement=acknowledgement) - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'), \ - unittest.mock.patch('cyclonedx.factory.license.is_spdx_expression', return_value=True): + with unittest.mock.patch('cyclonedx.contrib.license.factories.spdx_fixup', return_value='bar'), \ + unittest.mock.patch('cyclonedx.contrib.license.factories.is_spdx_expression', return_value=True): actual = LicenseFactory().make_from_string('foo', license_text=text, license_url=url, @@ -47,8 +47,8 @@ def test_make_from_string_with_name(self) -> None: acknowledgement = unittest.mock.NonCallableMock(spec=LicenseAcknowledgement) expected = DisjunctiveLicense(name='foo', text=text, url=url, acknowledgement=acknowledgement) - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None), \ - unittest.mock.patch('cyclonedx.factory.license.is_spdx_expression', return_value=False): + with unittest.mock.patch('cyclonedx.contrib.license.factories.spdx_fixup', return_value=None), \ + unittest.mock.patch('cyclonedx.contrib.license.factories.is_spdx_expression', return_value=False): actual = LicenseFactory().make_from_string('foo', license_text=text, license_url=url, @@ -60,8 +60,8 @@ def test_make_from_string_with_expression(self) -> None: acknowledgement = unittest.mock.NonCallableMock(spec=LicenseAcknowledgement) expected = LicenseExpression('foo', acknowledgement=acknowledgement) - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None), \ - unittest.mock.patch('cyclonedx.factory.license.is_spdx_expression', return_value=True): + with unittest.mock.patch('cyclonedx.contrib.license.factories.spdx_fixup', return_value=None), \ + unittest.mock.patch('cyclonedx.contrib.license.factories.is_spdx_expression', return_value=True): actual = LicenseFactory().make_from_string('foo', license_acknowledgement=acknowledgement) @@ -73,14 +73,14 @@ def test_make_with_id(self) -> None: acknowledgement = unittest.mock.NonCallableMock(spec=LicenseAcknowledgement) expected = DisjunctiveLicense(id='bar', text=text, url=url, acknowledgement=acknowledgement) - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'): + with unittest.mock.patch('cyclonedx.contrib.license.factories.spdx_fixup', return_value='bar'): actual = LicenseFactory().make_with_id(spdx_id='foo', text=text, url=url, acknowledgement=acknowledgement) self.assertEqual(expected, actual) def test_make_with_id_raises(self) -> None: with self.assertRaises(InvalidSpdxLicenseException, msg='foo'): - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None): + with unittest.mock.patch('cyclonedx.contrib.license.factories.spdx_fixup', return_value=None): LicenseFactory().make_with_id(spdx_id='foo') def test_make_with_name(self) -> None: @@ -94,11 +94,11 @@ def test_make_with_name(self) -> None: def test_make_with_expression(self) -> None: acknowledgement = unittest.mock.NonCallableMock(spec=LicenseAcknowledgement) expected = LicenseExpression('foo', acknowledgement=acknowledgement) - with unittest.mock.patch('cyclonedx.factory.license.is_spdx_expression', return_value=True): + with unittest.mock.patch('cyclonedx.contrib.license.factories.is_spdx_expression', return_value=True): actual = LicenseFactory().make_with_expression(expression='foo', acknowledgement=acknowledgement) self.assertEqual(expected, actual) def test_make_with_expression_raises(self) -> None: with self.assertRaises(InvalidLicenseExpressionException, msg='foo'): - with unittest.mock.patch('cyclonedx.factory.license.is_spdx_expression', return_value=False): + with unittest.mock.patch('cyclonedx.contrib.license.factories.is_spdx_expression', return_value=False): LicenseFactory().make_with_expression('foo') diff --git a/tests/test_output_json.py b/tests/test_output_json.py index 0bc3f1e2..b9340a4e 100644 --- a/tests/test_output_json.py +++ b/tests/test_output_json.py @@ -62,7 +62,7 @@ def test_unsupported_schema_raises(self, sv: SchemaVersion) -> None: and is_valid_for_schema_version(gb, sv) )) @unpack - @patch('cyclonedx.builder.this.__ThisVersion', 'TESTING') + @patch('cyclonedx.contrib.this.builders.__ThisVersion', 'TESTING') def test_valid(self, get_bom: Callable[[], Bom], sv: SchemaVersion, *_: Any, **__: Any) -> None: snapshot_name = mksname(get_bom, sv, OutputFormat.JSON) bom = get_bom() diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index 2b1a16ee..6e887ded 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -49,7 +49,7 @@ class TestOutputXml(TestCase, SnapshotMixin): if is_valid_for_schema_version(gb, sv) )) @unpack - @patch('cyclonedx.builder.this.__ThisVersion', 'TESTING') + @patch('cyclonedx.contrib.this.builders.__ThisVersion', 'TESTING') def test_valid(self, get_bom: Callable[[], Bom], sv: SchemaVersion, *_: Any, **__: Any) -> None: snapshot_name = mksname(get_bom, sv, OutputFormat.XML) if snapshot_name is None: diff --git a/tests/test_real_world_examples.py b/tests/test_real_world_examples.py index 3170e730..232e3291 100644 --- a/tests/test_real_world_examples.py +++ b/tests/test_real_world_examples.py @@ -26,7 +26,7 @@ from tests import OWN_DATA_DIRECTORY -@patch('cyclonedx.builder.this.__ThisVersion', 'TESTING') +@patch('cyclonedx.contrib.this.builders.__ThisVersion', 'TESTING') @patch('cyclonedx.model.bom._get_now_utc', return_value=datetime.fromisoformat('2023-01-07 13:44:32.312678+00:00')) class TestDeserializeRealWorldExamples(unittest.TestCase): From 562049cbea36c27b36c2195b70254b1918e9acbc Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 15:55:17 +0100 Subject: [PATCH 09/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/license/exceptions.py | 61 +++++++++++++++++++ cyclonedx/contrib/license/factories.py | 2 +- cyclonedx/exception/factory.py | 78 ++++++++++++++++--------- cyclonedx/factory/license.py | 1 + 4 files changed, 115 insertions(+), 27 deletions(-) create mode 100644 cyclonedx/contrib/license/exceptions.py diff --git a/cyclonedx/contrib/license/exceptions.py b/cyclonedx/contrib/license/exceptions.py new file mode 100644 index 00000000..4a484a50 --- /dev/null +++ b/cyclonedx/contrib/license/exceptions.py @@ -0,0 +1,61 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + + +""" +Exceptions relating to specific conditions that occur when factoring a model. +""" + +from ...exception import CycloneDxException + +__all__ = ['FactoryException', 'LicenseChoiceFactoryException', 'InvalidSpdxLicenseException', + 'LicenseFactoryException', 'InvalidLicenseExpressionException'] + + +class FactoryException(CycloneDxException): + """ + Base exception that covers all exceptions that may be thrown during model factoring. + """ + pass + + +class LicenseChoiceFactoryException(FactoryException): + """ + Base exception that covers all LicenseChoiceFactory exceptions. + """ + pass + + +class InvalidSpdxLicenseException(LicenseChoiceFactoryException): + """ + Thrown when an invalid SPDX License is provided. + """ + pass + + +class LicenseFactoryException(FactoryException): + """ + Base exception that covers all LicenseFactory exceptions. + """ + pass + + +class InvalidLicenseExpressionException(LicenseFactoryException): + """ + Thrown when an invalid License expressions is provided. + """ + pass diff --git a/cyclonedx/contrib/license/factories.py b/cyclonedx/contrib/license/factories.py index df989490..a74ff6b0 100644 --- a/cyclonedx/contrib/license/factories.py +++ b/cyclonedx/contrib/license/factories.py @@ -19,9 +19,9 @@ from typing import TYPE_CHECKING, Optional -from ...exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException from ...model.license import DisjunctiveLicense, LicenseExpression from ...spdx import fixup_id as spdx_fixup, is_expression as is_spdx_expression +from .exceptions import InvalidLicenseExpressionException, InvalidSpdxLicenseException if TYPE_CHECKING: # pragma: no cover from ...model import AttachedText, XsUri diff --git a/cyclonedx/exception/factory.py b/cyclonedx/exception/factory.py index 2ddbb327..6ca1d3af 100644 --- a/cyclonedx/exception/factory.py +++ b/cyclonedx/exception/factory.py @@ -20,39 +20,65 @@ Exceptions relating to specific conditions that occur when factoring a model. """ -from . import CycloneDxException +__all__ = ['CycloneDxFactoryException', 'LicenseChoiceFactoryException', + 'InvalidSpdxLicenseException', 'LicenseFactoryException', 'InvalidLicenseExpressionException'] +from ..contrib.license.exceptions import ( + FactoryException as _FactoryException, + InvalidLicenseExpressionException as _InvalidLicenseExpressionException, + InvalidSpdxLicenseException as _InvalidSpdxLicenseException, + LicenseChoiceFactoryException as _LicenseChoiceFactoryException, + LicenseFactoryException as _LicenseFactoryException, +) -class CycloneDxFactoryException(CycloneDxException): - """ - Base exception that covers all exceptions that may be thrown during model factoring.. - """ - pass +# region deprecated re-export +# re-export NOT as inherited class with @deprecated, to keep the original subclassing intact!!1 -class LicenseChoiceFactoryException(CycloneDxFactoryException): - """ - Base exception that covers all LicenseChoiceFactory exceptions. - """ - pass +CycloneDxFactoryException = _FactoryException +"""Deprecated — Alias of :class:`cyclonedx.contrib.license.exceptions.FactoryException`. -class InvalidSpdxLicenseException(LicenseChoiceFactoryException): - """ - Thrown when an invalid SPDX License is provided. - """ - pass +.. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license.exceptions import FactoryException`` instead. + The exported symbol itself is NOT deprecated - only this import path. +""" + +LicenseChoiceFactoryException = _LicenseChoiceFactoryException +"""Deprecated — Alias of :class:`cyclonedx.contrib.license.exceptions.LicenseChoiceFactoryException`. + +.. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license.exceptions import LicenseChoiceFactoryException`` instead. + The exported symbol itself is NOT deprecated - only this import path. +""" + +InvalidSpdxLicenseException = _InvalidSpdxLicenseException +"""Deprecated — Alias of :class:`cyclonedx.contrib.license.exceptions.InvalidSpdxLicenseException`. +.. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license.exceptions import InvalidSpdxLicenseException`` instead. + The exported symbol itself is NOT deprecated - only this import path. +""" + +LicenseFactoryException = _LicenseFactoryException +"""Deprecated — Alias of :class:`cyclonedx.contrib.license.exceptions.LicenseFactoryException`. -class LicenseFactoryException(CycloneDxFactoryException): - """ - Base exception that covers all LicenseFactory exceptions. - """ - pass +.. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license.exceptions import LicenseFactoryException`` instead. + The exported symbol itself is NOT deprecated - only this import path. +""" +InvalidLicenseExpressionException = _InvalidLicenseExpressionException +"""Deprecated — Alias of :class:`cyclonedx.contrib.license.exceptions.InvalidLicenseExpressionException`. + +.. deprecated:: next + This re-export location is deprecated. + Use ``from cyclonedx.contrib.license.exceptions import InvalidLicenseExpressionException`` instead. + The exported symbol itself is NOT deprecated - only this import path. +""" -class InvalidLicenseExpressionException(LicenseFactoryException): - """ - Thrown when an invalid License expressions is provided. - """ - pass +# endregion deprecated re-export diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index d394abec..7bb61d7b 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -38,5 +38,6 @@ class LicenseFactory(_LicenseFactory): Use ``from cyclonedx.contrib.license.factories import LicenseFactory`` instead. The exported symbol itself is NOT deprecated - only this import path. """ + pass # endregion deprecated re-export From ef83a07138b697cfd1d5a4167958e46981008d72 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 16:09:32 +0100 Subject: [PATCH 10/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/license/__init__.py | 2 ++ cyclonedx/contrib/license/exceptions.py | 2 +- cyclonedx/contrib/license/factories.py | 7 +++++++ cyclonedx/contrib/this/__init__.py | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cyclonedx/contrib/license/__init__.py b/cyclonedx/contrib/license/__init__.py index 671a2188..cd8b075e 100644 --- a/cyclonedx/contrib/license/__init__.py +++ b/cyclonedx/contrib/license/__init__.py @@ -14,3 +14,5 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. + +"""License related functionality""" diff --git a/cyclonedx/contrib/license/exceptions.py b/cyclonedx/contrib/license/exceptions.py index 4a484a50..d2d723fd 100644 --- a/cyclonedx/contrib/license/exceptions.py +++ b/cyclonedx/contrib/license/exceptions.py @@ -17,7 +17,7 @@ """ -Exceptions relating to specific conditions that occur when factoring a model. +Exceptions relating to specific conditions that occur when factoring a module. """ from ...exception import CycloneDxException diff --git a/cyclonedx/contrib/license/factories.py b/cyclonedx/contrib/license/factories.py index a74ff6b0..b0afc2ff 100644 --- a/cyclonedx/contrib/license/factories.py +++ b/cyclonedx/contrib/license/factories.py @@ -15,6 +15,8 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +"""License related factories""" + __all__ = ['LicenseFactory'] from typing import TYPE_CHECKING, Optional @@ -88,3 +90,8 @@ def make_with_name(self, name: str, *, ) -> DisjunctiveLicense: """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` with a name.""" return DisjunctiveLicense(name=name, text=text, url=url, acknowledgement=acknowledgement) + + +# Idea for more factories: +# class LicenseAttachmentFactory: +# def male_from_file(self, path: PathLike) -> AttachedText: ... diff --git a/cyclonedx/contrib/this/__init__.py b/cyclonedx/contrib/this/__init__.py index 671a2188..b6b61320 100644 --- a/cyclonedx/contrib/this/__init__.py +++ b/cyclonedx/contrib/this/__init__.py @@ -14,3 +14,5 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. + +"""Representation of this very python library.""" From 7d14191ef46088869fe3dfc5dea8d7f704e8005b Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 16:15:10 +0100 Subject: [PATCH 11/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/factory/license.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 7bb61d7b..1f9d6e50 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -36,7 +36,7 @@ class LicenseFactory(_LicenseFactory): .. deprecated:: next This re-export location is deprecated. Use ``from cyclonedx.contrib.license.factories import LicenseFactory`` instead. - The exported symbol itself is NOT deprecated - only this import path. + The exported symbol itself is NOT deprecated — only this import path. """ pass From b88d831c4040dd4cdfc25e41a3dec29c88f2e831 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 17:20:47 +0100 Subject: [PATCH 12/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- .../hash.py => contrib/component/__init__.py} | 27 +------ cyclonedx/contrib/component/builders.py | 72 +++++++++++++++++++ cyclonedx/model/__init__.py | 4 +- cyclonedx/model/component.py | 35 +++++---- 4 files changed, 91 insertions(+), 47 deletions(-) rename cyclonedx/{_internal/hash.py => contrib/component/__init__.py} (56%) create mode 100644 cyclonedx/contrib/component/builders.py diff --git a/cyclonedx/_internal/hash.py b/cyclonedx/contrib/component/__init__.py similarity index 56% rename from cyclonedx/_internal/hash.py rename to cyclonedx/contrib/component/__init__.py index 4fc17f5e..02d2b73f 100644 --- a/cyclonedx/_internal/hash.py +++ b/cyclonedx/contrib/component/__init__.py @@ -15,29 +15,4 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. - -""" -!!! ALL SYMBOLS IN HERE ARE INTERNAL. -Everything might change without any notice. -""" - - -from hashlib import sha1 - - -def file_sha1sum(filename: str) -> str: - """ - Generate a SHA1 hash of the provided file. - - Args: - filename: - Absolute path to file to hash as `str` - - Returns: - SHA-1 hash - """ - h = sha1() # nosec B303, B324 - with open(filename, 'rb') as f: - for byte_block in iter(lambda: f.read(4096), b''): - h.update(byte_block) - return h.hexdigest() +"""Component related functionality""" diff --git a/cyclonedx/contrib/component/builders.py b/cyclonedx/contrib/component/builders.py new file mode 100644 index 00000000..45c900fb --- /dev/null +++ b/cyclonedx/contrib/component/builders.py @@ -0,0 +1,72 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""Component related builders""" + + +from hashlib import sha1 +from typing import Optional +from os.path import exists + +from ...model import HashType, HashAlgorithm +from ...model.component import Component, ComponentType + +class ComponentBuilder: + + def make_for_file(self, absolute_file_path: str, name: Optional[str]) -> Component: + """ + Helper method to create a :class:`cyclonedx.model.component.Component` + that represents the provided local file as a Component. + + Args: + absolute_file_path: + Absolute path to the file you wish to represent + name: + Optionally, if supplied this is the name that will be used for the component. + Defaults to arg ``absolute_file_path``. + + Returns: + `Component` representing the supplied file + """ + if not exists(absolute_file_path): + raise FileExistsError(f'Supplied file path {absolute_file_path!r} does not exist') + + return Component( + type=ComponentType.FILE, + name=name or absolute_file_path, + hashes=[ + HashType(alg=HashAlgorithm.SHA_1, content=self._file_sha1sum(absolute_file_path)) + ] + ) + + @staticmethod + def _file_sha1sum(filename: str) -> str: + """ + Generate a SHA1 hash of the provided file. + + Args: + filename: + Absolute path to file to hash as `str` + + Returns: + SHA-1 hash + """ + h = sha1() # nosec B303, B324 + with open(filename, 'rb') as f: + for byte_block in iter(lambda: f.read(4096), b''): + h.update(byte_block) + return h.hexdigest() diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index ff0abc77..aa6e6b4a 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -395,7 +395,7 @@ class HashType: """ @staticmethod - def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': + def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': # TODO: move to contrib """ Attempts to convert a hashlib-algorithm to our internal model classes. @@ -419,7 +419,7 @@ def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': return HashType(alg=alg, content=content) @staticmethod - def from_composite_str(composite_hash: str) -> 'HashType': + def from_composite_str(composite_hash: str) -> 'HashType': # TODO: move to contrib """ Attempts to convert a string which includes both the Hash Algorithm and Hash Value and represent using our internal model classes. diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index c4e4e5c6..7244a348 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -18,7 +18,6 @@ import re from collections.abc import Iterable from enum import Enum -from os.path import exists from typing import Any, Optional, Union from warnings import warn @@ -26,10 +25,11 @@ import py_serializable as serializable from packageurl import PackageURL from sortedcontainers import SortedSet +from typing_extensions import deprecated +from contrib.component.builders import ComponentBuilder from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparablePackageURL as _ComparablePackageURL, ComparableTuple as _ComparableTuple -from .._internal.hash import file_sha1sum as _file_sha1sum from ..exception.model import InvalidOmniBorIdException, InvalidSwhidException from ..exception.serialization import ( CycloneDxDeserializationException, @@ -50,12 +50,11 @@ from . import ( AttachedText, ExternalReference, - HashAlgorithm, HashType, IdentifiableAction, Property, XsUri, - _HashTypeRepositorySerializationHelper, + _HashTypeRepositorySerializationHelper, HashAlgorithm, ) from .bom_ref import BomRef from .component_evidence import ComponentEvidence, _ComponentEvidenceSerializationHelper @@ -955,8 +954,10 @@ class Component(Dependable): """ @staticmethod + @deprecated('Deprecated - use cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file instead') def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component': - """ + """Deprecated — Wrapper of :func:`cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file`. + Helper method to create a Component that represents the provided local file as a Component. Args: @@ -967,22 +968,18 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component Returns: `Component` representing the supplied file + + .. deprecated:: next + Use ``cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file()`` instead. """ - if not exists(absolute_file_path): - raise FileExistsError(f'Supplied file path {absolute_file_path!r} does not exist') - - sha1_hash: str = _file_sha1sum(absolute_file_path) - return Component( - name=path_for_bom if path_for_bom else absolute_file_path, - version=f'0.0.0-{sha1_hash[0:12]}', - hashes=[ - HashType(alg=HashAlgorithm.SHA_1, content=sha1_hash) - ], - type=ComponentType.FILE, purl=PackageURL( - type='generic', name=path_for_bom if path_for_bom else absolute_file_path, - version=f'0.0.0-{sha1_hash[0:12]}' - ) + component = ComponentBuilder().make_for_file(absolute_file_path, path_for_bom) + sha1_hash = next(h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1) + component.version=f'0.0.0-{sha1_hash[0:12]}' + component.purl=PackageURL( # DEPRECATED: a file has no PURL! + type='generic', name=path_for_bom if path_for_bom else absolute_file_path, + version=f'0.0.0-{sha1_hash[0:12]}' ) + return component def __init__( self, *, From 28b94a72bead19c5c48c36a91942fa5004624527 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 17:43:48 +0100 Subject: [PATCH 13/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/component/builders.py | 3 +- cyclonedx/contrib/hash/__init__.py | 18 ++++ cyclonedx/contrib/hash/factories.py | 131 ++++++++++++++++++++++++ cyclonedx/exception/model.py | 2 +- cyclonedx/model/__init__.py | 110 +++----------------- cyclonedx/model/component.py | 11 +- 6 files changed, 167 insertions(+), 108 deletions(-) create mode 100644 cyclonedx/contrib/hash/__init__.py create mode 100644 cyclonedx/contrib/hash/factories.py diff --git a/cyclonedx/contrib/component/builders.py b/cyclonedx/contrib/component/builders.py index 45c900fb..73502d4f 100644 --- a/cyclonedx/contrib/component/builders.py +++ b/cyclonedx/contrib/component/builders.py @@ -27,7 +27,8 @@ class ComponentBuilder: - def make_for_file(self, absolute_file_path: str, name: Optional[str]) -> Component: + def make_for_file(self, absolute_file_path: str, *, + name: Optional[str]) -> Component: """ Helper method to create a :class:`cyclonedx.model.component.Component` that represents the provided local file as a Component. diff --git a/cyclonedx/contrib/hash/__init__.py b/cyclonedx/contrib/hash/__init__.py new file mode 100644 index 00000000..b65a1aff --- /dev/null +++ b/cyclonedx/contrib/hash/__init__.py @@ -0,0 +1,18 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""Hash related functionality""" diff --git a/cyclonedx/contrib/hash/factories.py b/cyclonedx/contrib/hash/factories.py new file mode 100644 index 00000000..967ec3cc --- /dev/null +++ b/cyclonedx/contrib/hash/factories.py @@ -0,0 +1,131 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""Hash related factories""" + +__all__ = ['HashTypeFactory'] + +from ...model import HashType, HashAlgorithm + +from ...exception.model import UnknownHashTypeException + + + +_MAP_HASHLIB: dict[str, HashAlgorithm] = { + # from hashlib.algorithms_guaranteed + 'md5': HashAlgorithm.MD5, + 'sha1': HashAlgorithm.SHA_1, + # sha224: + 'sha256': HashAlgorithm.SHA_256, + 'sha384': HashAlgorithm.SHA_384, + 'sha512': HashAlgorithm.SHA_512, + # blake2b: + # blake2s: + # sha3_224: + 'sha3_256': HashAlgorithm.SHA3_256, + 'sha3_384': HashAlgorithm.SHA3_384, + 'sha3_512': HashAlgorithm.SHA3_512, + # shake_128: + # shake_256: +} + +class HashTypeFactory: + + def from_hashlib_alg(self, hashlib_alg: str, content: str) -> HashType: + """ + Attempts to convert a hashlib-algorithm to our internal model classes. + + Args: + hashlib_alg: + Hash algorith - like it is used by `hashlib`. + Example: `sha256`. + + content: + Hash value. + + Raises: + `UnknownHashTypeException` if the algorithm of hash cannot be determined. + + Returns: + An instance of `HashType`. + """ + alg = _MAP_HASHLIB.get(hashlib_alg.lower()) + if alg is None: + raise UnknownHashTypeException(f'Unable to determine hash alg for {hashlib_alg!r}') + return HashType(alg=alg, content=content) + + def from_composite_str(self, composite_hash: str) -> HashType: + """ + Attempts to convert a string which includes both the Hash Algorithm and Hash Value and represent using our + internal model classes. + + Args: + composite_hash: + Composite Hash string of the format `HASH_ALGORITHM`:`HASH_VALUE`. + Example: `sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b`. + + Valid case insensitive prefixes are: + `md5`, `sha1`, `sha256`, `sha384`, `sha512`, `blake2b256`, `blake2b384`, `blake2b512`, + `blake2256`, `blake2384`, `blake2512`, `sha3-256`, `sha3-384`, `sha3-512`, + `blake3`. + + Raises: + `UnknownHashTypeException` if the type of hash cannot be determined. + + Returns: + An instance of `HashType`. + """ + parts = composite_hash.split(':') + + algorithm_prefix = parts[0].lower() + if algorithm_prefix == 'md5': + return HashType( + alg=HashAlgorithm.MD5, + content=parts[1].lower() + ) + elif algorithm_prefix[0:4] == 'sha3': + return HashType( + alg=getattr(HashAlgorithm, f'SHA3_{algorithm_prefix[5:]}'), + content=parts[1].lower() + ) + elif algorithm_prefix == 'sha1': + return HashType( + alg=HashAlgorithm.SHA_1, + content=parts[1].lower() + ) + elif algorithm_prefix[0:3] == 'sha': + # This is actually SHA2... + return HashType( + alg=getattr(HashAlgorithm, f'SHA_{algorithm_prefix[3:]}'), + content=parts[1].lower() + ) + elif algorithm_prefix[0:7] == 'blake2b': + return HashType( + alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[7:]}'), + content=parts[1].lower() + ) + elif algorithm_prefix[0:6] == 'blake2': + return HashType( + alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[6:]}'), + content=parts[1].lower() + ) + elif algorithm_prefix[0:6] == 'blake3': + return HashType( + alg=HashAlgorithm.BLAKE3, + content=parts[1].lower() + ) + raise UnknownHashTypeException(f'Unable to determine hash type from {composite_hash!r}') diff --git a/cyclonedx/exception/model.py b/cyclonedx/exception/model.py index f3986eb9..a38cc386 100644 --- a/cyclonedx/exception/model.py +++ b/cyclonedx/exception/model.py @@ -116,7 +116,7 @@ class UnknownHashTypeException(CycloneDxModelException): """ Exception raised when we are unable to determine the type of hash from a composite hash string. """ - pass + pass # TODO research deprecation of this... class LicenseExpressionAlongWithOthersException(CycloneDxModelException): diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index aa6e6b4a..3403388f 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -37,8 +37,9 @@ import py_serializable as serializable from sortedcontainers import SortedSet +from contrib.hash.factories import HashTypeFactory from .._internal.compare import ComparableTuple as _ComparableTuple -from ..exception.model import InvalidLocaleTypeException, InvalidUriException, UnknownHashTypeException +from ..exception.model import InvalidLocaleTypeException, InvalidUriException from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException from ..schema.schema import ( SchemaVersion1Dot0, @@ -366,25 +367,6 @@ def xml_denormalize(cls, o: 'XmlElement', *, ] -_MAP_HASHLIB: dict[str, HashAlgorithm] = { - # from hashlib.algorithms_guaranteed - 'md5': HashAlgorithm.MD5, - 'sha1': HashAlgorithm.SHA_1, - # sha224: - 'sha256': HashAlgorithm.SHA_256, - 'sha384': HashAlgorithm.SHA_384, - 'sha512': HashAlgorithm.SHA_512, - # blake2b: - # blake2s: - # sha3_224: - 'sha3_256': HashAlgorithm.SHA3_256, - 'sha3_384': HashAlgorithm.SHA3_384, - 'sha3_512': HashAlgorithm.SHA3_512, - # shake_128: - # shake_256: -} - - @serializable.serializable_class class HashType: """ @@ -395,91 +377,27 @@ class HashType: """ @staticmethod - def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': # TODO: move to contrib - """ - Attempts to convert a hashlib-algorithm to our internal model classes. - - Args: - hashlib_alg: - Hash algorith - like it is used by `hashlib`. - Example: `sha256`. + def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': + """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alge`. - content: - Hash value. - - Raises: - `UnknownHashTypeException` if the algorithm of hash cannot be determined. + Attempts to convert a hashlib-algorithm to our internal model classes. - Returns: - An instance of `HashType`. + .. deprecated:: next + Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg()`` instead. """ - alg = _MAP_HASHLIB.get(hashlib_alg.lower()) - if alg is None: - raise UnknownHashTypeException(f'Unable to determine hash alg for {hashlib_alg!r}') - return HashType(alg=alg, content=content) + return HashTypeFactory().from_hashlib_alg(hashlib_alg, content) @staticmethod - def from_composite_str(composite_hash: str) -> 'HashType': # TODO: move to contrib - """ + def from_composite_str(composite_hash: str) -> 'HashType': + """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str`. + Attempts to convert a string which includes both the Hash Algorithm and Hash Value and represent using our internal model classes. - Args: - composite_hash: - Composite Hash string of the format `HASH_ALGORITHM`:`HASH_VALUE`. - Example: `sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b`. - - Valid case insensitive prefixes are: - `md5`, `sha1`, `sha256`, `sha384`, `sha512`, `blake2b256`, `blake2b384`, `blake2b512`, - `blake2256`, `blake2384`, `blake2512`, `sha3-256`, `sha3-384`, `sha3-512`, - `blake3`. - - Raises: - `UnknownHashTypeException` if the type of hash cannot be determined. - - Returns: - An instance of `HashType`. + .. deprecated:: next + Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str()`` instead. """ - parts = composite_hash.split(':') - - algorithm_prefix = parts[0].lower() - if algorithm_prefix == 'md5': - return HashType( - alg=HashAlgorithm.MD5, - content=parts[1].lower() - ) - elif algorithm_prefix[0:4] == 'sha3': - return HashType( - alg=getattr(HashAlgorithm, f'SHA3_{algorithm_prefix[5:]}'), - content=parts[1].lower() - ) - elif algorithm_prefix == 'sha1': - return HashType( - alg=HashAlgorithm.SHA_1, - content=parts[1].lower() - ) - elif algorithm_prefix[0:3] == 'sha': - # This is actually SHA2... - return HashType( - alg=getattr(HashAlgorithm, f'SHA_{algorithm_prefix[3:]}'), - content=parts[1].lower() - ) - elif algorithm_prefix[0:7] == 'blake2b': - return HashType( - alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[7:]}'), - content=parts[1].lower() - ) - elif algorithm_prefix[0:6] == 'blake2': - return HashType( - alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[6:]}'), - content=parts[1].lower() - ) - elif algorithm_prefix[0:6] == 'blake3': - return HashType( - alg=HashAlgorithm.BLAKE3, - content=parts[1].lower() - ) - raise UnknownHashTypeException(f'Unable to determine hash type from {composite_hash!r}') + return HashTypeFactory().from_composite_str(composite_hash) def __init__( self, *, diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 7244a348..705f042a 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -960,19 +960,10 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component Helper method to create a Component that represents the provided local file as a Component. - Args: - absolute_file_path: - Absolute path to the file you wish to represent - path_for_bom: - Optionally, if supplied this is the path that will be used to identify the file in the BOM - - Returns: - `Component` representing the supplied file - .. deprecated:: next Use ``cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file()`` instead. """ - component = ComponentBuilder().make_for_file(absolute_file_path, path_for_bom) + component = ComponentBuilder().make_for_file(absolute_file_path, name=path_for_bom) sha1_hash = next(h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1) component.version=f'0.0.0-{sha1_hash[0:12]}' component.purl=PackageURL( # DEPRECATED: a file has no PURL! From 66d199c1641b46a298ac64454f147536b7a00371 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 28 Nov 2025 17:57:22 +0100 Subject: [PATCH 14/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/model/__init__.py | 9 +++++++-- cyclonedx/model/component.py | 3 ++- tests/test_component.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 3403388f..2be0937d 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -31,13 +31,12 @@ from typing import Any, Optional, Union from urllib.parse import quote as url_quote from uuid import UUID -from warnings import warn +from warnings import warn, deprecated from xml.etree.ElementTree import Element as XmlElement # nosec B405 import py_serializable as serializable from sortedcontainers import SortedSet -from contrib.hash.factories import HashTypeFactory from .._internal.compare import ComparableTuple as _ComparableTuple from ..exception.model import InvalidLocaleTypeException, InvalidUriException from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException @@ -377,6 +376,7 @@ class HashType: """ @staticmethod + @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg instead') def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alge`. @@ -385,9 +385,12 @@ def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': .. deprecated:: next Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg()`` instead. """ + from ..contrib.hash.factories import HashTypeFactory + return HashTypeFactory().from_hashlib_alg(hashlib_alg, content) @staticmethod + @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str instead') def from_composite_str(composite_hash: str) -> 'HashType': """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str`. @@ -397,6 +400,8 @@ def from_composite_str(composite_hash: str) -> 'HashType': .. deprecated:: next Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str()`` instead. """ + from ..contrib.hash.factories import HashTypeFactory + return HashTypeFactory().from_composite_str(composite_hash) def __init__( diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 705f042a..2454e53c 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -27,7 +27,6 @@ from sortedcontainers import SortedSet from typing_extensions import deprecated -from contrib.component.builders import ComponentBuilder from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparablePackageURL as _ComparablePackageURL, ComparableTuple as _ComparableTuple from ..exception.model import InvalidOmniBorIdException, InvalidSwhidException @@ -963,6 +962,8 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component .. deprecated:: next Use ``cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file()`` instead. """ + from ..contrib.component.builders import ComponentBuilder + component = ComponentBuilder().make_for_file(absolute_file_path, name=path_for_bom) sha1_hash = next(h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1) component.version=f'0.0.0-{sha1_hash[0:12]}' diff --git a/tests/test_component.py b/tests/test_component.py index 05ee373f..8e6e7e4d 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -21,7 +21,7 @@ # See https://github.com/package-url/packageurl-python/issues/65 from packageurl import PackageURL -from cyclonedx._internal.hash import file_sha1sum as _file_sha1sum +from cyclonedx.contrib.component.builders import ComponentBuilder from cyclonedx.model.component import Component from tests import OWN_DATA_DIRECTORY from tests._data.models import get_component_setuptools_simple @@ -65,7 +65,7 @@ def test_purl_incorrect_name(self) -> None: def test_from_xml_file_with_path_for_bom(self) -> None: test_file = join(OWN_DATA_DIRECTORY, 'xml', '1.4', 'bom_setuptools.xml') c = Component.for_file(absolute_file_path=test_file, path_for_bom='fixtures/bom_setuptools.xml') - sha1_hash: str = _file_sha1sum(filename=test_file) + sha1_hash: str = ComponentBuilder._file_sha1sum(filename=test_file) expected_version = f'0.0.0-{sha1_hash[0:12]}' self.assertEqual(c.name, 'fixtures/bom_setuptools.xml') self.assertEqual(c.version, expected_version) From c1abdc32437872c1ee1cd262cfefe2280e95e321 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Sun, 30 Nov 2025 14:35:59 +0100 Subject: [PATCH 15/27] feat: prep contrib Signed-off-by: Jan Kowalleck --- examples/complex_serialize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/complex_serialize.py b/examples/complex_serialize.py index 03619f8b..d12821f3 100644 --- a/examples/complex_serialize.py +++ b/examples/complex_serialize.py @@ -20,9 +20,9 @@ from packageurl import PackageURL -from cyclonedx.builder.this import this_component as cdx_lib_component +from cyclonedx.contrib.this.builders import this_component as cdx_lib_component +from cyclonedx.contrib.license.factories import LicenseFactory from cyclonedx.exception import MissingOptionalDependencyException -from cyclonedx.factory.license import LicenseFactory from cyclonedx.model import XsUri from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component, ComponentType From f8c43e6a3b1a0590b61aa9e18fdcc197acdad766 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Sun, 30 Nov 2025 14:50:21 +0100 Subject: [PATCH 16/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cyclonedx/contrib/__init__.py b/cyclonedx/contrib/__init__.py index d80b3a34..d0babedd 100644 --- a/cyclonedx/contrib/__init__.py +++ b/cyclonedx/contrib/__init__.py @@ -17,8 +17,9 @@ """ -Some features in this library are marked as contrib. These are community-provided extensions and are not part of the -official standard. They are optional and may evolve independently from the core. +Some features in this library are marked as contrib. +These are community-provided extensions and are not part of the official standard. +They are optional and may evolve independently from the core. """ __all__ = [ From c0ca9bce5bfeea82e7c0be32ebec535e78f481d9 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 09:43:19 +0100 Subject: [PATCH 17/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- examples/complex_serialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/complex_serialize.py b/examples/complex_serialize.py index d12821f3..a7c162bb 100644 --- a/examples/complex_serialize.py +++ b/examples/complex_serialize.py @@ -20,8 +20,8 @@ from packageurl import PackageURL -from cyclonedx.contrib.this.builders import this_component as cdx_lib_component from cyclonedx.contrib.license.factories import LicenseFactory +from cyclonedx.contrib.this.builders import this_component as cdx_lib_component from cyclonedx.exception import MissingOptionalDependencyException from cyclonedx.model import XsUri from cyclonedx.model.bom import Bom From be8d60734362bfc0f219dd1e1a9304f5cc0bd5ca Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 10:49:15 +0100 Subject: [PATCH 18/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/component/builders.py | 5 +- cyclonedx/contrib/hash/factories.py | 6 +-- cyclonedx/contrib/vulnerability/__init__.py | 18 +++++++ cyclonedx/contrib/vulnerability/cvss.py | 58 +++++++++++++++++++++ cyclonedx/model/__init__.py | 2 +- cyclonedx/model/bom.py | 18 +++++++ cyclonedx/model/component.py | 7 +-- cyclonedx/model/license.py | 6 ++- cyclonedx/model/vulnerability.py | 37 ++++--------- 9 files changed, 118 insertions(+), 39 deletions(-) create mode 100644 cyclonedx/contrib/vulnerability/__init__.py create mode 100644 cyclonedx/contrib/vulnerability/cvss.py diff --git a/cyclonedx/contrib/component/builders.py b/cyclonedx/contrib/component/builders.py index 73502d4f..5a70628f 100644 --- a/cyclonedx/contrib/component/builders.py +++ b/cyclonedx/contrib/component/builders.py @@ -19,12 +19,13 @@ from hashlib import sha1 -from typing import Optional from os.path import exists +from typing import Optional -from ...model import HashType, HashAlgorithm +from ...model import HashAlgorithm, HashType from ...model.component import Component, ComponentType + class ComponentBuilder: def make_for_file(self, absolute_file_path: str, *, diff --git a/cyclonedx/contrib/hash/factories.py b/cyclonedx/contrib/hash/factories.py index 967ec3cc..c120c4a7 100644 --- a/cyclonedx/contrib/hash/factories.py +++ b/cyclonedx/contrib/hash/factories.py @@ -19,11 +19,8 @@ __all__ = ['HashTypeFactory'] -from ...model import HashType, HashAlgorithm - from ...exception.model import UnknownHashTypeException - - +from ...model import HashAlgorithm, HashType _MAP_HASHLIB: dict[str, HashAlgorithm] = { # from hashlib.algorithms_guaranteed @@ -43,6 +40,7 @@ # shake_256: } + class HashTypeFactory: def from_hashlib_alg(self, hashlib_alg: str, content: str) -> HashType: diff --git a/cyclonedx/contrib/vulnerability/__init__.py b/cyclonedx/contrib/vulnerability/__init__.py new file mode 100644 index 00000000..5d93d047 --- /dev/null +++ b/cyclonedx/contrib/vulnerability/__init__.py @@ -0,0 +1,18 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""Vulnerability related functionality""" diff --git a/cyclonedx/contrib/vulnerability/cvss.py b/cyclonedx/contrib/vulnerability/cvss.py new file mode 100644 index 00000000..461b5ee9 --- /dev/null +++ b/cyclonedx/contrib/vulnerability/cvss.py @@ -0,0 +1,58 @@ +# This file is part of CycloneDX Python Library +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +"""CVSS related utilities""" + +__all__ = ['vs_from_cvss_scores'] + +from typing import Union + +from ...model.vulnerability import VulnerabilitySeverity + + +def vs_from_cvss_scores(scores: Union[tuple[float, ...], float, None]) -> VulnerabilitySeverity: + """ + Derives the Severity of a Vulnerability from it's declared CVSS scores. + + Args: + scores: A `tuple` of CVSS scores. CVSS scoring system allows for up to three separate scores. + + Returns: + Always returns an instance of :class:`cyclonedx.model.vulnerability.VulnerabilitySeverity`. + """ + if type(scores) is float: + scores = (scores,) + + if scores is None: + return VulnerabilitySeverity.UNKNOWN + + max_cvss_score: float + if isinstance(scores, tuple): + max_cvss_score = max(scores) + else: + max_cvss_score = float(scores) + + if max_cvss_score >= 9.0: + return VulnerabilitySeverity.CRITICAL + elif max_cvss_score >= 7.0: + return VulnerabilitySeverity.HIGH + elif max_cvss_score >= 4.0: + return VulnerabilitySeverity.MEDIUM + elif max_cvss_score > 0.0: + return VulnerabilitySeverity.LOW + else: + return VulnerabilitySeverity.NONE diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 2be0937d..21db4eea 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -31,7 +31,7 @@ from typing import Any, Optional, Union from urllib.parse import quote as url_quote from uuid import UUID -from warnings import warn, deprecated +from warnings import deprecated, warn from xml.etree.ElementTree import Element as XmlElement # nosec B405 import py_serializable as serializable diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 9c4b8cad..184f545c 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -26,6 +26,7 @@ import py_serializable as serializable from sortedcontainers import SortedSet +from typing_extensions import deprecated from .._internal.compare import ComparableTuple as _ComparableTuple from .._internal.time import get_now_utc as _get_now_utc @@ -706,6 +707,8 @@ def get_component_by_purl(self, purl: Optional['PackageURL']) -> Optional[Compon Returns: `Component` or `None` + + .. deprecated:: next """ if purl: found = [x for x in self.components if x.purl == purl] @@ -720,6 +723,8 @@ def get_urn_uuid(self) -> str: Returns: URN formatted UUID that uniquely identified this Bom instance. + + .. deprecated:: next """ return self.serial_number.urn @@ -733,6 +738,8 @@ def has_component(self, component: Component) -> bool: Returns: `bool` - `True` if the supplied Component is part of this Bom, `False` otherwise. + + .. deprecated:: next """ return component in self.components @@ -751,6 +758,8 @@ def get_vulnerabilities_for_bom_ref(self, bom_ref: BomRef) -> 'SortedSet[Vulnera Returns: `SortedSet` of `Vulnerability` + + .. deprecated:: next """ vulnerabilities: SortedSet[Vulnerability] = SortedSet() @@ -766,6 +775,8 @@ def has_vulnerabilities(self) -> bool: Returns: `bool` - `True` if this Bom has at least one Vulnerability, `False` otherwise. + + .. deprecated:: next """ return bool(self.vulnerabilities) @@ -788,6 +799,10 @@ def register_dependency(self, target: Dependable, depends_on: Optional[Iterable[ self.register_dependency(target=_d2, depends_on=None) def urn(self) -> str: + """ + .. deprecated:: next + """ + # idea: have 'serial_number' be a string, and use it instead of this method return f'{_BOM_LINK_PREFIX}{self.serial_number}/{self.version}' def validate(self) -> bool: @@ -797,7 +812,10 @@ def validate(self) -> bool: Returns: `bool` + + .. deprecated:: next """ + # !! deprecated function. have this as an part of the normlization process, like the BomRefDiscrimator # 0. Make sure all Dependable have a Dependency entry if self.metadata.component: self.register_dependency(target=self.metadata.component) diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 2454e53c..f992ed89 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -49,11 +49,12 @@ from . import ( AttachedText, ExternalReference, + HashAlgorithm, HashType, IdentifiableAction, Property, XsUri, - _HashTypeRepositorySerializationHelper, HashAlgorithm, + _HashTypeRepositorySerializationHelper, ) from .bom_ref import BomRef from .component_evidence import ComponentEvidence, _ComponentEvidenceSerializationHelper @@ -966,8 +967,8 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component component = ComponentBuilder().make_for_file(absolute_file_path, name=path_for_bom) sha1_hash = next(h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1) - component.version=f'0.0.0-{sha1_hash[0:12]}' - component.purl=PackageURL( # DEPRECATED: a file has no PURL! + component.version = f'0.0.0-{sha1_hash[0:12]}' + component.purl = PackageURL( # DEPRECATED: a file has no PURL! type='generic', name=path_for_bom if path_for_bom else absolute_file_path, version=f'0.0.0-{sha1_hash[0:12]}' ) diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py index 3d936942..866579f6 100644 --- a/cyclonedx/model/license.py +++ b/cyclonedx/model/license.py @@ -59,7 +59,11 @@ class LicenseAcknowledgement(str, Enum): # In an error, the name of the enum was `LicenseExpressionAcknowledgement`. # Even though this was changed, there might be some downstream usage of this symbol, so we keep it around ... LicenseExpressionAcknowledgement = LicenseAcknowledgement -"""Deprecated alias for :class:`LicenseAcknowledgement`""" +"""Deprecated - Alias for :class:`LicenseAcknowledgement` + +.. deprecated:: next Import `LicenseAcknowledgement` instead. + The exported original symbol itself is NOT deprecated - only this import path. +""" @serializable.serializable_class( diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index fcfb4a68..80ac92c9 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -38,6 +38,7 @@ import py_serializable as serializable from sortedcontainers import SortedSet +from typing_extensions import deprecated from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparableTuple as _ComparableTuple @@ -724,38 +725,18 @@ class VulnerabilitySeverity(str, Enum): UNKNOWN = 'unknown' @staticmethod + @deprecated('Deprecated - use cyclonedx.contrib.vulnerability.cvss.vs_from_cvss_scores instead') def get_from_cvss_scores(scores: Union[tuple[float, ...], float, None]) -> 'VulnerabilitySeverity': - """ + """Deprecated — Alias of :func:`cyclonedx.contrib.vulnerability.cvss.vs_from_cvss_scores()`. + Derives the Severity of a Vulnerability from it's declared CVSS scores. - Args: - scores: A `tuple` of CVSS scores. CVSS scoring system allows for up to three separate scores. + .. deprecated:: next + Use ``cyclonedx.contrib.vulnerability.cvss.VulnerabilitySeverity_from_cvss_scores()`` instead. + """ + from ..contrib.vulnerability.cvss import vs_from_cvss_scores - Returns: - Always returns an instance of `VulnerabilitySeverity`. - """ - if type(scores) is float: - scores = (scores,) - - if scores is None: - return VulnerabilitySeverity.UNKNOWN - - max_cvss_score: float - if isinstance(scores, tuple): - max_cvss_score = max(scores) - else: - max_cvss_score = float(scores) - - if max_cvss_score >= 9.0: - return VulnerabilitySeverity.CRITICAL - elif max_cvss_score >= 7.0: - return VulnerabilitySeverity.HIGH - elif max_cvss_score >= 4.0: - return VulnerabilitySeverity.MEDIUM - elif max_cvss_score > 0.0: - return VulnerabilitySeverity.LOW - else: - return VulnerabilitySeverity.NONE + return vs_from_cvss_scores(scores) @serializable.serializable_class(ignore_unknown_during_deserialization=True) From 840fbd49713970e04b91f9904859d8e7fc684a18 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 11:14:18 +0100 Subject: [PATCH 19/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/model/bom.py | 6 ++++-- cyclonedx/model/component.py | 7 ++++++- cyclonedx/model/vulnerability.py | 8 ++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 184f545c..a846b912 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -26,7 +26,6 @@ import py_serializable as serializable from sortedcontainers import SortedSet -from typing_extensions import deprecated from .._internal.compare import ComparableTuple as _ComparableTuple from .._internal.time import get_now_utc as _get_now_utc @@ -760,8 +759,8 @@ def get_vulnerabilities_for_bom_ref(self, bom_ref: BomRef) -> 'SortedSet[Vulnera `SortedSet` of `Vulnerability` .. deprecated:: next + Deprecated without any replacement. """ - vulnerabilities: SortedSet[Vulnerability] = SortedSet() for v in self.vulnerabilities: for target in v.affects: @@ -777,6 +776,7 @@ def has_vulnerabilities(self) -> bool: `bool` - `True` if this Bom has at least one Vulnerability, `False` otherwise. .. deprecated:: next + Deprecated without any replacement. """ return bool(self.vulnerabilities) @@ -801,6 +801,7 @@ def register_dependency(self, target: Dependable, depends_on: Optional[Iterable[ def urn(self) -> str: """ .. deprecated:: next + Deprecated without any replacement. """ # idea: have 'serial_number' be a string, and use it instead of this method return f'{_BOM_LINK_PREFIX}{self.serial_number}/{self.version}' @@ -814,6 +815,7 @@ def validate(self) -> bool: `bool` .. deprecated:: next + Deprecated without any replacement. """ # !! deprecated function. have this as an part of the normlization process, like the BomRefDiscrimator # 0. Make sure all Dependable have a Dependency entry diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index f992ed89..e4412bed 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -16,16 +16,21 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. import re +import sys from collections.abc import Iterable from enum import Enum from typing import Any, Optional, Union from warnings import warn +if sys.version_info >= (3, 13): + from warnings import deprecated +else: + from typing_extensions import deprecated + # See https://github.com/package-url/packageurl-python/issues/65 import py_serializable as serializable from packageurl import PackageURL from sortedcontainers import SortedSet -from typing_extensions import deprecated from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparablePackageURL as _ComparablePackageURL, ComparableTuple as _ComparableTuple diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 80ac92c9..4632ef9b 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -28,7 +28,7 @@ See the CycloneDX Schema extension definition https://cyclonedx.org/docs/1.7/xml/#type_vulnerabilitiesType """ - +import sys import re from collections.abc import Iterable from datetime import datetime @@ -36,9 +36,13 @@ from enum import Enum from typing import Any, Optional, Union +if sys.version_info >= (3, 13): + from warnings import deprecated +else: + from typing_extensions import deprecated + import py_serializable as serializable from sortedcontainers import SortedSet -from typing_extensions import deprecated from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str from .._internal.compare import ComparableTuple as _ComparableTuple From f61abe94e3838e32feb028c1c234fb51c1b43288 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 11:14:39 +0100 Subject: [PATCH 20/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/model/vulnerability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 4632ef9b..3b4dfd2a 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -28,8 +28,8 @@ See the CycloneDX Schema extension definition https://cyclonedx.org/docs/1.7/xml/#type_vulnerabilitiesType """ -import sys import re +import sys from collections.abc import Iterable from datetime import datetime from decimal import Decimal From 5b98ab48d71694f5588c2075d4d0e3f4db21c8c6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 11:15:36 +0100 Subject: [PATCH 21/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/model/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 21db4eea..98313c73 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -22,6 +22,7 @@ from a `cyclonedx.parser.BaseParser` implementation. """ +import sys import re from collections.abc import Generator, Iterable from datetime import datetime @@ -31,9 +32,14 @@ from typing import Any, Optional, Union from urllib.parse import quote as url_quote from uuid import UUID -from warnings import deprecated, warn +from warnings import warn from xml.etree.ElementTree import Element as XmlElement # nosec B405 +if sys.version_info >= (3, 13): + from warnings import deprecated +else: + from typing_extensions import deprecated + import py_serializable as serializable from sortedcontainers import SortedSet From 07c2a700b61f0730217903dcffb92e97344f8d32 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 11:26:39 +0100 Subject: [PATCH 22/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 98313c73..24e329db 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -22,8 +22,8 @@ from a `cyclonedx.parser.BaseParser` implementation. """ -import sys import re +import sys from collections.abc import Generator, Iterable from datetime import datetime from enum import Enum From 0e36aac34793f90d22aa303cf3c6b6724a486c82 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 11:51:19 +0100 Subject: [PATCH 23/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/this/builders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cyclonedx/contrib/this/builders.py b/cyclonedx/contrib/this/builders.py index 37f0159f..2aa5bfe7 100644 --- a/cyclonedx/contrib/this/builders.py +++ b/cyclonedx/contrib/this/builders.py @@ -29,7 +29,7 @@ def this_component() -> Component: - """Representation of this very python library as a :class:`Component`.""" + """Representation of this very python library as a :class:`cyclonedx.model.component.Component`.""" return Component( type=ComponentType.LIBRARY, group='CycloneDX', @@ -79,5 +79,5 @@ def this_component() -> Component: def this_tool() -> Tool: - """Representation of this very python library as a :class:`Tool`.""" + """Representation of this very python library as a :class:`cyclonedx.model.tool.Tool`.""" return Tool.from_component(this_component()) From bccb7e3e362a09eb8f5349179fdfcdcf01372a38 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 1 Dec 2025 11:54:03 +0100 Subject: [PATCH 24/27] feat: prepare "contrib" area Signed-off-by: Jan Kowalleck --- cyclonedx/builder/__init__.py | 2 ++ cyclonedx/builder/this.py | 5 ++++- cyclonedx/exception/factory.py | 2 ++ cyclonedx/factory/__init__.py | 2 ++ cyclonedx/factory/license.py | 4 ++++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cyclonedx/builder/__init__.py b/cyclonedx/builder/__init__.py index ec68e667..dc45dec3 100644 --- a/cyclonedx/builder/__init__.py +++ b/cyclonedx/builder/__init__.py @@ -17,4 +17,6 @@ """ Builders used in this library. + +.. deprecated:: next """ diff --git a/cyclonedx/builder/this.py b/cyclonedx/builder/this.py index 991d9f04..31f7a8ab 100644 --- a/cyclonedx/builder/this.py +++ b/cyclonedx/builder/this.py @@ -15,7 +15,10 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -"""Representation of this very python library.""" +"""Representation of this very python library. + +.. deprecated:: next +""" __all__ = ['this_component', 'this_tool'] diff --git a/cyclonedx/exception/factory.py b/cyclonedx/exception/factory.py index 6ca1d3af..ffe34eaf 100644 --- a/cyclonedx/exception/factory.py +++ b/cyclonedx/exception/factory.py @@ -18,6 +18,8 @@ """ Exceptions relating to specific conditions that occur when factoring a model. + +.. deprecated:: next """ __all__ = ['CycloneDxFactoryException', 'LicenseChoiceFactoryException', diff --git a/cyclonedx/factory/__init__.py b/cyclonedx/factory/__init__.py index ffb3ca2f..58f48f6d 100644 --- a/cyclonedx/factory/__init__.py +++ b/cyclonedx/factory/__init__.py @@ -17,4 +17,6 @@ """ Factories used in this library. + +.. deprecated:: next """ diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 1f9d6e50..043f3967 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -15,6 +15,10 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +""" +.. deprecated:: next +""" + __all__ = ['LicenseFactory'] import sys From ff0e11d027f809150eabf0cf5b4799ce91255cf1 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 2 Dec 2025 12:41:30 +0100 Subject: [PATCH 25/27] typos Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/hash/factories.py | 2 +- cyclonedx/contrib/license/exceptions.py | 2 +- cyclonedx/contrib/license/factories.py | 2 +- cyclonedx/model/__init__.py | 2 +- cyclonedx/model/bom.py | 2 +- cyclonedx/model/component.py | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cyclonedx/contrib/hash/factories.py b/cyclonedx/contrib/hash/factories.py index c120c4a7..c4b477fd 100644 --- a/cyclonedx/contrib/hash/factories.py +++ b/cyclonedx/contrib/hash/factories.py @@ -49,7 +49,7 @@ def from_hashlib_alg(self, hashlib_alg: str, content: str) -> HashType: Args: hashlib_alg: - Hash algorith - like it is used by `hashlib`. + Hash algorithm - like it is used by `hashlib`. Example: `sha256`. content: diff --git a/cyclonedx/contrib/license/exceptions.py b/cyclonedx/contrib/license/exceptions.py index d2d723fd..4a484a50 100644 --- a/cyclonedx/contrib/license/exceptions.py +++ b/cyclonedx/contrib/license/exceptions.py @@ -17,7 +17,7 @@ """ -Exceptions relating to specific conditions that occur when factoring a module. +Exceptions relating to specific conditions that occur when factoring a model. """ from ...exception import CycloneDxException diff --git a/cyclonedx/contrib/license/factories.py b/cyclonedx/contrib/license/factories.py index b0afc2ff..581acbed 100644 --- a/cyclonedx/contrib/license/factories.py +++ b/cyclonedx/contrib/license/factories.py @@ -94,4 +94,4 @@ def make_with_name(self, name: str, *, # Idea for more factories: # class LicenseAttachmentFactory: -# def male_from_file(self, path: PathLike) -> AttachedText: ... +# def make_from_file(self, path: PathLike) -> AttachedText: ... diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 24e329db..d7d0eed0 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -384,7 +384,7 @@ class HashType: @staticmethod @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg instead') def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': - """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alge`. + """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg`. Attempts to convert a hashlib-algorithm to our internal model classes. diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index a846b912..440b8949 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -817,7 +817,7 @@ def validate(self) -> bool: .. deprecated:: next Deprecated without any replacement. """ - # !! deprecated function. have this as an part of the normlization process, like the BomRefDiscrimator + # !! deprecated function. have this as an part of the normalization process, like the BomRefDiscrimator # 0. Make sure all Dependable have a Dependency entry if self.metadata.component: self.register_dependency(target=self.metadata.component) diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index e4412bed..a845a48a 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -971,7 +971,8 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component from ..contrib.component.builders import ComponentBuilder component = ComponentBuilder().make_for_file(absolute_file_path, name=path_for_bom) - sha1_hash = next(h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1) + sha1_hash = next((h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1), None) + assert sha1_hash is not None component.version = f'0.0.0-{sha1_hash[0:12]}' component.purl = PackageURL( # DEPRECATED: a file has no PURL! type='generic', name=path_for_bom if path_for_bom else absolute_file_path, From 943674700edda34cd3028aba604ab14628f8676f Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 2 Dec 2025 12:53:09 +0100 Subject: [PATCH 26/27] typos Signed-off-by: Jan Kowalleck --- cyclonedx/contrib/component/builders.py | 1 + cyclonedx/contrib/license/exceptions.py | 2 +- cyclonedx/model/bom.py | 8 ++++---- cyclonedx/model/vulnerability.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cyclonedx/contrib/component/builders.py b/cyclonedx/contrib/component/builders.py index 5a70628f..633e186b 100644 --- a/cyclonedx/contrib/component/builders.py +++ b/cyclonedx/contrib/component/builders.py @@ -17,6 +17,7 @@ """Component related builders""" +__all__ = ['ComponentBuilder'] from hashlib import sha1 from os.path import exists diff --git a/cyclonedx/contrib/license/exceptions.py b/cyclonedx/contrib/license/exceptions.py index 4a484a50..571c7598 100644 --- a/cyclonedx/contrib/license/exceptions.py +++ b/cyclonedx/contrib/license/exceptions.py @@ -56,6 +56,6 @@ class LicenseFactoryException(FactoryException): class InvalidLicenseExpressionException(LicenseFactoryException): """ - Thrown when an invalid License expressions is provided. + Thrown when an invalid License expression is provided. """ pass diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 440b8949..08e83877 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -759,7 +759,7 @@ def get_vulnerabilities_for_bom_ref(self, bom_ref: BomRef) -> 'SortedSet[Vulnera `SortedSet` of `Vulnerability` .. deprecated:: next - Deprecated without any replacement. + Deprecated without any replacement. """ vulnerabilities: SortedSet[Vulnerability] = SortedSet() for v in self.vulnerabilities: @@ -776,7 +776,7 @@ def has_vulnerabilities(self) -> bool: `bool` - `True` if this Bom has at least one Vulnerability, `False` otherwise. .. deprecated:: next - Deprecated without any replacement. + Deprecated without any replacement. """ return bool(self.vulnerabilities) @@ -801,7 +801,7 @@ def register_dependency(self, target: Dependable, depends_on: Optional[Iterable[ def urn(self) -> str: """ .. deprecated:: next - Deprecated without any replacement. + Deprecated without any replacement. """ # idea: have 'serial_number' be a string, and use it instead of this method return f'{_BOM_LINK_PREFIX}{self.serial_number}/{self.version}' @@ -815,7 +815,7 @@ def validate(self) -> bool: `bool` .. deprecated:: next - Deprecated without any replacement. + Deprecated without any replacement. """ # !! deprecated function. have this as an part of the normalization process, like the BomRefDiscrimator # 0. Make sure all Dependable have a Dependency entry diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 3b4dfd2a..5f3bf024 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -736,7 +736,7 @@ def get_from_cvss_scores(scores: Union[tuple[float, ...], float, None]) -> 'Vuln Derives the Severity of a Vulnerability from it's declared CVSS scores. .. deprecated:: next - Use ``cyclonedx.contrib.vulnerability.cvss.VulnerabilitySeverity_from_cvss_scores()`` instead. + Use ``cyclonedx.contrib.vulnerability.cvss.vs_from_cvss_scores()`` instead. """ from ..contrib.vulnerability.cvss import vs_from_cvss_scores From 0d0443de223f2055163ecb9628e06fbb3bce4e96 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 2 Dec 2025 13:07:58 +0100 Subject: [PATCH 27/27] typos Signed-off-by: Jan Kowalleck --- cyclonedx/exception/factory.py | 10 +++++----- cyclonedx/model/__init__.py | 8 ++++---- cyclonedx/model/component.py | 4 ++-- cyclonedx/model/license.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cyclonedx/exception/factory.py b/cyclonedx/exception/factory.py index ffe34eaf..41591e41 100644 --- a/cyclonedx/exception/factory.py +++ b/cyclonedx/exception/factory.py @@ -44,7 +44,7 @@ .. deprecated:: next This re-export location is deprecated. Use ``from cyclonedx.contrib.license.exceptions import FactoryException`` instead. - The exported symbol itself is NOT deprecated - only this import path. + The exported symbol itself is NOT deprecated — only this import path. """ LicenseChoiceFactoryException = _LicenseChoiceFactoryException @@ -53,7 +53,7 @@ .. deprecated:: next This re-export location is deprecated. Use ``from cyclonedx.contrib.license.exceptions import LicenseChoiceFactoryException`` instead. - The exported symbol itself is NOT deprecated - only this import path. + The exported symbol itself is NOT deprecated — only this import path. """ InvalidSpdxLicenseException = _InvalidSpdxLicenseException @@ -62,7 +62,7 @@ .. deprecated:: next This re-export location is deprecated. Use ``from cyclonedx.contrib.license.exceptions import InvalidSpdxLicenseException`` instead. - The exported symbol itself is NOT deprecated - only this import path. + The exported symbol itself is NOT deprecated — only this import path. """ LicenseFactoryException = _LicenseFactoryException @@ -71,7 +71,7 @@ .. deprecated:: next This re-export location is deprecated. Use ``from cyclonedx.contrib.license.exceptions import LicenseFactoryException`` instead. - The exported symbol itself is NOT deprecated - only this import path. + The exported symbol itself is NOT deprecated — only this import path. """ InvalidLicenseExpressionException = _InvalidLicenseExpressionException @@ -80,7 +80,7 @@ .. deprecated:: next This re-export location is deprecated. Use ``from cyclonedx.contrib.license.exceptions import InvalidLicenseExpressionException`` instead. - The exported symbol itself is NOT deprecated - only this import path. + The exported symbol itself is NOT deprecated — only this import path. """ # endregion deprecated re-export diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index d7d0eed0..f949e10c 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -382,21 +382,21 @@ class HashType: """ @staticmethod - @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg instead') + @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory().from_hashlib_alg() instead') def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg`. Attempts to convert a hashlib-algorithm to our internal model classes. .. deprecated:: next - Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg()`` instead. + Use ``cyclonedx.contrib.hash.factories.HashTypeFactory().from_hashlib_alg()`` instead. """ from ..contrib.hash.factories import HashTypeFactory return HashTypeFactory().from_hashlib_alg(hashlib_alg, content) @staticmethod - @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str instead') + @deprecated('Deprecated - use cyclonedx.contrib.hash.factories.HashTypeFactory().from_composite_str() instead') def from_composite_str(composite_hash: str) -> 'HashType': """Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str`. @@ -404,7 +404,7 @@ def from_composite_str(composite_hash: str) -> 'HashType': internal model classes. .. deprecated:: next - Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str()`` instead. + Use ``cyclonedx.contrib.hash.factories.HashTypeFactory().from_composite_str()`` instead. """ from ..contrib.hash.factories import HashTypeFactory diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index a845a48a..47702ed0 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -959,14 +959,14 @@ class Component(Dependable): """ @staticmethod - @deprecated('Deprecated - use cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file instead') + @deprecated('Deprecated - use cyclonedx.contrib.component.builders.ComponentBuilder().make_for_file() instead') def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component': """Deprecated — Wrapper of :func:`cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file`. Helper method to create a Component that represents the provided local file as a Component. .. deprecated:: next - Use ``cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file()`` instead. + Use ``cyclonedx.contrib.component.builders.ComponentBuilder().make_for_file()`` instead. """ from ..contrib.component.builders import ComponentBuilder diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py index 866579f6..9d32160e 100644 --- a/cyclonedx/model/license.py +++ b/cyclonedx/model/license.py @@ -59,7 +59,7 @@ class LicenseAcknowledgement(str, Enum): # In an error, the name of the enum was `LicenseExpressionAcknowledgement`. # Even though this was changed, there might be some downstream usage of this symbol, so we keep it around ... LicenseExpressionAcknowledgement = LicenseAcknowledgement -"""Deprecated - Alias for :class:`LicenseAcknowledgement` +"""Deprecated — Alias for :class:`LicenseAcknowledgement` .. deprecated:: next Import `LicenseAcknowledgement` instead. The exported original symbol itself is NOT deprecated - only this import path.