Skip to content

Commit 8b2a07d

Browse files
feat!: drop support for Python <3.9 (#809)
Python 3.8 is end-of-life. --------- Signed-off-by: Simoh23999 <simocasmina@gmail.com> Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com> Co-authored-by: Jan Kowalleck <jan.kowalleck@gmail.com>
1 parent d6a87c5 commit 8b2a07d

Some content is hidden

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

43 files changed

+154
-150
lines changed

.github/workflows/python.yml

+4-5
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ jobs:
8686
toxenv-factors: '-current'
8787
- # test with the lowest dependencies
8888
os: ubuntu-latest
89-
python-version: '3.8'
89+
python-version: '3.9'
9090
toxenv-factors: '-lowest'
9191
steps:
9292
- name: Checkout
@@ -117,15 +117,14 @@ jobs:
117117
matrix:
118118
os:
119119
- ubuntu-latest
120-
- macos-13 # macos-latest might be incompatible to py38 - see https://github.com/CycloneDX/cyclonedx-python-lib/pull/599#issuecomment-2077462142
120+
- macos-13 # macos-latest might be incompatible to py310 - see https://github.com/CycloneDX/cyclonedx-python-lib/pull/599#issuecomment-2077462142
121121
- windows-latest
122122
python-version:
123123
- "3.13" # highest supported
124124
- "3.12"
125125
- "3.11"
126126
- "3.10"
127-
- "3.9"
128-
- "3.8" # lowest supported
127+
- "3.9" # lowest supported
129128
toxenv-factors:
130129
- '-allExtras'
131130
- '-noExtras'
@@ -219,7 +218,7 @@ jobs:
219218
# see https://github.com/actions/setup-python
220219
uses: actions/setup-python@v5
221220
with:
222-
python-version: '>=3.8 <=3.13' # supported version range
221+
python-version: '>=3.9 <=3.13' # supported version range
223222
- name: Validate Python Environment
224223
shell: python
225224
run: |

cyclonedx/_internal/compare.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
"""
2323

2424
from itertools import zip_longest
25-
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
25+
from typing import TYPE_CHECKING, Any, Optional
2626

2727
if TYPE_CHECKING: # pragma: no cover
2828
from packageurl import PackageURL
2929

3030

31-
class ComparableTuple(Tuple[Optional[Any], ...]):
31+
class ComparableTuple(tuple[Optional[Any], ...]):
3232
"""
3333
Allows comparison of tuples, allowing for None values.
3434
"""
@@ -63,7 +63,7 @@ class ComparableDict(ComparableTuple):
6363
Allows comparison of dictionaries, allowing for missing/None values.
6464
"""
6565

66-
def __new__(cls, d: Dict[Any, Any]) -> 'ComparableDict':
66+
def __new__(cls, d: dict[Any, Any]) -> 'ComparableDict':
6767
return super(ComparableDict, cls).__new__(cls, sorted(d.items()))
6868

6969

cyclonedx/model/__init__.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
"""
2424

2525
import re
26+
from collections.abc import Generator, Iterable
2627
from datetime import datetime
2728
from enum import Enum
2829
from functools import reduce
2930
from json import loads as json_loads
30-
from typing import Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Tuple, Type, Union
31+
from typing import Any, Optional, Union
3132
from urllib.parse import quote as url_quote
3233
from uuid import UUID
3334
from warnings import warn
@@ -280,7 +281,7 @@ class HashAlgorithm(str, Enum):
280281
class _HashTypeRepositorySerializationHelper(serializable.helpers.BaseHelper):
281282
""" THIS CLASS IS NON-PUBLIC API """
282283

283-
__CASES: Dict[Type[serializable.ViewType], FrozenSet[HashAlgorithm]] = dict()
284+
__CASES: dict[type[serializable.ViewType], frozenset[HashAlgorithm]] = dict()
284285
__CASES[SchemaVersion1Dot0] = frozenset({
285286
HashAlgorithm.MD5,
286287
HashAlgorithm.SHA_1,
@@ -304,7 +305,7 @@ class _HashTypeRepositorySerializationHelper(serializable.helpers.BaseHelper):
304305
__CASES[SchemaVersion1Dot6] = __CASES[SchemaVersion1Dot5]
305306

306307
@classmethod
307-
def __prep(cls, hts: Iterable['HashType'], view: Type[serializable.ViewType]) -> Generator['HashType', None, None]:
308+
def __prep(cls, hts: Iterable['HashType'], view: type[serializable.ViewType]) -> Generator['HashType', None, None]:
308309
cases = cls.__CASES.get(view, ())
309310
for ht in hts:
310311
if ht.alg in cases:
@@ -315,8 +316,8 @@ def __prep(cls, hts: Iterable['HashType'], view: Type[serializable.ViewType]) ->
315316

316317
@classmethod
317318
def json_normalize(cls, o: Iterable['HashType'], *,
318-
view: Optional[Type[serializable.ViewType]],
319-
**__: Any) -> List[Any]:
319+
view: Optional[type[serializable.ViewType]],
320+
**__: Any) -> list[Any]:
320321
assert view is not None
321322
return [
322323
json_loads(
@@ -328,7 +329,7 @@ def json_normalize(cls, o: Iterable['HashType'], *,
328329
@classmethod
329330
def xml_normalize(cls, o: Iterable['HashType'], *,
330331
element_name: str,
331-
view: Optional[Type[serializable.ViewType]],
332+
view: Optional[type[serializable.ViewType]],
332333
xmlns: Optional[str],
333334
**__: Any) -> XmlElement:
334335
assert view is not None
@@ -342,7 +343,7 @@ def xml_normalize(cls, o: Iterable['HashType'], *,
342343

343344
@classmethod
344345
def json_denormalize(cls, o: Any,
345-
**__: Any) -> List['HashType']:
346+
**__: Any) -> list['HashType']:
346347
return [
347348
HashType.from_json( # type:ignore[attr-defined]
348349
ht) for ht in o
@@ -351,14 +352,14 @@ def json_denormalize(cls, o: Any,
351352
@classmethod
352353
def xml_denormalize(cls, o: 'XmlElement', *,
353354
default_ns: Optional[str],
354-
**__: Any) -> List['HashType']:
355+
**__: Any) -> list['HashType']:
355356
return [
356357
HashType.from_xml( # type:ignore[attr-defined]
357358
ht, default_ns) for ht in o
358359
]
359360

360361

361-
_MAP_HASHLIB: Dict[str, HashAlgorithm] = {
362+
_MAP_HASHLIB: dict[str, HashAlgorithm] = {
362363
# from hashlib.algorithms_guaranteed
363364
'md5': HashAlgorithm.MD5,
364365
'sha1': HashAlgorithm.SHA_1,
@@ -593,7 +594,7 @@ class ExternalReferenceType(str, Enum):
593594
class _ExternalReferenceSerializationHelper(serializable.helpers.BaseHelper):
594595
""" THIS CLASS IS NON-PUBLIC API """
595596

596-
__CASES: Dict[Type[serializable.ViewType], FrozenSet[ExternalReferenceType]] = dict()
597+
__CASES: dict[type[serializable.ViewType], frozenset[ExternalReferenceType]] = dict()
597598
__CASES[SchemaVersion1Dot1] = frozenset({
598599
ExternalReferenceType.VCS,
599600
ExternalReferenceType.ISSUE_TRACKER,
@@ -649,7 +650,7 @@ class _ExternalReferenceSerializationHelper(serializable.helpers.BaseHelper):
649650
}
650651

651652
@classmethod
652-
def __normalize(cls, extref: ExternalReferenceType, view: Type[serializable.ViewType]) -> str:
653+
def __normalize(cls, extref: ExternalReferenceType, view: type[serializable.ViewType]) -> str:
653654
return (
654655
extref
655656
if extref in cls.__CASES.get(view, ())
@@ -658,14 +659,14 @@ def __normalize(cls, extref: ExternalReferenceType, view: Type[serializable.View
658659

659660
@classmethod
660661
def json_normalize(cls, o: Any, *,
661-
view: Optional[Type[serializable.ViewType]],
662+
view: Optional[type[serializable.ViewType]],
662663
**__: Any) -> str:
663664
assert view is not None
664665
return cls.__normalize(o, view)
665666

666667
@classmethod
667668
def xml_normalize(cls, o: Any, *,
668-
view: Optional[Type[serializable.ViewType]],
669+
view: Optional[type[serializable.ViewType]],
669670
**__: Any) -> str:
670671
assert view is not None
671672
return cls.__normalize(o, view)
@@ -703,7 +704,7 @@ class XsUri(serializable.helpers.BaseHelper):
703704
)
704705

705706
@staticmethod
706-
def __spec_replace(v: str, r: Tuple[str, str]) -> str:
707+
def __spec_replace(v: str, r: tuple[str, str]) -> str:
707708
return v.replace(*r)
708709

709710
@classmethod

cyclonedx/model/bom.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818

19+
from collections.abc import Generator, Iterable
1920
from datetime import datetime
2021
from itertools import chain
21-
from typing import TYPE_CHECKING, Generator, Iterable, Optional, Union
22+
from typing import TYPE_CHECKING, Optional, Union
2223
from uuid import UUID, uuid4
2324
from warnings import warn
2425

cyclonedx/model/bom_ref.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException
2424

2525
if TYPE_CHECKING: # pragma: no cover
26-
from typing import Type, TypeVar
26+
from typing import TypeVar
2727

2828
_T_BR = TypeVar('_T_BR', bound='BomRef')
2929

@@ -90,7 +90,7 @@ def serialize(cls, o: Any) -> Optional[str]:
9090
f'Attempt to serialize a non-BomRef: {o!r}')
9191

9292
@classmethod
93-
def deserialize(cls: 'Type[_T_BR]', o: Any) -> '_T_BR':
93+
def deserialize(cls: 'type[_T_BR]', o: Any) -> '_T_BR':
9494
try:
9595
return cls(value=str(o))
9696
except ValueError as err:

cyclonedx/model/component.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
import re
19+
from collections.abc import Iterable
1920
from enum import Enum
2021
from os.path import exists
21-
from typing import Any, Dict, FrozenSet, Iterable, Optional, Set, Type, Union
22+
from typing import Any, Optional, Union
2223
from warnings import warn
2324

2425
# See https://github.com/package-url/packageurl-python/issues/65
@@ -309,7 +310,7 @@ class ComponentScope(str, Enum):
309310
class _ComponentScopeSerializationHelper(serializable.helpers.BaseHelper):
310311
""" THIS CLASS IS NON-PUBLIC API """
311312

312-
__CASES: Dict[Type[serializable.ViewType], FrozenSet[ComponentScope]] = dict()
313+
__CASES: dict[type[serializable.ViewType], frozenset[ComponentScope]] = dict()
313314
__CASES[SchemaVersion1Dot0] = frozenset({
314315
ComponentScope.REQUIRED,
315316
ComponentScope.OPTIONAL,
@@ -324,21 +325,21 @@ class _ComponentScopeSerializationHelper(serializable.helpers.BaseHelper):
324325
__CASES[SchemaVersion1Dot6] = __CASES[SchemaVersion1Dot5]
325326

326327
@classmethod
327-
def __normalize(cls, cs: ComponentScope, view: Type[serializable.ViewType]) -> Optional[str]:
328+
def __normalize(cls, cs: ComponentScope, view: type[serializable.ViewType]) -> Optional[str]:
328329
return cs.value \
329330
if cs in cls.__CASES.get(view, ()) \
330331
else None
331332

332333
@classmethod
333334
def json_normalize(cls, o: Any, *,
334-
view: Optional[Type[serializable.ViewType]],
335+
view: Optional[type[serializable.ViewType]],
335336
**__: Any) -> Optional[str]:
336337
assert view is not None
337338
return cls.__normalize(o, view)
338339

339340
@classmethod
340341
def xml_normalize(cls, o: Any, *,
341-
view: Optional[Type[serializable.ViewType]],
342+
view: Optional[type[serializable.ViewType]],
342343
**__: Any) -> Optional[str]:
343344
assert view is not None
344345
return cls.__normalize(o, view)
@@ -375,7 +376,7 @@ class ComponentType(str, Enum):
375376
class _ComponentTypeSerializationHelper(serializable.helpers.BaseHelper):
376377
""" THIS CLASS IS NON-PUBLIC API """
377378

378-
__CASES: Dict[Type[serializable.ViewType], FrozenSet[ComponentType]] = dict()
379+
__CASES: dict[type[serializable.ViewType], frozenset[ComponentType]] = dict()
379380
__CASES[SchemaVersion1Dot0] = frozenset({
380381
ComponentType.APPLICATION,
381382
ComponentType.DEVICE,
@@ -403,21 +404,21 @@ class _ComponentTypeSerializationHelper(serializable.helpers.BaseHelper):
403404
}
404405

405406
@classmethod
406-
def __normalize(cls, ct: ComponentType, view: Type[serializable.ViewType]) -> Optional[str]:
407+
def __normalize(cls, ct: ComponentType, view: type[serializable.ViewType]) -> Optional[str]:
407408
if ct in cls.__CASES.get(view, ()):
408409
return ct.value
409410
raise SerializationOfUnsupportedComponentTypeException(f'unsupported {ct!r} for view {view!r}')
410411

411412
@classmethod
412413
def json_normalize(cls, o: Any, *,
413-
view: Optional[Type[serializable.ViewType]],
414+
view: Optional[type[serializable.ViewType]],
414415
**__: Any) -> Optional[str]:
415416
assert view is not None
416417
return cls.__normalize(o, view)
417418

418419
@classmethod
419420
def xml_normalize(cls, o: Any, *,
420-
view: Optional[Type[serializable.ViewType]],
421+
view: Optional[type[serializable.ViewType]],
421422
**__: Any) -> Optional[str]:
422423
assert view is not None
423424
return cls.__normalize(o, view)
@@ -1734,7 +1735,7 @@ def tags(self) -> 'SortedSet[str]':
17341735
def tags(self, tags: Iterable[str]) -> None:
17351736
self._tags = SortedSet(tags)
17361737

1737-
def get_all_nested_components(self, include_self: bool = False) -> Set['Component']:
1738+
def get_all_nested_components(self, include_self: bool = False) -> set['Component']:
17381739
components = set()
17391740
if include_self:
17401741
components.add(self)

cyclonedx/model/contact.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818

19-
from typing import Any, Iterable, Optional, Union
19+
from collections.abc import Iterable
20+
from typing import Any, Optional, Union
2021

2122
import py_serializable as serializable
2223
from sortedcontainers import SortedSet

cyclonedx/model/crypto.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
See the CycloneDX Schema for hashType: https://cyclonedx.org/docs/1.6/#type_cryptoPropertiesType
2626
"""
2727

28+
from collections.abc import Iterable
2829
from datetime import datetime
2930
from enum import Enum
30-
from typing import Any, Iterable, Optional
31+
from typing import Any, Optional
3132

3233
import py_serializable as serializable
3334
from sortedcontainers import SortedSet

cyclonedx/model/definition.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
import re
19-
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union
19+
from collections.abc import Iterable
20+
from typing import TYPE_CHECKING, Any, Optional, Union
2021

2122
import py_serializable as serializable
2223
from sortedcontainers import SortedSet
@@ -29,7 +30,7 @@
2930
from .bom_ref import BomRef
3031

3132
if TYPE_CHECKING: # pragma: no cover
32-
from typing import Type, TypeVar
33+
from typing import TypeVar
3334

3435
_T_CreId = TypeVar('_T_CreId', bound='CreId')
3536

@@ -65,7 +66,7 @@ def serialize(cls, o: Any) -> str:
6566
f'Attempt to serialize a non-CreId: {o!r}')
6667

