Skip to content

Commit 8c4082e

Browse files
authored
Merge branch 'CycloneDX:main' into main
2 parents 4197b8f + c5efc8c commit 8c4082e

File tree

76 files changed

+1619
-58
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1619
-58
lines changed

.github/workflows/python.yml

+9-8
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ name: Python CI
44

55
on:
66
push:
7-
branches: ["main"]
7+
branches: ["main", "next"]
8+
tags: [ 'v*' ]
89
pull_request:
9-
branches-ignore: ['dependabot/**']
1010
workflow_dispatch:
1111
schedule:
12-
# schedule weekly tests, since some dependencies are not intended to be pinned
13-
# this means: at 23:42 on Fridays
14-
- cron: '42 23 * * 5'
12+
# schedule daily tests, since some dependencies are not intended to be pinned
13+
# this means: at 23:42 every day
14+
- cron: '42 23 * * *'
1515

1616
concurrency:
1717
group: ${{ github.workflow }}-${{ github.ref }}
@@ -82,7 +82,7 @@ jobs:
8282
include:
8383
- # test with the latest dependencies
8484
os: ubuntu-latest
85-
python-version: '3.12'
85+
python-version: '3.13'
8686
toxenv-factors: '-current'
8787
- # test with the lowest dependencies
8888
os: ubuntu-latest
@@ -117,7 +117,8 @@ jobs:
117117
matrix:
118118
os: ['ubuntu-latest', 'windows-latest', 'macos-13']
119119
python-version:
120-
- "3.12" # highest supported
120+
- "3.13" # highest supported
121+
- "3.12"
121122
- "3.11"
122123
- "3.10"
123124
- "3.9"
@@ -215,7 +216,7 @@ jobs:
215216
# see https://github.com/actions/setup-python
216217
uses: actions/setup-python@v5
217218
with:
218-
python-version: '>=3.8 <=3.12' # supported version range
219+
python-version: '>=3.8 <=3.13' # supported version range
219220
- name: Validate Python Environment
220221
shell: python
221222
run: |

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ repos:
44
hooks:
55
- id: system
66
name: mypy
7-
entry: poetry run tox -e mypy-locked
7+
entry: poetry run tox -e mypy-current
88
pass_filenames: false
99
language: system
1010
- repo: local

CHANGELOG.md

+61
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,67 @@
22

33

44

