cyclonedx-python-lib review
cyclonedx-python-lib 11.12.0 gives Python programs an object model for CycloneDX bills of materials, plus readers, JSON and XML writers, and optional schema validation. Our Python 3.12 sandbox loaded the typed, pure-Python package in 0.01 seconds. You populate components, services, licenses, vulnerabilities, metadata, and dependency edges, then choose an explicit CycloneDX schema for output. Release 11.12.0 adds the `isExternal` field for components written as CycloneDX 1.7 and adds Python 3.14 to the test matrix. It is a construction library, so installing it alone does not scan an environment or create an SBOM.
Our cyclonedx-python-lib 11.12.0 install took 0.4 seconds, used 6 MB across 8 packages, and had no pip-audit findings, so it is a low-friction model layer for software that reads or writes CycloneDX. Install cyclonedx-bom instead when the desired outcome is a generated Python SBOM rather than an API.
We installed it
| Install | ✓ · 0.4s | 8 packages on disk · 6 MB |
| Import | ✓ | import cyclonedx in 0.01s · pure Python · py.typed · requires Python >=3.9,<4.0 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does cyclonedx-python-lib install cleanly?
Yes. In a fresh container with an empty cache, pip install cyclonedx-python-lib finished in 0.4s, leaving 8 packages and 6 MB on disk. pip-audit reported no known vulnerabilities.
What does cyclonedx-python-lib need to run?
Python >=3.9,<4.0, and nothing compiled: it is pure Python. In our run import cyclonedx succeeded in 0.01s, and the package ships py.typed for type checkers.
cyclonedx-python-lib or cyclonedx-bom: which should you use?
cyclonedx-bom: Pick it to generate a CycloneDX document from a Python environment, requirements file, or project. Our cyclonedx-python-lib 11.12.0 install took 0.4 seconds, used 6 MB across 8 packages, and had no pip-audit findings, so it is a low-friction model layer for software that reads or writes CycloneDX.
When should you not use cyclonedx-python-lib?
The README directs one-command Python SBOM generation to cyclonedx-bom because this library expects your code to supply inventory data
Use it if
- make_outputter can render one Python BOM model as CycloneDX 1.7 JSON or XML for different consumers
- Bom.from_json and Bom.from_xml let an inventory service load a document, alter its graph, and write it again
- A compliance pipeline needs stable bom-ref values and explicit dependency edges instead of assembling raw dictionaries
- Schema validators should reject malformed CycloneDX output before it reaches a signing or ingestion step
- The README directs one-command Python SBOM generation to cyclonedx-bom because this library expects your code to supply inventory data
- spdx-tools matches an organization that exchanges SPDX; cyclonedx-python-lib models CycloneDX and does not advertise format conversion
- Validators are optional extras and raise MissingOptionalDependencyException when their JSON or XML dependencies are absent
- The official deserialization example parses outside XML with defusedxml, so callers unwilling to choose a safe parser should not accept untrusted XML here
- Version 11.12.0 is a major-version API with schema-sensitive fields; teams that cannot pin it and review release notes risk noisy upgrades
Setup reality
We installed cyclonedx-python-lib 11.12.0 in 0.4 seconds inside a fresh, unprivileged Python 3.12 Bookworm sandbox with 3 CPUs and 8 GB of RAM. Eight packages occupied 6 MB afterward. pip-audit returned zero known vulnerabilities, and import cyclonedx took 0.01 seconds. The wheel is pure Python, declares 8 direct dependencies, includes py.typed, and supports Python 3.9 through the 3.x line. Our run required no compiler or system package.
Creating and serializing a BOM works from the base install, but schema checks use extras named validation, json-validation, and xml-validation. Missing support raises MissingOptionalDependencyException. Treat that as a failed validation stage rather than letting a build continue. The project's own 11.12.0 example feeds untrusted XML through defusedxml before Bom.from_xml. No credentials are involved unless your surrounding inventory process calls a registry or vulnerability service.
Select SchemaVersion on every outputter. The new isExternal property is representable in CycloneDX 1.7, while an older target cannot carry it. Give important components a repeatable bom_ref because Bom.register_dependency links objects through those references; random references make two otherwise identical builds difficult to diff. output_to_file protects an existing artifact and raises FileExistsError until allow_overwrite=True is passed.
Bom.from_json expects an already parsed mapping, and Bom.from_xml expects an XML element. In the repository's deserialization example, both methods carry a targeted type-ignore because serialization attaches them dynamically even though the distribution ships py.typed. This 6 MB library also does no package discovery. Use cyclonedx-bom for an environment scan, or supply your own component and dependency inventory before constructing the model.
Patterns
Define the application at the BOM root build-bom
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component, ComponentType
app = Component(name='billing-api', type=ComponentType.APPLICATION, version='2.4.0', bom_ref='billing-api@2.4.0')
bom = Bom()
bom.metadata.component = appA repeatable bom_ref keeps dependency links and generated diffs stable between builds.
Record a PyPI component add-component
from packageurl import PackageURL
from cyclonedx.model.component import Component, ComponentType
requests = Component(
type=ComponentType.LIBRARY, name='requests', version='2.32.5',
purl=PackageURL(type='pypi', name='requests', version='2.32.5'),
bom_ref='pkg:pypi/requests@2.32.5',
)
bom.components.add(requests)Bom.components is a sorted set, so component identity controls whether repeated additions collapse.
Flag an externally managed component mark-external
external_parser = Component(
type=ComponentType.LIBRARY,
name='vendor-parser',
bom_ref='vendor-parser@4',
is_external=True,
)
bom.components.add(external_parser)Package 11.12.0 can serialize isExternal in CycloneDX 1.7; earlier target schemas omit that field.
Turn license text into a model value add-license
from cyclonedx.contrib.license.factories import LicenseFactory
license_factory = LicenseFactory()
requests.licenses.add(license_factory.make_from_string('Apache-2.0'))LicenseFactory identifies SPDX IDs and expressions before falling back to a named license.
Connect the application to a dependency dependency-graph
bom.register_dependency(app, [requests])
bom.register_dependency(requests, [])register_dependency uses each object's bom_ref and also registers referenced child components in the graph.
Render CycloneDX 1.7 as JSON output-json
from cyclonedx.output import make_outputter
from cyclonedx.schema import OutputFormat, SchemaVersion
outputter = make_outputter(bom, OutputFormat.JSON, SchemaVersion.V1_7)
json_text = outputter.output_as_string(indent=2)SchemaVersion.V1_7 makes the consumer contract explicit instead of following a future default.
Render the BOM as XML output-xml
xml_outputter = make_outputter(bom, OutputFormat.XML, SchemaVersion.V1_7)
xml_text = xml_outputter.output_as_string(indent=2)The same Bom feeds both formats, while XML and JSON validation require their respective extras.
Allow replacement of a build artifact write-file
outputter.output_to_file(
'build/bom.json',
allow_overwrite=True,
indent=2,
)output_to_file raises FileExistsError for an existing path unless allow_overwrite is true.
Fail a build on invalid JSON validate-json
from cyclonedx.schema import SchemaVersion
from cyclonedx.validation.json import JsonStrictValidator
errors = JsonStrictValidator(SchemaVersion.V1_7).validate_str(json_text)
if errors:
raise ValueError(f'invalid CycloneDX document: {errors!r}')JsonStrictValidator needs the JSON validation extra; missing support raises MissingOptionalDependencyException.
Load an existing JSON BOM deserialize-json
import json
from cyclonedx.model.bom import Bom
with open('bom.json', encoding='utf-8') as handle:
bom = Bom.from_json(json.load(handle)) # type: ignore[attr-defined]Bom.from_json receives a parsed mapping. The official 11.12.0 example uses a type-ignore for the dynamically attached method.
Parse an outside XML BOM deserialize-xml
from defusedxml import ElementTree
from cyclonedx.model.bom import Bom
root = ElementTree.parse('bom.xml').getroot()
bom = Bom.from_xml(root) # type: ignore[attr-defined]The repository example uses defusedxml before passing an element to Bom.from_xml.
Validate against the output schema select-validator
from cyclonedx.validation import make_schemabased_validator
validator = make_schemabased_validator(outputter.output_format, outputter.schema_version)
errors = validator.validate_str(outputter.output_as_string())Taking format and version from the outputter prevents a CycloneDX 1.7 document from being checked as another schema.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| cyclonedx-bom | PyPI | Pick it to generate a CycloneDX document from a Python environment, requirements file, or project |
| spdx-tools | PyPI | Pick it when the required exchange format and policy language are SPDX |
| pip-audit | PyPI | Pick it when dependency vulnerability findings matter more than editing a BOM model |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

