Examples

Complex Serialize

  1# This file is part of CycloneDX Python Lib
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14#
 15# SPDX-License-Identifier: Apache-2.0
 16# Copyright (c) OWASP Foundation. All Rights Reserved.
 17
 18import sys
 19from typing import TYPE_CHECKING
 20
 21from packageurl import PackageURL
 22
 23from cyclonedx.exception import MissingOptionalDependencyException
 24from cyclonedx.factory.license import LicenseFactory
 25from cyclonedx.model import XsUri
 26from cyclonedx.model.bom import Bom
 27from cyclonedx.model.component import Component, ComponentType
 28from cyclonedx.model.contact import OrganizationalEntity
 29from cyclonedx.output import make_outputter
 30from cyclonedx.output.json import JsonV1Dot5
 31from cyclonedx.schema import OutputFormat, SchemaVersion
 32from cyclonedx.validation import make_schemabased_validator
 33from cyclonedx.validation.json import JsonStrictValidator
 34
 35if TYPE_CHECKING:
 36    from cyclonedx.output.json import Json as JsonOutputter
 37    from cyclonedx.output.xml import Xml as XmlOutputter
 38    from cyclonedx.validation.xml import XmlValidator
 39
 40
 41lc_factory = LicenseFactory()
 42
 43# region build the BOM
 44
 45bom = Bom()
 46bom.metadata.component = root_component = Component(
 47    name='myApp',
 48    type=ComponentType.APPLICATION,
 49    licenses=[lc_factory.make_from_string('MIT')],
 50    bom_ref='myApp',
 51)
 52
 53component1 = Component(
 54    type=ComponentType.LIBRARY,
 55    name='some-component',
 56    group='acme',
 57    version='1.33.7-beta.1',
 58    licenses=[lc_factory.make_from_string('(c) 2021 Acme inc.')],
 59    supplier=OrganizationalEntity(
 60        name='Acme Inc',
 61        urls=[XsUri('https://www.acme.org')]
 62    ),
 63    bom_ref='myComponent@1.33.7-beta.1',
 64    purl=PackageURL('generic', 'acme', 'some-component', '1.33.7-beta.1')
 65)
 66bom.components.add(component1)
 67bom.register_dependency(root_component, [component1])
 68
 69component2 = Component(
 70    type=ComponentType.LIBRARY,
 71    name='some-library',
 72    licenses=[lc_factory.make_from_string('GPL-3.0-only WITH Classpath-exception-2.0')]
 73)
 74bom.components.add(component2)
 75bom.register_dependency(component1, [component2])
 76
 77# endregion build the BOM
 78
 79# region JSON
 80"""demo with explicit instructions for SchemaVersion, outputter and validator"""
 81
 82my_json_outputter: 'JsonOutputter' = JsonV1Dot5(bom)
 83serialized_json = my_json_outputter.output_as_string(indent=2)
 84print(serialized_json)
 85my_json_validator = JsonStrictValidator(SchemaVersion.V1_6)
 86try:
 87    validation_errors = my_json_validator.validate_str(serialized_json)
 88    if validation_errors:
 89        print('JSON invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
 90        sys.exit(2)
 91    print('JSON valid')
 92except MissingOptionalDependencyException as error:
 93    print('JSON-validation was skipped due to', error)
 94
 95# endregion JSON
 96
 97print('', '=' * 30, '', sep='\n')
 98
 99# region XML
100"""demo with implicit instructions for SchemaVersion, outputter and validator. TypeCheckers will catch errors."""
101
102my_xml_outputter: 'XmlOutputter' = make_outputter(bom, OutputFormat.XML, SchemaVersion.V1_6)
103serialized_xml = my_xml_outputter.output_as_string(indent=2)
104print(serialized_xml)
105my_xml_validator: 'XmlValidator' = make_schemabased_validator(
106    my_xml_outputter.output_format, my_xml_outputter.schema_version)
107try:
108    validation_errors = my_xml_validator.validate_str(serialized_xml)
109    if validation_errors:
110        print('XML invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
111        sys.exit(2)
112    print('XML valid')
113except MissingOptionalDependencyException as error:
114    print('XML-validation was skipped due to', error)
115
116# endregion XML

Complex Deserialize

  1# This file is part of CycloneDX Python Lib
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14#
 15# SPDX-License-Identifier: Apache-2.0
 16# Copyright (c) OWASP Foundation. All Rights Reserved.
 17
 18import sys
 19from json import loads as json_loads
 20from typing import TYPE_CHECKING
 21
 22from defusedxml import ElementTree as SafeElementTree  # type:ignore[import-untyped]
 23
 24from cyclonedx.exception import MissingOptionalDependencyException
 25from cyclonedx.model.bom import Bom
 26from cyclonedx.schema import OutputFormat, SchemaVersion
 27from cyclonedx.validation import make_schemabased_validator
 28from cyclonedx.validation.json import JsonStrictValidator
 29
 30if TYPE_CHECKING:
 31    from cyclonedx.validation.xml import XmlValidator
 32
 33# region JSON
 34
 35json_data = """{
 36  "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
 37  "bomFormat": "CycloneDX",
 38  "specVersion": "1.6",
 39  "serialNumber": "urn:uuid:88fabcfa-7529-4ba2-8256-29bec0c03900",
 40  "version": 1,
 41  "metadata": {
 42    "timestamp": "2024-02-10T21:38:53.313120+00:00",
 43    "tools": [
 44      {
 45        "vendor": "CycloneDX",
 46        "name": "cyclonedx-python-lib",
 47        "version": "6.4.1",
 48        "externalReferences": [
 49          {
 50            "type": "build-system",
 51            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions"
 52          },
 53          {
 54            "type": "distribution",
 55            "url": "https://pypi.org/project/cyclonedx-python-lib/"
 56          },
 57          {
 58            "type": "documentation",
 59            "url": "https://cyclonedx-python-library.readthedocs.io/"
 60          },
 61          {
 62            "type": "issue-tracker",
 63            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues"
 64          },
 65          {
 66            "type": "license",
 67            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE"
 68          },
 69          {
 70            "type": "release-notes",
 71            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md"
 72          },
 73          {
 74            "type": "vcs",
 75            "url": "https://github.com/CycloneDX/cyclonedx-python-lib"
 76          },
 77          {
 78            "type": "website",
 79            "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme"
 80          }
 81        ]
 82      }
 83    ],
 84    "component": {
 85      "bom-ref": "myApp",
 86      "name": "myApp",
 87      "type": "application",
 88      "licenses": [
 89        {
 90          "license": {
 91            "id": "MIT"
 92          }
 93        }
 94      ]
 95    }
 96  },
 97  "components": [
 98    {
 99      "bom-ref": "myComponent@1.33.7-beta.1",
100      "type": "library",
101      "group": "acme",
102      "name": "some-component",
103      "version": "1.33.7-beta.1",
104      "purl": "pkg:generic/acme/some-component@1.33.7-beta.1",
105      "licenses": [
106        {
107          "license": {
108            "name": "(c) 2021 Acme inc."
109          }
110        }
111      ],
112      "supplier": {
113        "name": "Acme Inc",
114        "url": [
115          "https://www.acme.org"
116        ]
117      }
118    },
119    {
120      "bom-ref": "some-lib",
121      "type": "library",
122      "name": "some-library",
123      "licenses": [
124        {
125          "expression": "GPL-3.0-only WITH Classpath-exception-2.0"
126        }
127      ]
128    }
129  ],
130  "dependencies": [
131    {
132      "ref": "some-lib"
133    },
134    {
135      "dependsOn": [
136        "myComponent@1.33.7-beta.1"
137      ],
138      "ref": "myApp"
139    },
140    {
141      "dependsOn": [
142        "some-lib"
143      ],
144      "ref": "myComponent@1.33.7-beta.1"
145    }
146  ]
147}"""
148my_json_validator = JsonStrictValidator(SchemaVersion.V1_6)
149try:
150    validation_errors = my_json_validator.validate_str(json_data)
151    if validation_errors:
152        print('JSON invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
153        sys.exit(2)
154    print('JSON valid')
155except MissingOptionalDependencyException as error:
156    print('JSON-validation was skipped due to', error)
157bom_from_json = Bom.from_json(  # type: ignore[attr-defined]
158    json_loads(json_data))
159print('bom_from_json', repr(bom_from_json))
160
161# endregion JSON
162
163print('', '=' * 30, '', sep='\n')
164
165# endregion XML
166
167xml_data = """<?xml version="1.0" ?>
168<bom xmlns="http://cyclonedx.org/schema/bom/1.6"
169  serialNumber="urn:uuid:88fabcfa-7529-4ba2-8256-29bec0c03900"
170  version="1"
171>
172  <metadata>
173    <timestamp>2024-02-10T21:38:53.313120+00:00</timestamp>
174    <tools>
175      <tool>
176        <vendor>CycloneDX</vendor>
177        <name>cyclonedx-python-lib</name>
178        <version>6.4.1</version>
179        <externalReferences>
180          <reference type="build-system">
181            <url>https://github.com/CycloneDX/cyclonedx-python-lib/actions</url>
182          </reference>
183          <reference type="distribution">
184            <url>https://pypi.org/project/cyclonedx-python-lib/</url>
185          </reference>
186          <reference type="documentation">
187            <url>https://cyclonedx-python-library.readthedocs.io/</url>
188          </reference>
189          <reference type="issue-tracker">
190            <url>https://github.com/CycloneDX/cyclonedx-python-lib/issues</url>
191          </reference>
192          <reference type="license">
193            <url>https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE</url>
194          </reference>
195          <reference type="release-notes">
196            <url>https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md</url>
197          </reference>
198          <reference type="vcs">
199            <url>https://github.com/CycloneDX/cyclonedx-python-lib</url>
200          </reference>
201          <reference type="website">
202            <url>https://github.com/CycloneDX/cyclonedx-python-lib/#readme</url>
203          </reference>
204        </externalReferences>
205      </tool>
206    </tools>
207    <component type="application" bom-ref="myApp">
208      <name>myApp</name>
209      <licenses>
210        <license>
211          <id>MIT</id>
212        </license>
213      </licenses>
214    </component>
215  </metadata>
216  <components>
217    <component type="library" bom-ref="myComponent@1.33.7-beta.1">
218      <supplier>
219        <name>Acme Inc</name>
220        <url>https://www.acme.org</url>
221      </supplier>
222      <group>acme</group>
223      <name>some-component</name>
224      <version>1.33.7-beta.1</version>
225      <licenses>
226        <license>
227          <name>(c) 2021 Acme inc.</name>
228        </license>
229      </licenses>
230      <purl>pkg:generic/acme/some-component@1.33.7-beta.1</purl>
231    </component>
232    <component type="library" bom-ref="some-lib">
233      <name>some-library</name>
234      <licenses>
235        <expression>GPL-3.0-only WITH Classpath-exception-2.0</expression>
236      </licenses>
237    </component>
238  </components>
239  <dependencies>
240    <dependency ref="some-lib"/>
241    <dependency ref="myApp">
242      <dependency ref="myComponent@1.33.7-beta.1"/>
243    </dependency>
244    <dependency ref="myComponent@1.33.7-beta.1">
245      <dependency ref="some-lib"/>
246    </dependency>
247  </dependencies>
248</bom>"""
249my_xml_validator: 'XmlValidator' = make_schemabased_validator(OutputFormat.XML, SchemaVersion.V1_6)
250try:
251    validation_errors = my_xml_validator.validate_str(xml_data)
252    if validation_errors:
253        print('XML invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr)
254        sys.exit(2)
255    print('XML valid')
256except MissingOptionalDependencyException as error:
257    print('XML-validation was skipped due to', error)
258bom_from_xml = Bom.from_xml(  # type: ignore[attr-defined]
259    SafeElementTree.fromstring(xml_data))
260print('bom_from_xml', repr(bom_from_xml))
261
262# endregion XML
263
264print('', '=' * 30, '', sep='\n')
265
266print('assert bom_from_json equals bom_from_xml')
267assert bom_from_json == bom_from_xml, 'expected to have equal BOMs from JSON and XML'