diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 269f865c..d5186dfd 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -86,7 +86,7 @@ jobs: toxenv-factors: '-current' - # test with the lowest dependencies os: ubuntu-latest - python-version: '3.8' + python-version: '3.9' toxenv-factors: '-lowest' steps: - name: Checkout @@ -117,15 +117,14 @@ jobs: matrix: os: - ubuntu-latest - - macos-13 # macos-latest might be incompatible to py38 - see https://github.com/CycloneDX/cyclonedx-python-lib/pull/599#issuecomment-2077462142 + - macos-13 # macos-latest might be incompatible to py310 - see https://github.com/CycloneDX/cyclonedx-python-lib/pull/599#issuecomment-2077462142 - windows-latest python-version: - "3.13" # highest supported - "3.12" - "3.11" - "3.10" - - "3.9" - - "3.8" # lowest supported + - "3.9" # lowest supported toxenv-factors: - '-allExtras' - '-noExtras' @@ -219,7 +218,7 @@ jobs: # see https://github.com/actions/setup-python uses: actions/setup-python@v5 with: - python-version: '>=3.8 <=3.13' # supported version range + python-version: '>=3.9 <=3.13' # supported version range - name: Validate Python Environment shell: python run: | diff --git a/cyclonedx/_internal/compare.py b/cyclonedx/_internal/compare.py index bd64e692..e7544b3b 100644 --- a/cyclonedx/_internal/compare.py +++ b/cyclonedx/_internal/compare.py @@ -22,13 +22,13 @@ """ from itertools import zip_longest -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: # pragma: no cover from packageurl import PackageURL -class ComparableTuple(Tuple[Optional[Any], ...]): +class ComparableTuple(tuple[Optional[Any], ...]): """ Allows comparison of tuples, allowing for None values. """ @@ -63,7 +63,7 @@ class ComparableDict(ComparableTuple): Allows comparison of dictionaries, allowing for missing/None values. """ - def __new__(cls, d: Dict[Any, Any]) -> 'ComparableDict': + def __new__(cls, d: dict[Any, Any]) -> 'ComparableDict': return super(ComparableDict, cls).__new__(cls, sorted(d.items())) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index b2e40187..3677ebf2 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -23,11 +23,12 @@ """ import re +from collections.abc import Generator, Iterable from datetime import datetime from enum import Enum from functools import reduce from json import loads as json_loads -from typing import Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Tuple, Type, Union +from typing import Any, Optional, Union from urllib.parse import quote as url_quote from uuid import UUID from warnings import warn @@ -280,7 +281,7 @@ class HashAlgorithm(str, Enum): class _HashTypeRepositorySerializationHelper(serializable.helpers.BaseHelper): """ THIS CLASS IS NON-PUBLIC API """ - __CASES: Dict[Type[serializable.ViewType], FrozenSet[HashAlgorithm]] = dict() + __CASES: dict[type[serializable.ViewType], frozenset[HashAlgorithm]] = dict() __CASES[SchemaVersion1Dot0] = frozenset({ HashAlgorithm.MD5, HashAlgorithm.SHA_1, @@ -304,7 +305,7 @@ class _HashTypeRepositorySerializationHelper(serializable.helpers.BaseHelper): __CASES[SchemaVersion1Dot6] = __CASES[SchemaVersion1Dot5] @classmethod - def __prep(cls, hts: Iterable['HashType'], view: Type[serializable.ViewType]) -> Generator['HashType', None, None]: + def __prep(cls, hts: Iterable['HashType'], view: type[serializable.ViewType]) -> Generator['HashType', None, None]: cases = cls.__CASES.get(view, ()) for ht in hts: if ht.alg in cases: @@ -315,8 +316,8 @@ def __prep(cls, hts: Iterable['HashType'], view: Type[serializable.ViewType]) -> @classmethod def json_normalize(cls, o: Iterable['HashType'], *, - view: Optional[Type[serializable.ViewType]], - **__: Any) -> List[Any]: + view: Optional[type[serializable.ViewType]], + **__: Any) -> list[Any]: assert view is not None return [ json_loads( @@ -328,7 +329,7 @@ def json_normalize(cls, o: Iterable['HashType'], *, @classmethod def xml_normalize(cls, o: Iterable['HashType'], *, element_name: str, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], xmlns: Optional[str], **__: Any) -> XmlElement: assert view is not None @@ -342,7 +343,7 @@ def xml_normalize(cls, o: Iterable['HashType'], *, @classmethod def json_denormalize(cls, o: Any, - **__: Any) -> List['HashType']: + **__: Any) -> list['HashType']: return [ HashType.from_json( # type:ignore[attr-defined] ht) for ht in o @@ -351,14 +352,14 @@ def json_denormalize(cls, o: Any, @classmethod def xml_denormalize(cls, o: 'XmlElement', *, default_ns: Optional[str], - **__: Any) -> List['HashType']: + **__: Any) -> list['HashType']: return [ HashType.from_xml( # type:ignore[attr-defined] ht, default_ns) for ht in o ] -_MAP_HASHLIB: Dict[str, HashAlgorithm] = { +_MAP_HASHLIB: dict[str, HashAlgorithm] = { # from hashlib.algorithms_guaranteed 'md5': HashAlgorithm.MD5, 'sha1': HashAlgorithm.SHA_1, @@ -593,7 +594,7 @@ class ExternalReferenceType(str, Enum): class _ExternalReferenceSerializationHelper(serializable.helpers.BaseHelper): """ THIS CLASS IS NON-PUBLIC API """ - __CASES: Dict[Type[serializable.ViewType], FrozenSet[ExternalReferenceType]] = dict() + __CASES: dict[type[serializable.ViewType], frozenset[ExternalReferenceType]] = dict() __CASES[SchemaVersion1Dot1] = frozenset({ ExternalReferenceType.VCS, ExternalReferenceType.ISSUE_TRACKER, @@ -649,7 +650,7 @@ class _ExternalReferenceSerializationHelper(serializable.helpers.BaseHelper): } @classmethod - def __normalize(cls, extref: ExternalReferenceType, view: Type[serializable.ViewType]) -> str: + def __normalize(cls, extref: ExternalReferenceType, view: type[serializable.ViewType]) -> str: return ( extref if extref in cls.__CASES.get(view, ()) @@ -658,14 +659,14 @@ def __normalize(cls, extref: ExternalReferenceType, view: Type[serializable.View @classmethod def json_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> str: assert view is not None return cls.__normalize(o, view) @classmethod def xml_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> str: assert view is not None return cls.__normalize(o, view) @@ -703,7 +704,7 @@ class XsUri(serializable.helpers.BaseHelper): ) @staticmethod - def __spec_replace(v: str, r: Tuple[str, str]) -> str: + def __spec_replace(v: str, r: tuple[str, str]) -> str: return v.replace(*r) @classmethod diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 130074ee..7ed2dab7 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -16,9 +16,10 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Generator, Iterable from datetime import datetime from itertools import chain -from typing import TYPE_CHECKING, Generator, Iterable, Optional, Union +from typing import TYPE_CHECKING, Optional, Union from uuid import UUID, uuid4 from warnings import warn diff --git a/cyclonedx/model/bom_ref.py b/cyclonedx/model/bom_ref.py index cc4571a7..a1895d3e 100644 --- a/cyclonedx/model/bom_ref.py +++ b/cyclonedx/model/bom_ref.py @@ -23,7 +23,7 @@ from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException if TYPE_CHECKING: # pragma: no cover - from typing import Type, TypeVar + from typing import TypeVar _T_BR = TypeVar('_T_BR', bound='BomRef') @@ -90,7 +90,7 @@ def serialize(cls, o: Any) -> Optional[str]: f'Attempt to serialize a non-BomRef: {o!r}') @classmethod - def deserialize(cls: 'Type[_T_BR]', o: Any) -> '_T_BR': + def deserialize(cls: 'type[_T_BR]', o: Any) -> '_T_BR': try: return cls(value=str(o)) except ValueError as err: diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 28de1ea0..6e26947e 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -16,9 +16,10 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. import re +from collections.abc import Iterable from enum import Enum from os.path import exists -from typing import Any, Dict, FrozenSet, Iterable, Optional, Set, Type, Union +from typing import Any, Optional, Union from warnings import warn # See https://github.com/package-url/packageurl-python/issues/65 @@ -309,7 +310,7 @@ class ComponentScope(str, Enum): class _ComponentScopeSerializationHelper(serializable.helpers.BaseHelper): """ THIS CLASS IS NON-PUBLIC API """ - __CASES: Dict[Type[serializable.ViewType], FrozenSet[ComponentScope]] = dict() + __CASES: dict[type[serializable.ViewType], frozenset[ComponentScope]] = dict() __CASES[SchemaVersion1Dot0] = frozenset({ ComponentScope.REQUIRED, ComponentScope.OPTIONAL, @@ -324,21 +325,21 @@ class _ComponentScopeSerializationHelper(serializable.helpers.BaseHelper): __CASES[SchemaVersion1Dot6] = __CASES[SchemaVersion1Dot5] @classmethod - def __normalize(cls, cs: ComponentScope, view: Type[serializable.ViewType]) -> Optional[str]: + def __normalize(cls, cs: ComponentScope, view: type[serializable.ViewType]) -> Optional[str]: return cs.value \ if cs in cls.__CASES.get(view, ()) \ else None @classmethod def json_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> Optional[str]: assert view is not None return cls.__normalize(o, view) @classmethod def xml_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> Optional[str]: assert view is not None return cls.__normalize(o, view) @@ -375,7 +376,7 @@ class ComponentType(str, Enum): class _ComponentTypeSerializationHelper(serializable.helpers.BaseHelper): """ THIS CLASS IS NON-PUBLIC API """ - __CASES: Dict[Type[serializable.ViewType], FrozenSet[ComponentType]] = dict() + __CASES: dict[type[serializable.ViewType], frozenset[ComponentType]] = dict() __CASES[SchemaVersion1Dot0] = frozenset({ ComponentType.APPLICATION, ComponentType.DEVICE, @@ -403,21 +404,21 @@ class _ComponentTypeSerializationHelper(serializable.helpers.BaseHelper): } @classmethod - def __normalize(cls, ct: ComponentType, view: Type[serializable.ViewType]) -> Optional[str]: + def __normalize(cls, ct: ComponentType, view: type[serializable.ViewType]) -> Optional[str]: if ct in cls.__CASES.get(view, ()): return ct.value raise SerializationOfUnsupportedComponentTypeException(f'unsupported {ct!r} for view {view!r}') @classmethod def json_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> Optional[str]: assert view is not None return cls.__normalize(o, view) @classmethod def xml_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> Optional[str]: assert view is not None return cls.__normalize(o, view) @@ -1734,7 +1735,7 @@ def tags(self) -> 'SortedSet[str]': def tags(self, tags: Iterable[str]) -> None: self._tags = SortedSet(tags) - def get_all_nested_components(self, include_self: bool = False) -> Set['Component']: + def get_all_nested_components(self, include_self: bool = False) -> set['Component']: components = set() if include_self: components.add(self) diff --git a/cyclonedx/model/contact.py b/cyclonedx/model/contact.py index cea865e7..3a4704b4 100644 --- a/cyclonedx/model/contact.py +++ b/cyclonedx/model/contact.py @@ -16,7 +16,8 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Any, Iterable, Optional, Union +from collections.abc import Iterable +from typing import Any, Optional, Union import py_serializable as serializable from sortedcontainers import SortedSet diff --git a/cyclonedx/model/crypto.py b/cyclonedx/model/crypto.py index 765e840b..b7557c77 100644 --- a/cyclonedx/model/crypto.py +++ b/cyclonedx/model/crypto.py @@ -25,9 +25,10 @@ See the CycloneDX Schema for hashType: https://cyclonedx.org/docs/1.6/#type_cryptoPropertiesType """ +from collections.abc import Iterable from datetime import datetime from enum import Enum -from typing import Any, Iterable, Optional +from typing import Any, Optional import py_serializable as serializable from sortedcontainers import SortedSet diff --git a/cyclonedx/model/definition.py b/cyclonedx/model/definition.py index 675e5476..7ec28bda 100644 --- a/cyclonedx/model/definition.py +++ b/cyclonedx/model/definition.py @@ -16,7 +16,8 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. import re -from typing import TYPE_CHECKING, Any, Iterable, Optional, Union +from collections.abc import Iterable +from typing import TYPE_CHECKING, Any, Optional, Union import py_serializable as serializable from sortedcontainers import SortedSet @@ -29,7 +30,7 @@ from .bom_ref import BomRef if TYPE_CHECKING: # pragma: no cover - from typing import Type, TypeVar + from typing import TypeVar _T_CreId = TypeVar('_T_CreId', bound='CreId') @@ -65,7 +66,7 @@ def serialize(cls, o: Any) -> str: f'Attempt to serialize a non-CreId: {o!r}') @classmethod - def deserialize(cls: 'Type[_T_CreId]', o: Any) -> '_T_CreId': + def deserialize(cls: 'type[_T_CreId]', o: Any) -> '_T_CreId': return cls(id=str(o)) def __eq__(self, other: Any) -> bool: diff --git a/cyclonedx/model/dependency.py b/cyclonedx/model/dependency.py index 8241fdfc..49b51428 100644 --- a/cyclonedx/model/dependency.py +++ b/cyclonedx/model/dependency.py @@ -17,7 +17,8 @@ from abc import ABC, abstractmethod -from typing import Any, Iterable, List, Optional, Set +from collections.abc import Iterable +from typing import Any, Optional import py_serializable as serializable from sortedcontainers import SortedSet @@ -31,14 +32,14 @@ class _DependencyRepositorySerializationHelper(serializable.helpers.BaseHelper): """ THIS CLASS IS NON-PUBLIC API """ @classmethod - def serialize(cls, o: Any) -> List[str]: + def serialize(cls, o: Any) -> list[str]: if isinstance(o, (SortedSet, set)): return [str(i.ref) for i in o] raise SerializationOfUnexpectedValueException( f'Attempt to serialize a non-DependencyRepository: {o!r}') @classmethod - def deserialize(cls, o: Any) -> Set['Dependency']: + def deserialize(cls, o: Any) -> set['Dependency']: dependencies = set() if isinstance(o, list): for v in o: @@ -80,7 +81,7 @@ def dependencies(self) -> 'SortedSet[Dependency]': def dependencies(self, dependencies: Iterable['Dependency']) -> None: self._dependencies = SortedSet(dependencies) - def dependencies_as_bom_refs(self) -> Set[BomRef]: + def dependencies_as_bom_refs(self) -> set[BomRef]: return set(map(lambda d: d.ref, self.dependencies)) def __comparable_tuple(self) -> _ComparableTuple: diff --git a/cyclonedx/model/issue.py b/cyclonedx/model/issue.py index 4b1f1aa2..1369a88c 100644 --- a/cyclonedx/model/issue.py +++ b/cyclonedx/model/issue.py @@ -15,8 +15,9 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Iterable from enum import Enum -from typing import Any, Iterable, Optional +from typing import Any, Optional import py_serializable as serializable from sortedcontainers import SortedSet diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py index b4348993..86f08dbe 100644 --- a/cyclonedx/model/license.py +++ b/cyclonedx/model/license.py @@ -22,7 +22,7 @@ from enum import Enum from json import loads as json_loads -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Optional, Union from warnings import warn from xml.etree.ElementTree import Element # nosec B405 @@ -385,7 +385,7 @@ class _LicenseRepositorySerializationHelper(serializable.helpers.BaseHelper): @classmethod def json_normalize(cls, o: LicenseRepository, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> Any: if len(o) == 0: return None @@ -405,7 +405,7 @@ def json_normalize(cls, o: LicenseRepository, *, ] @classmethod - def json_denormalize(cls, o: List[Dict[str, Any]], + def json_denormalize(cls, o: list[dict[str, Any]], **__: Any) -> LicenseRepository: repo = LicenseRepository() for li in o: @@ -423,7 +423,7 @@ def json_denormalize(cls, o: List[Dict[str, Any]], @classmethod def xml_normalize(cls, o: LicenseRepository, *, element_name: str, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], xmlns: Optional[str], **__: Any) -> Optional[Element]: if len(o) == 0: diff --git a/cyclonedx/model/lifecycle.py b/cyclonedx/model/lifecycle.py index db688bb8..d8f1db52 100644 --- a/cyclonedx/model/lifecycle.py +++ b/cyclonedx/model/lifecycle.py @@ -27,7 +27,7 @@ from enum import Enum from json import loads as json_loads -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Optional, Union from xml.etree.ElementTree import Element # nosec B405 import py_serializable as serializable @@ -192,7 +192,7 @@ class LifecycleRepository(SortedSet): class _LifecycleRepositoryHelper(BaseHelper): @classmethod def json_normalize(cls, o: LifecycleRepository, *, - view: Optional[Type['ViewType']], + view: Optional[type['ViewType']], **__: Any) -> Any: if len(o) == 0: return None @@ -200,7 +200,7 @@ def json_normalize(cls, o: LifecycleRepository, *, view_=view)) for li in o] @classmethod - def json_denormalize(cls, o: List[Dict[str, Any]], + def json_denormalize(cls, o: list[dict[str, Any]], **__: Any) -> LifecycleRepository: repo = LifecycleRepository() for li in o: @@ -217,7 +217,7 @@ def json_denormalize(cls, o: List[Dict[str, Any]], @classmethod def xml_normalize(cls, o: LifecycleRepository, *, element_name: str, - view: Optional[Type['ViewType']], + view: Optional[type['ViewType']], xmlns: Optional[str], **__: Any) -> Optional[Element]: if len(o) == 0: diff --git a/cyclonedx/model/release_note.py b/cyclonedx/model/release_note.py index 4509bb2b..970d7c6e 100644 --- a/cyclonedx/model/release_note.py +++ b/cyclonedx/model/release_note.py @@ -15,8 +15,9 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Iterable from datetime import datetime -from typing import Iterable, Optional +from typing import Optional import py_serializable as serializable from sortedcontainers import SortedSet diff --git a/cyclonedx/model/service.py b/cyclonedx/model/service.py index 91541f6b..332456cc 100644 --- a/cyclonedx/model/service.py +++ b/cyclonedx/model/service.py @@ -24,7 +24,8 @@ """ -from typing import Any, Iterable, Optional, Union +from collections.abc import Iterable +from typing import Any, Optional, Union import py_serializable as serializable from sortedcontainers import SortedSet diff --git a/cyclonedx/model/tool.py b/cyclonedx/model/tool.py index 38b1e065..afe31441 100644 --- a/cyclonedx/model/tool.py +++ b/cyclonedx/model/tool.py @@ -16,8 +16,9 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Iterable from itertools import chain -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Optional, Union from warnings import warn from xml.etree.ElementTree import Element # nosec B405 @@ -171,7 +172,7 @@ def __repr__(self) -> str: return f'' @classmethod - def from_component(cls: Type['Tool'], component: 'Component') -> 'Tool': + def from_component(cls: type['Tool'], component: 'Component') -> 'Tool': return cls( vendor=component.group, name=component.name, @@ -181,7 +182,7 @@ def from_component(cls: Type['Tool'], component: 'Component') -> 'Tool': ) @classmethod - def from_service(cls: Type['Tool'], service: 'Service') -> 'Tool': + def from_service(cls: type['Tool'], service: 'Service') -> 'Tool': return cls( vendor=service.group, name=service.name, @@ -289,12 +290,12 @@ def __supports_components_and_services(view: Any) -> bool: @classmethod def json_normalize(cls, o: ToolRepository, *, - view: Optional[Type['ViewType']], + view: Optional[type['ViewType']], **__: Any) -> Any: if len(o.tools) > 0 or not cls.__supports_components_and_services(view): ts = cls.__all_as_tools(o) return tuple(ts) if ts else None - elem: Dict[str, Any] = {} + elem: dict[str, Any] = {} if o.components: elem['components'] = tuple(o.components) if o.services: @@ -302,12 +303,12 @@ def json_normalize(cls, o: ToolRepository, *, return elem or None @classmethod - def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]], + def json_denormalize(cls, o: Union[list[dict[str, Any]], dict[str, Any]], **__: Any) -> ToolRepository: tools = None components = None services = None - if isinstance(o, Dict): + if isinstance(o, dict): components = map(lambda c: Component.from_json( # type:ignore[attr-defined] c), o.get('components', ())) services = map(lambda s: Service.from_json( # type:ignore[attr-defined] @@ -320,7 +321,7 @@ def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]], @classmethod def xml_normalize(cls, o: ToolRepository, *, element_name: str, - view: Optional[Type['ViewType']], + view: Optional[type['ViewType']], xmlns: Optional[str], **__: Any) -> Optional[Element]: elem = Element(element_name) @@ -353,7 +354,7 @@ def xml_normalize(cls, o: ToolRepository, *, def xml_denormalize(cls, o: Element, *, default_ns: Optional[str], prop_info: 'ObjectMetadataLibrary.SerializableProperty', - ctx: Type[Any], + ctx: type[Any], **kwargs: Any) -> ToolRepository: ns_map = {'bom': default_ns or ''} # Do not iterate over `o` and do not check for expected `.tag` of items. diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index f2eb1a7f..d4c17d4e 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -30,10 +30,11 @@ import re +from collections.abc import Iterable from datetime import datetime from decimal import Decimal from enum import Enum -from typing import Any, Dict, FrozenSet, Iterable, Optional, Tuple, Type, Union +from typing import Any, Optional, Union import py_serializable as serializable from sortedcontainers import SortedSet @@ -651,7 +652,7 @@ def get_value_pre_1_4(self) -> str: class _VulnerabilityScoreSourceSerializationHelper(serializable.helpers.BaseHelper): """ THIS CLASS IS NON-PUBLIC API """ - __CASES: Dict[Type[serializable.ViewType], FrozenSet[VulnerabilityScoreSource]] = dict() + __CASES: dict[type[serializable.ViewType], frozenset[VulnerabilityScoreSource]] = dict() __CASES[SchemaVersion1Dot4] = frozenset({ VulnerabilityScoreSource.CVSS_V2, VulnerabilityScoreSource.CVSS_V3, @@ -666,7 +667,7 @@ class _VulnerabilityScoreSourceSerializationHelper(serializable.helpers.BaseHelp __CASES[SchemaVersion1Dot6] = __CASES[SchemaVersion1Dot5] @classmethod - def __normalize(cls, vss: VulnerabilityScoreSource, view: Type[serializable.ViewType]) -> str: + def __normalize(cls, vss: VulnerabilityScoreSource, view: type[serializable.ViewType]) -> str: return ( vss if vss in cls.__CASES.get(view, ()) @@ -675,14 +676,14 @@ def __normalize(cls, vss: VulnerabilityScoreSource, view: Type[serializable.View @classmethod def json_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> str: assert view is not None return cls.__normalize(o, view) @classmethod def xml_normalize(cls, o: Any, *, - view: Optional[Type[serializable.ViewType]], + view: Optional[type[serializable.ViewType]], **__: Any) -> str: assert view is not None return cls.__normalize(o, view) @@ -709,7 +710,7 @@ class VulnerabilitySeverity(str, Enum): UNKNOWN = 'unknown' @staticmethod - def get_from_cvss_scores(scores: Union[Tuple[float, ...], float, None]) -> 'VulnerabilitySeverity': + def get_from_cvss_scores(scores: Union[tuple[float, ...], float, None]) -> 'VulnerabilitySeverity': """ Derives the Severity of a Vulnerability from it's declared CVSS scores. diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 61868d43..95f66e0e 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -23,9 +23,10 @@ import os from abc import ABC, abstractmethod +from collections.abc import Iterable, Mapping from itertools import chain from random import random -from typing import TYPE_CHECKING, Any, Iterable, Literal, Mapping, Optional, Type, Union, overload +from typing import TYPE_CHECKING, Any, Literal, Optional, Union, overload from ..schema import OutputFormat, SchemaVersion @@ -124,7 +125,7 @@ def make_outputter(bom: 'Bom', output_format: OutputFormat, schema_version: Sche :return: BaseOutput """ if TYPE_CHECKING: # pragma: no cover - BY_SCHEMA_VERSION: Mapping[SchemaVersion, Type[BaseOutput]] # noqa:N806 + BY_SCHEMA_VERSION: Mapping[SchemaVersion, type[BaseOutput]] # noqa:N806 if OutputFormat.JSON is output_format: from .json import BY_SCHEMA_VERSION elif OutputFormat.XML is output_format: diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index bece526b..8caa7b90 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -17,7 +17,7 @@ from abc import abstractmethod from json import dumps as json_dumps, loads as json_loads -from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Literal, Optional, Union from ..exception.output import FormatNotSupportedException from ..schema import OutputFormat, SchemaVersion @@ -42,7 +42,7 @@ class Json(BaseOutput, BaseSchemaVersion): def __init__(self, bom: 'Bom') -> None: super().__init__(bom=bom) - self._bom_json: Dict[str, Any] = dict() + self._bom_json: dict[str, Any] = dict() @property def schema_version(self) -> SchemaVersion: @@ -70,7 +70,7 @@ def generate(self, force_regeneration: bool = False) -> None: bom = self.get_bom() bom.validate() with BomRefDiscriminator.from_bom(bom): - bom_json: Dict[str, Any] = json_loads( + bom_json: dict[str, Any] = json_loads( bom.as_json( # type:ignore[attr-defined] view_=_view)) bom_json.update(_json_core) @@ -131,7 +131,7 @@ def _get_schema_uri(self) -> str: return 'http://cyclonedx.org/schema/bom-1.6.schema.json' -BY_SCHEMA_VERSION: Dict[SchemaVersion, Type[Json]] = { +BY_SCHEMA_VERSION: dict[SchemaVersion, type[Json]] = { SchemaVersion.V1_6: JsonV1Dot6, SchemaVersion.V1_5: JsonV1Dot5, SchemaVersion.V1_4: JsonV1Dot4, diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 604b6297..7121999f 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -16,7 +16,7 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Literal, Optional, Union from xml.dom.minidom import parseString as dom_parseString # nosec B408 from xml.etree.ElementTree import Element as XmlElement, tostring as xml_dumps # nosec B405 @@ -124,7 +124,7 @@ class XmlV1Dot6(Xml, SchemaVersion1Dot6): pass -BY_SCHEMA_VERSION: Dict[SchemaVersion, Type[Xml]] = { +BY_SCHEMA_VERSION: dict[SchemaVersion, type[Xml]] = { SchemaVersion.V1_6: XmlV1Dot6, SchemaVersion.V1_5: XmlV1Dot5, SchemaVersion.V1_4: XmlV1Dot4, diff --git a/cyclonedx/schema/__init__.py b/cyclonedx/schema/__init__.py index 0b74ec7d..cb57d8ef 100644 --- a/cyclonedx/schema/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -17,7 +17,7 @@ from enum import Enum, auto, unique -from typing import Any, Type, TypeVar +from typing import Any, TypeVar @unique @@ -64,7 +64,7 @@ class SchemaVersion(Enum): V1_0 = (1, 0) @classmethod - def from_version(cls: Type[_SV], version: str) -> _SV: + def from_version(cls: type[_SV], version: str) -> _SV: """Return instance based of a version string - e.g. `1.4`""" return cls(tuple(map(int, version.split('.')))[:2]) diff --git a/cyclonedx/schema/_res/__init__.py b/cyclonedx/schema/_res/__init__.py index 8ce859cc..3a967883 100644 --- a/cyclonedx/schema/_res/__init__.py +++ b/cyclonedx/schema/_res/__init__.py @@ -23,13 +23,13 @@ from os.path import dirname, join -from typing import Dict, Optional +from typing import Optional from .. import SchemaVersion __DIR = dirname(__file__) -BOM_XML: Dict[SchemaVersion, Optional[str]] = { +BOM_XML: dict[SchemaVersion, Optional[str]] = { SchemaVersion.V1_6: join(__DIR, 'bom-1.6.SNAPSHOT.xsd'), SchemaVersion.V1_5: join(__DIR, 'bom-1.5.SNAPSHOT.xsd'), SchemaVersion.V1_4: join(__DIR, 'bom-1.4.SNAPSHOT.xsd'), @@ -39,7 +39,7 @@ SchemaVersion.V1_0: join(__DIR, 'bom-1.0.SNAPSHOT.xsd'), } -BOM_JSON: Dict[SchemaVersion, Optional[str]] = { +BOM_JSON: dict[SchemaVersion, Optional[str]] = { SchemaVersion.V1_6: join(__DIR, 'bom-1.6.SNAPSHOT.schema.json'), SchemaVersion.V1_5: join(__DIR, 'bom-1.5.SNAPSHOT.schema.json'), SchemaVersion.V1_4: join(__DIR, 'bom-1.4.SNAPSHOT.schema.json'), @@ -50,7 +50,7 @@ SchemaVersion.V1_0: None, } -BOM_JSON_STRICT: Dict[SchemaVersion, Optional[str]] = { +BOM_JSON_STRICT: dict[SchemaVersion, Optional[str]] = { SchemaVersion.V1_6: BOM_JSON[SchemaVersion.V1_6], SchemaVersion.V1_5: BOM_JSON[SchemaVersion.V1_5], SchemaVersion.V1_4: BOM_JSON[SchemaVersion.V1_4], diff --git a/cyclonedx/schema/schema.py b/cyclonedx/schema/schema.py index a51686e9..b29b22da 100644 --- a/cyclonedx/schema/schema.py +++ b/cyclonedx/schema/schema.py @@ -16,7 +16,7 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. from abc import ABC, abstractmethod -from typing import Dict, Literal, Type +from typing import Literal from py_serializable import ViewType @@ -83,7 +83,7 @@ def schema_version_enum(self) -> Literal[SchemaVersion.V1_0]: return SchemaVersion.V1_0 -SCHEMA_VERSIONS: Dict[SchemaVersion, Type[BaseSchemaVersion]] = { +SCHEMA_VERSIONS: dict[SchemaVersion, type[BaseSchemaVersion]] = { SchemaVersion.V1_6: SchemaVersion1Dot6, SchemaVersion.V1_5: SchemaVersion1Dot5, SchemaVersion.V1_4: SchemaVersion1Dot4, diff --git a/cyclonedx/spdx.py b/cyclonedx/spdx.py index 9781af54..c87d9a19 100644 --- a/cyclonedx/spdx.py +++ b/cyclonedx/spdx.py @@ -22,7 +22,7 @@ ] from json import load as json_load -from typing import TYPE_CHECKING, Dict, Optional, Set +from typing import TYPE_CHECKING, Optional from license_expression import get_spdx_licensing # type:ignore[import-untyped] @@ -36,10 +36,10 @@ # !!! this requires to ship the actual schema data with the package. with open(__SPDX_JSON_SCHEMA) as schema: - __IDS: Set[str] = set(json_load(schema).get('enum', [])) + __IDS: set[str] = set(json_load(schema).get('enum', [])) assert len(__IDS) > 0, 'known SPDX-IDs should be non-empty set' -__IDS_LOWER_MAP: Dict[str, str] = dict((id_.lower(), id_) for id_ in __IDS) +__IDS_LOWER_MAP: dict[str, str] = dict((id_.lower(), id_) for id_ in __IDS) __SPDX_EXPRESSION_LICENSING: 'Licensing' = get_spdx_licensing() diff --git a/cyclonedx/validation/json.py b/cyclonedx/validation/json.py index ebdd01e0..7e6dcf0b 100644 --- a/cyclonedx/validation/json.py +++ b/cyclonedx/validation/json.py @@ -20,7 +20,7 @@ from abc import ABC from json import loads as json_loads -from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple +from typing import TYPE_CHECKING, Any, Literal, Optional from ..schema import OutputFormat @@ -31,7 +31,7 @@ from ..schema._res import BOM_JSON as _S_BOM, BOM_JSON_STRICT as _S_BOM_STRICT, JSF as _S_JSF, SPDX_JSON as _S_SPDX from . import BaseSchemabasedValidator, SchemabasedValidator, ValidationError -_missing_deps_error: Optional[Tuple[MissingOptionalDependencyException, ImportError]] = None +_missing_deps_error: Optional[tuple[MissingOptionalDependencyException, ImportError]] = None try: from jsonschema.exceptions import ValidationError as JsonValidationError # type:ignore[import-untyped] from jsonschema.validators import Draft7Validator # type:ignore[import-untyped] diff --git a/cyclonedx/validation/xml.py b/cyclonedx/validation/xml.py index 2ba79890..6df74244 100644 --- a/cyclonedx/validation/xml.py +++ b/cyclonedx/validation/xml.py @@ -19,7 +19,7 @@ __all__ = ['XmlValidator'] from abc import ABC -from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple +from typing import TYPE_CHECKING, Any, Literal, Optional from ..exception import MissingOptionalDependencyException from ..schema import OutputFormat @@ -29,7 +29,7 @@ if TYPE_CHECKING: # pragma: no cover from ..schema import SchemaVersion -_missing_deps_error: Optional[Tuple[MissingOptionalDependencyException, ImportError]] = None +_missing_deps_error: Optional[tuple[MissingOptionalDependencyException, ImportError]] = None try: from lxml.etree import ( # type:ignore[import-untyped] # nosec B410 XMLParser, diff --git a/pyproject.toml b/pyproject.toml index aa027a52..1c1260f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,6 @@ classifiers = [ 'Topic :: Software Development', 'Topic :: System :: Software Distribution', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -68,7 +67,7 @@ keywords = [ "Funding" = "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" packageurl-python = ">=0.11, <2" py-serializable = "^2.0.0" sortedcontainers = "^2.4.0" @@ -83,22 +82,22 @@ xml-validation = ["lxml"] [tool.poetry.group.dev.dependencies] ddt = "1.7.2" -coverage = "7.6.1" -flake8 = { version="7.1.2", python=">=3.8.1" } -flake8-annotations = { version="3.1.1", python=">=3.8.1" } -flake8-bugbear = { version="24.12.12", python=">=3.8.1" } +coverage = "7.8.0" +flake8 = "7.2.0" +flake8-annotations = "3.1.1" +flake8-bugbear = "24.12.12" flake8-copyright-validator = "0.0.1" -flake8-isort = "6.1.1" +flake8-isort = "6.1.2" flake8-quotes = "3.4.0" flake8-use-fstring = "1.4" pep8-naming = "0.14.1" -isort = "5.13.2" -autopep8 = "2.3.1" -mypy = "1.14.1" +isort = "6.0.1" +autopep8 = "2.3.2" +mypy = "1.15.0" tomli = { version = "2.2.1", python = "<3.11" } tox = "4.25.0" xmldiff = "2.7.0" -bandit = "1.7.10" +bandit = "1.8.3" [tool.semantic_release] # see https://python-semantic-release.readthedocs.io/en/latest/configuration.html diff --git a/tests/__init__.py b/tests/__init__.py index 93111a22..9c664cc3 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,8 +17,9 @@ import re import sys +from collections.abc import Generator, Iterable from os import getenv, path -from typing import TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Tuple, TypeVar, Union +from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union from unittest import TestCase from uuid import UUID @@ -130,7 +131,7 @@ def assertDependenciesFuzzyEqual(self: TestCase, # noqa: N802 self.assertEqual(0, len(d.dependencies), f'unexpected dependencies for {d.ref}') -def reorder(items: List[_T], indexes: List[int]) -> List[_T]: +def reorder(items: list[_T], indexes: list[int]) -> list[_T]: """ Return list of items in the order indicated by indexes. """ @@ -186,14 +187,14 @@ def mksname(purpose: Union[Any], sv: SchemaVersion, f: OutputFormat) -> str: return f'{_get_purpose_as_str(purpose)}-{sv.to_version()}.{_SNAME_EXT[f]}' -class DpTuple(Tuple[SchemaVersion, str]): +class DpTuple(tuple[SchemaVersion, str]): @property def __name__(self) -> str: schema_version, test_data_file = self return f'{schema_version.to_version()}-{path.splitext(path.basename(test_data_file))[0]}' -def load_pyproject() -> Dict[str, Any]: +def load_pyproject() -> dict[str, Any]: if sys.version_info >= (3, 11): from tomllib import load as toml_load else: diff --git a/tests/_data/models.py b/tests/_data/models.py index 6a25c552..24ec42eb 100644 --- a/tests/_data/models.py +++ b/tests/_data/models.py @@ -20,7 +20,7 @@ from datetime import datetime, timezone from decimal import Decimal from inspect import getmembers, isfunction -from typing import Any, List, Optional, Tuple +from typing import Any, Optional from uuid import UUID # See https://github.com/package-url/packageurl-python/issues/65 @@ -875,7 +875,7 @@ def get_pedigree_1() -> Pedigree: ) -def get_properties_1() -> List[Property]: +def get_properties_1() -> list[Property]: return [ Property(name='key1', value='val1'), Property(name='key2', value='val2') @@ -992,7 +992,7 @@ def get_bom_metadata_licenses_invalid() -> Bom: return Bom(metadata=BomMetaData(licenses=get_invalid_license_repository())) -def get_invalid_license_repository() -> List[License]: +def get_invalid_license_repository() -> list[License]: """ license expression and a license -- this is an invalid constellation according to schema see https://github.com/CycloneDX/specification/pull/205 @@ -1261,7 +1261,7 @@ def get_bom_for_issue_598_multiple_components_with_purl_qualifiers() -> Bom: ]) -def bom_all_same_bomref() -> Tuple[Bom, int]: +def bom_all_same_bomref() -> tuple[Bom, int]: bom = Bom() bom.metadata.component = Component(name='root', bom_ref='foo', components=[ Component(name='root.sub', bom_ref='foo')]) diff --git a/tests/test_builder_this.py b/tests/test_builder_this.py index 2e56b317..f200410d 100644 --- a/tests/test_builder_this.py +++ b/tests/test_builder_this.py @@ -15,7 +15,8 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Any, Dict, Iterable, Tuple, Union +from collections.abc import Iterable +from typing import Any, Union from unittest import TestCase from cyclonedx.builder.this import this_component, this_tool @@ -33,7 +34,7 @@ def __first_ers_uri(t: ExternalReferenceType, ers: Iterable[ExternalReference]) def assertExtRefs( # noqa:N802 self: Union[TestCase, 'ExtRefsTestMixin'], - p: Dict[str, Any], ers: Iterable[ExternalReference] + p: dict[str, Any], ers: Iterable[ExternalReference] ) -> None: self.assertEqual(p['tool']['poetry']['homepage'], self.__first_ers_uri( ExternalReferenceType.WEBSITE, ers)) @@ -57,7 +58,7 @@ def test_basics(self) -> None: def test_license(self) -> None: p = load_pyproject() - ls: Tuple[License, ...] = tuple(this_component().licenses) + ls: tuple[License, ...] = tuple(this_component().licenses) self.assertEqual(1, len(ls)) l = ls[0] # noqa:E741 self.assertIs(LicenseAcknowledgement.DECLARED, l.acknowledgement) @@ -66,7 +67,7 @@ def test_license(self) -> None: def test_extrefs(self) -> None: p = load_pyproject() - ers: Tuple[ExternalReference, ...] = tuple(this_component().external_references) + ers: tuple[ExternalReference, ...] = tuple(this_component().external_references) self.assertExtRefs(p, ers) @@ -80,5 +81,5 @@ def test_basics(self) -> None: def test_extrefs(self) -> None: p = load_pyproject() - ers: Tuple[ExternalReference, ...] = tuple(this_tool().external_references) + ers: tuple[ExternalReference, ...] = tuple(this_tool().external_references) self.assertExtRefs(p, ers) diff --git a/tests/test_deserialize_json.py b/tests/test_deserialize_json.py index 0b6bf9d2..21ee621f 100644 --- a/tests/test_deserialize_json.py +++ b/tests/test_deserialize_json.py @@ -16,9 +16,10 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Callable from json import loads as json_loads from os.path import join -from typing import Any, Callable +from typing import Any from unittest import TestCase from unittest.mock import patch diff --git a/tests/test_deserialize_xml.py b/tests/test_deserialize_xml.py index 26cc34ec..3f213aef 100644 --- a/tests/test_deserialize_xml.py +++ b/tests/test_deserialize_xml.py @@ -15,7 +15,8 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from unittest import TestCase from unittest.mock import patch diff --git a/tests/test_enums.py b/tests/test_enums.py index 27c37def..48138876 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -16,10 +16,11 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Generator, Iterable from enum import Enum from itertools import chain from json import load as json_load -from typing import Any, Generator, Iterable, Tuple, Type +from typing import Any from unittest import TestCase from warnings import warn from xml.etree.ElementTree import parse as xml_parse # nosec B405 @@ -128,7 +129,7 @@ def dp_cases_from_json_schemas(*jsonpointer: str) -> Generator[str, None, None]: class _EnumTestCase(TestCase, SnapshotMixin): - def _test_knows_value(self, enum: Type[Enum], value: str) -> None: + def _test_knows_value(self, enum: type[Enum], value: str) -> None: ec = enum(value) # throws valueError if value unknown self.assertTrue(ec.name) # TODO test for an expected name @@ -258,7 +259,7 @@ class _DP_ComponentType(): # noqa: N801 JSON_SCHEMA_POINTER = ('definitions', 'component', 'properties', 'type') @classmethod - def unsupported_cases(cls) -> Generator[Tuple[str, OutputFormat, SchemaVersion, ComponentType], None, None]: + def unsupported_cases(cls) -> Generator[tuple[str, OutputFormat, SchemaVersion, ComponentType], None, None]: for name, of, sv in NAMED_OF_SV: if OutputFormat.XML is of: schema_cases = set(dp_cases_from_xml_schema(SCHEMA_XML[sv], cls.XML_SCHEMA_XPATH)) diff --git a/tests/test_model_bom.py b/tests/test_model_bom.py index 1c490a8a..d47e2466 100644 --- a/tests/test_model_bom.py +++ b/tests/test_model_bom.py @@ -15,7 +15,7 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. import warnings -from typing import Callable, Tuple +from collections.abc import Callable from unittest import TestCase from uuid import uuid4 @@ -253,7 +253,7 @@ def test_get_component_by_purl(self) -> None: ('duplicate name(k)', ((Component(name='A', bom_ref='A1'), tuple()), (Component(name='A', bom_ref='A2'), tuple()))), ) - def test_register_dependency(self, dependencies: Tuple[Tuple[Component, Tuple[Component, ...]], ...]) -> None: + def test_register_dependency(self, dependencies: tuple[tuple[Component, tuple[Component, ...]], ...]) -> None: bom = Bom() for d1, d2 in dependencies: bom.components.update((d1,), d2) diff --git a/tests/test_model_component.py b/tests/test_model_component.py index 3f893907..05cf278c 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -16,7 +16,6 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. import datetime -from typing import List from unittest import TestCase from cyclonedx.model import ( @@ -185,7 +184,7 @@ def test_component_equal_1(self) -> None: self.assertEqual(c1, c2) def test_component_equal_2(self) -> None: - props: List[Property] = ( + props: list[Property] = ( Property(name='prop1', value='val1'), Property(name='prop2', value='val2'), ) diff --git a/tests/test_output.py b/tests/test_output.py index 9d13855b..e29d12a3 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -17,7 +17,6 @@ from itertools import product -from typing import Tuple from unittest import TestCase from unittest.mock import Mock @@ -46,7 +45,7 @@ def test_as_expected(self, of: OutputFormat, sv: SchemaVersion) -> None: *(('foo', sv, (ValueError, "Unexpected output_format: 'foo'")) for sv in SchemaVersion), ) @unpack - def test_fails_on_wrong_args(self, of: OutputFormat, sv: SchemaVersion, raises_regex: Tuple) -> None: + def test_fails_on_wrong_args(self, of: OutputFormat, sv: SchemaVersion, raises_regex: tuple) -> None: bom = Mock(spec=Bom) with self.assertRaisesRegex(*raises_regex): make_outputter(bom, of, sv) diff --git a/tests/test_output_json.py b/tests/test_output_json.py index 304a8fa1..0bc3f1e2 100644 --- a/tests/test_output_json.py +++ b/tests/test_output_json.py @@ -17,7 +17,8 @@ import re -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from unittest import TestCase from unittest.mock import Mock, patch from warnings import warn diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index e7ed5b88..2b1a16ee 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -16,7 +16,8 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. import re -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from unittest import TestCase from unittest.mock import Mock, patch from warnings import warn diff --git a/tests/test_schema__res.py b/tests/test_schema__res.py index 0107949b..4292aaf2 100644 --- a/tests/test_schema__res.py +++ b/tests/test_schema__res.py @@ -15,8 +15,8 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Generator from os.path import isfile -from typing import Generator from unittest import TestCase from ddt import ddt, idata diff --git a/tests/test_validation.py b/tests/test_validation.py index 7465cf78..c4d4e085 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -17,7 +17,6 @@ from itertools import product -from typing import Tuple from unittest import TestCase from ddt import data, ddt, named_data, unpack @@ -49,6 +48,6 @@ def test_as_expected(self, of: OutputFormat, sv: SchemaVersion) -> None: *((f, v, (ValueError, 'Unsupported schema_version')) for f, v in UNDEFINED_FORMAT_VERSION) ) @unpack - def test_fails_on_wrong_args(self, of: OutputFormat, sv: SchemaVersion, raises_regex: Tuple) -> None: + def test_fails_on_wrong_args(self, of: OutputFormat, sv: SchemaVersion, raises_regex: tuple) -> None: with self.assertRaisesRegex(*raises_regex): make_schemabased_validator(of, sv) diff --git a/tests/test_validation_xml.py b/tests/test_validation_xml.py index 8ec04d71..d5fcbb0f 100644 --- a/tests/test_validation_xml.py +++ b/tests/test_validation_xml.py @@ -15,10 +15,10 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +from collections.abc import Generator from glob import iglob from itertools import chain from os.path import join -from typing import Generator from unittest import TestCase from ddt import ddt, idata, unpack diff --git a/tox.ini b/tox.ini index 4eace2fc..88c3d0de 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ minversion = 4.0 envlist = flake8 mypy-{current,lowest} - py{313,312,311,310,39,38}-{allExtras,noExtras} + py{313,312,311,310,39}-{allExtras,noExtras} bandit skip_missing_interpreters = True usedevelop = False @@ -33,7 +33,7 @@ setenv = commands = # mypy config is in own file: `.mypy.ini` !lowest: poetry run mypy - lowest: poetry run mypy --python-version=3.8 + lowest: poetry run mypy --python-version=3.9 [testenv:flake8] commands = diff --git a/typings/sortedcontainers.pyi b/typings/sortedcontainers.pyi index c9167837..5959205f 100644 --- a/typings/sortedcontainers.pyi +++ b/typings/sortedcontainers.pyi @@ -3,19 +3,8 @@ # The contents of this file were obtained from # https://github.com/althonos/python-sortedcontainers/blob/d0a225d7fd0fb4c54532b8798af3cbeebf97e2d5/sortedcontainers/sortedset.pyi -from typing import ( # Iterator,; Tuple,; Type, Set - Any, - Callable, - Hashable, - Iterable, - List, - MutableSet, - Optional, - Sequence, - TypeVar, - Union, - overload, -) +from collections.abc import Callable, Iterable, MutableSet, Sequence +from typing import Any, Hashable, Optional, TypeVar, Union, overload # Iterator,; Tuple,; Type, Set # --- Global @@ -40,7 +29,7 @@ class SortedSet(MutableSet[_T], Sequence[_T]): # @overload # def __getitem__(self, index: int) -> _T: ... @overload - def __getitem__(self, index: slice) -> List[_T]: ... + def __getitem__(self, index: slice) -> list[_T]: ... # def __delitem__(self, index: Union[int, slice]) -> None: ... # def __eq__(self, other: Any) -> bool: ... # def __ne__(self, other: Any) -> bool: ...