6768
@classmethod
68-
def deserialize(cls: 'Type[_T_CreId]', o: Any) -> '_T_CreId':
69+
def deserialize(cls: 'type[_T_CreId]', o: Any) -> '_T_CreId':
6970
return cls(id=str(o))
7071

7172
def __eq__(self, other: Any) -> bool:

cyclonedx/model/dependency.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818

1919
from abc import ABC, abstractmethod
20-
from typing import Any, Iterable, List, Optional, Set
20+
from collections.abc import Iterable
21+
from typing import Any, Optional
2122

2223
import py_serializable as serializable
2324
from sortedcontainers import SortedSet
@@ -31,14 +32,14 @@ class _DependencyRepositorySerializationHelper(serializable.helpers.BaseHelper):
3132
""" THIS CLASS IS NON-PUBLIC API """
3233

3334
@classmethod
34-
def serialize(cls, o: Any) -> List[str]:
35+
def serialize(cls, o: Any) -> list[str]:
3536
if isinstance(o, (SortedSet, set)):
3637
return [str(i.ref) for i in o]
3738
raise SerializationOfUnexpectedValueException(
3839
f'Attempt to serialize a non-DependencyRepository: {o!r}')
3940

4041
@classmethod
41-
def deserialize(cls, o: Any) -> Set['Dependency']:
42+
def deserialize(cls, o: Any) -> set['Dependency']:
4243
dependencies = set()
4344
if isinstance(o, list):
4445
for v in o:
@@ -80,7 +81,7 @@ def dependencies(self) -> 'SortedSet[Dependency]':
8081
def dependencies(self, dependencies: Iterable['Dependency']) -> None:
8182
self._dependencies = SortedSet(dependencies)
8283

83-
def dependencies_as_bom_refs(self) -> Set[BomRef]:
84+
def dependencies_as_bom_refs(self) -> set[BomRef]:
8485
return set(map(lambda d: d.ref, self.dependencies))
8586

8687
def __comparable_tuple(self) -> _ComparableTuple:

cyclonedx/model/issue.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
# SPDX-License-Identifier: Apache-2.0
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

18+
from collections.abc import Iterable
1819
from enum import Enum
19-
from typing import Any, Iterable, Optional
20+
from typing import Any, Optional
2021

2122
import py_serializable as serializable
2223
from sortedcontainers import SortedSet

0 commit comments

Comments
 (0)