5+
## v8.3.0 (2024-10-26)
6+
7+
### Documentation
8+
9+
* docs: revisit examples readme (#725)
10+
11+
Signed-off-by: Jan Kowalleck &lt;[email protected]&gt; ([`e9020f0`](https://github.com/CycloneDX/cyclonedx-python-lib/commit/e9020f0b709a5245d1749d2811b8568f892869bb))
12+
13+
### Feature
14+
15+
* feat: add basic support for Definitions (#701)
16+
17+
18+
19+
---------
20+
21+
Signed-off-by: Hakan Dilek &lt;[email protected]&gt; ([`a1573e5`](https://github.com/CycloneDX/cyclonedx-python-lib/commit/a1573e5af12bb54c7328c73971dc2c2f8d820c0a))
22+
23+
24+
## v8.2.1 (2024-10-24)
25+
26+
### Fix
27+
28+
* fix: encode quotation mark in URL (#724)
29+
30+
Signed-off-by: Jan Kowalleck &lt;[email protected]&gt; ([`a7c7c97`](https://github.com/CycloneDX/cyclonedx-python-lib/commit/a7c7c97c37ee1c7988c028aa779f74893f858c7b))
31+
32+
33+
## v8.2.0 (2024-10-22)
34+
35+
### Feature
36+
37+
* feat: Add Python 3.13 support (#718)
38+
39+
Signed-off-by: gruebel &lt;[email protected]&gt; ([`d4be3ba`](https://github.com/CycloneDX/cyclonedx-python-lib/commit/d4be3ba6b3ccc65553a7dd10ad559c1eddfbb19b))
40+
41+
42+
## v8.1.0 (2024-10-21)
43+
44+
### Documentation
45+
46+
* docs: fix code examples regarding outputting (#709)
47+
48+
49+
50+
Signed-off-by: Hakan Dilek &lt;[email protected]&gt; ([`c72d5f4`](https://github.com/CycloneDX/cyclonedx-python-lib/commit/c72d5f483d5c1990fe643c4c25e37373d4d3248f))
51+
52+
### Feature
53+
54+
* feat: add support for Lifecycles in BOM metadata (#698)
55+
56+
57+
58+
---------
59+
60+
Signed-off-by: Johannes Feichtner &lt;[email protected]&gt;
61+
Signed-off-by: Jan Kowalleck &lt;[email protected]&gt;
62+
Signed-off-by: Johannes Feichtner &lt;[email protected]&gt;
63+
Co-authored-by: Jan Kowalleck &lt;[email protected]&gt; ([`6cfeb71`](https://github.com/CycloneDX/cyclonedx-python-lib/commit/6cfeb711f11aec8fa4d7be885f6797cc2eaa7e67))
64+
65+
566
## v8.0.0 (2024-10-14)
667

768
### Breaking

cyclonedx/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222

2323
# !! version is managed by semantic_release
2424
# do not use typing here, or else `semantic_release` might have issues finding the variable
25-
__version__ = "8.0.0" # noqa:Q000
25+
__version__ = "8.3.0" # noqa:Q000

cyclonedx/_internal/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@
2020
!!! ALL SYMBOLS IN HERE ARE INTERNAL.
2121
Everything might change without any notice.
2222
"""
23+
24+
# THIS FILE IS INTENDED TO BE EMPTY.
25+
# Put symbols in own modules/packages, not in this file!

cyclonedx/_internal/bom_ref.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# This file is part of CycloneDX Python Library
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+
18+
19+
"""
20+
!!! ALL SYMBOLS IN HERE ARE INTERNAL.
21+
Everything might change without any notice.
22+
"""
23+
24+
from typing import Literal, Optional, Union, overload
25+
26+
from ..model.bom_ref import BomRef
27+
28+
29+
@overload
30+
def bom_ref_from_str(bom_ref: BomRef, optional: bool = ...) -> BomRef:
31+
... # pragma: no cover
32+
33+
34+
@overload
35+
def bom_ref_from_str(bom_ref: Optional[str], optional: Literal[False] = False) -> BomRef:
36+
... # pragma: no cover
37+
38+
39+
@overload
40+
def bom_ref_from_str(bom_ref: Optional[str], optional: Literal[True] = ...) -> Optional[BomRef]:
41+
... # pragma: no cover
42+
43+
44+
def bom_ref_from_str(bom_ref: Optional[Union[str, BomRef]], optional: bool = False) -> Optional[BomRef]:
45+
if isinstance(bom_ref, BomRef):
46+
return bom_ref
47+
if bom_ref:
48+
return BomRef(value=str(bom_ref))
49+
return None \
50+
if optional \
51+
else BomRef()

cyclonedx/model/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,8 @@ class XsUri(serializable.helpers.BaseHelper):
689689

690690
__SPEC_REPLACEMENTS = (
691691
(' ', '%20'),
692+
('"', '%22'),
693+
("'", '%27'),
692694
('[', '%5B'),
693695
(']', '%5D'),
694696
('<', '%3C'),

cyclonedx/model/bom.py

+40-11
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
from .bom_ref import BomRef
4242
from .component import Component
4343
from .contact import OrganizationalContact, OrganizationalEntity
44+
from .definition import Definitions
4445
from .dependency import Dependable, Dependency
4546
from .license import License, LicenseExpression, LicenseRepository
47+
from .lifecycle import Lifecycle, LifecycleRepository, _LifecycleRepositoryHelper
4648
from .service import Service
4749
from .tool import Tool, ToolRepository, _ToolRepositoryHelper
4850
from .vulnerability import Vulnerability
@@ -70,6 +72,7 @@ def __init__(
7072
properties: Optional[Iterable[Property]] = None,
7173
timestamp: Optional[datetime] = None,
7274
manufacturer: Optional[OrganizationalEntity] = None,
75+
lifecycles: Optional[Iterable[Lifecycle]] = None,
7376
# Deprecated as of v1.6
7477
manufacture: Optional[OrganizationalEntity] = None,
7578
) -> None:
@@ -81,6 +84,7 @@ def __init__(
8184
self.licenses = licenses or [] # type:ignore[assignment]
8285
self.properties = properties or [] # type:ignore[assignment]
8386
self.manufacturer = manufacturer
87+
self.lifecycles = lifecycles or [] # type:ignore[assignment]
8488

8589
self.manufacture = manufacture
8690
if manufacture:
@@ -105,16 +109,23 @@ def timestamp(self) -> datetime:
105109
def timestamp(self, timestamp: datetime) -> None:
106110
self._timestamp = timestamp
107111

108-
# @property
109-
# ...
110-
# @serializable.view(SchemaVersion1Dot5)
111-
# @serializable.xml_sequence(2)
112-
# def lifecycles(self) -> ...:
113-
# ... # TODO since CDX1.5
114-
#
115-
# @lifecycles.setter
116-
# def lifecycles(self, ...) -> None:
117-
# ... # TODO since CDX1.5
112+
@property
113+
@serializable.view(SchemaVersion1Dot5)
114+
@serializable.view(SchemaVersion1Dot6)
115+
@serializable.type_mapping(_LifecycleRepositoryHelper)
116+
@serializable.xml_sequence(2)
117+
def lifecycles(self) -> LifecycleRepository:
118+
"""
119+
An optional list of BOM lifecycle stages.
120+
121+
Returns:
122+
Set of `Lifecycle`
123+
"""
124+
return self._lifecycles
125+
126+
@lifecycles.setter
127+
def lifecycles(self, lifecycles: Iterable[Lifecycle]) -> None:
128+
self._lifecycles = LifecycleRepository(lifecycles)
118129

119130
@property
120131
@serializable.type_mapping(_ToolRepositoryHelper)
@@ -290,7 +301,7 @@ def __eq__(self, other: object) -> bool:
290301
def __hash__(self) -> int:
291302
return hash((
292303
tuple(self.authors), self.component, tuple(self.licenses), self.manufacture, tuple(self.properties),
293-
self.supplier, self.timestamp, self.tools, self.manufacturer,
304+
tuple(self.lifecycles), self.supplier, self.timestamp, self.tools, self.manufacturer
294305
))
295306

296307
def __repr__(self) -> str:
@@ -317,6 +328,7 @@ def __init__(
317328
dependencies: Optional[Iterable[Dependency]] = None,
318329
vulnerabilities: Optional[Iterable[Vulnerability]] = None,
319330
properties: Optional[Iterable[Property]] = None,
331+
definitions: Optional[Definitions] = None,
320332
) -> None:
321333
"""
322334
Create a new Bom that you can manually/programmatically add data to later.
@@ -333,6 +345,7 @@ def __init__(
333345
self.vulnerabilities = vulnerabilities or [] # type:ignore[assignment]
334346
self.dependencies = dependencies or [] # type:ignore[assignment]
335347
self.properties = properties or [] # type:ignore[assignment]
348+
self.definitions = definitions or Definitions()
336349

337350
@property
338351
@serializable.type_mapping(UrnUuidHelper)
@@ -542,6 +555,22 @@ def vulnerabilities(self, vulnerabilities: Iterable[Vulnerability]) -> None:
542555
# def formulation(self, ...) -> None:
543556
# ... # TODO Since CDX 1.5
544557

558+
@property
559+
@serializable.view(SchemaVersion1Dot6)
560+
@serializable.xml_sequence(110)
561+
def definitions(self) -> Optional[Definitions]:
562+
"""
563+
The repository for definitions
564+
565+
Returns:
566+
`DefinitionRepository`
567+
"""
568+
return self._definitions if len(self._definitions.standards) > 0 else None
569+
570+
@definitions.setter
571+
def definitions(self, definitions: Definitions) -> None:
572+
self._definitions = definitions
573+
545574
def get_component_by_purl(self, purl: Optional['PackageURL']) -> Optional[Component]:
546575
"""
547576
Get a Component already in the Bom by its PURL

cyclonedx/model/component.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from packageurl import PackageURL
2828
from sortedcontainers import SortedSet
2929

30+
from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str
3031
from .._internal.compare import ComparablePackageURL as _ComparablePackageURL, ComparableTuple as _ComparableTuple
3132
from .._internal.hash import file_sha1sum as _file_sha1sum
3233
from ..exception.model import InvalidOmniBorIdException, InvalidSwhidException, NoPropertiesProvidedException
@@ -1098,10 +1099,7 @@ def __init__(
10981099
) -> None:
10991100
self.type = type
11001101
self.mime_type = mime_type
1101-
if isinstance(bom_ref, BomRef):
1102-
self._bom_ref = bom_ref
1103-
else:
1104-
self._bom_ref = BomRef(value=str(bom_ref) if bom_ref else None)
1102+
self._bom_ref = _bom_ref_from_str(bom_ref)
11051103
self.supplier = supplier
11061104
self.manufacturer = manufacturer
11071105
self.authors = authors or [] # type:ignore[assignment]

cyclonedx/model/contact.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import serializable
2222
from sortedcontainers import SortedSet
2323

24+
from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str
2425
from .._internal.compare import ComparableTuple as _ComparableTuple
2526
from ..exception.model import NoPropertiesProvidedException
2627
from ..schema.schema import SchemaVersion1Dot6
@@ -49,8 +50,7 @@ def __init__(
4950
postal_code: Optional[str] = None,
5051
street_address: Optional[str] = None,
5152
) -> None:
52-
self._bom_ref = bom_ref if isinstance(bom_ref, BomRef) else BomRef(
53-
value=bom_ref) if bom_ref else None
53+
self._bom_ref = _bom_ref_from_str(bom_ref, optional=True)
5454
self.country = country
5555
self.region = region
5656
self.locality = locality

0 commit comments

Comments
 (0)