From 22457e60daebacd413bb1397cf7a38fa70f8b0de Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 17:37:18 +0200 Subject: [PATCH 01/16] feat: typing overload for `get_instance()` Signed-off-by: Jan Kowalleck --- cyclonedx/output/__init__.py | 41 +++++++++++++++++++++++--------- cyclonedx/validation/__init__.py | 24 +++++++++++++++---- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index e151c1bf..8b521dd6 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -20,23 +20,27 @@ import os from abc import ABC, abstractmethod from importlib import import_module -from typing import Any, Iterable, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Iterable, Literal, Optional, Type, Union, overload -from ..model.bom import Bom -from ..model.component import Component from ..schema import OutputFormat, SchemaVersion +if TYPE_CHECKING: + from ..model.bom import Bom + from ..model.component import Component + from .json import Json as JsonOutputter + from .xml import Xml as XmlOutputter + LATEST_SUPPORTED_SCHEMA_VERSION = SchemaVersion.V1_4 class BaseOutput(ABC): - def __init__(self, bom: Bom, **kwargs: int) -> None: + def __init__(self, bom: 'Bom', **kwargs: int) -> None: super().__init__(**kwargs) self._bom = bom self._generated: bool = False - def _chained_components(self, container: Union[Bom, Component]) -> Iterable[Component]: + def _chained_components(self, container: Union['Bom', 'Component']) -> Iterable['Component']: for component in container.components: yield component yield from self._chained_components(component) @@ -59,10 +63,10 @@ def generated(self) -> bool: def generated(self, generated: bool) -> None: self._generated = generated - def get_bom(self) -> Bom: + def get_bom(self) -> 'Bom': return self._bom - def set_bom(self, bom: Bom) -> None: + def set_bom(self, bom: 'Bom') -> None: self._bom = bom @abstractmethod @@ -89,17 +93,32 @@ def output_to_file(self, filename: str, allow_overwrite: bool = False, *, f_out.write(self.output_as_string(indent=indent).encode('utf-8')) -def get_instance(bom: Bom, output_format: OutputFormat = OutputFormat.XML, +@overload +def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.JSON], + schema_version: SchemaVersion = ...) -> 'JsonOutputter': + ... + + +@overload +def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.XML] = ..., + schema_version: SchemaVersion = ...) -> 'XmlOutputter': + ... + + +def get_instance(bom: 'Bom', output_format: OutputFormat = OutputFormat.XML, schema_version: SchemaVersion = LATEST_SUPPORTED_SCHEMA_VERSION) -> BaseOutput: """ Helper method to quickly get the correct output class/formatter. Pass in your BOM and optionally an output format and schema version (defaults to XML and latest schema version). + + Raises error when no instance could be built. + :param bom: Bom :param output_format: OutputFormat :param schema_version: SchemaVersion - :return: + :return: BaseOutput """ # all exceptions are undocumented, as they are pure functional, and should be prevented by correct typing... if not isinstance(output_format, OutputFormat): @@ -108,9 +127,9 @@ def get_instance(bom: Bom, output_format: OutputFormat = OutputFormat.XML, raise TypeError(f"unexpected schema_version: {schema_version!r}") try: module = import_module(f'.{output_format.name.lower()}', __package__) - except ImportError as error: # pragma: no cover + except ImportError as error: raise ValueError(f'Unknown output_format: {output_format.name}') from error klass: Optional[Type[BaseOutput]] = module.BY_SCHEMA_VERSION.get(schema_version, None) - if klass is None: # pragma: no cover + if klass is None: raise ValueError(f'Unknown {output_format.name}/schema_version: {schema_version.name}') return klass(bom=bom) diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index e8b207de..93bb6b6a 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -14,12 +14,14 @@ from abc import ABC, abstractmethod from importlib import import_module -from typing import TYPE_CHECKING, Any, Optional, Protocol, Type +from typing import TYPE_CHECKING, Any, Literal, Optional, Protocol, Type, overload from ..schema import OutputFormat if TYPE_CHECKING: from ..schema import SchemaVersion + from .json import JsonValidator + from .xml import XmlValidator class ValidationError: @@ -80,15 +82,29 @@ def _schema_file(self) -> Optional[str]: ... +@overload +def get_instance(output_format: Literal[OutputFormat.JSON], schema_version: 'SchemaVersion') -> 'JsonValidator': + ... + + +@overload +def get_instance(output_format: Literal[OutputFormat.XML], schema_version: 'SchemaVersion') -> 'XmlValidator': + ... + + def get_instance(output_format: OutputFormat, schema_version: 'SchemaVersion') -> BaseValidator: - """get the default validator for a certain `OutputFormat`""" + """get the default validator for a certain `OutputFormat` + + Raises error when no instance could be built. + """ + # all exceptions are undocumented, as they are pure functional, and should be prevented by correct typing... if not isinstance(output_format, OutputFormat): raise TypeError(f"unexpected output_format: {output_format!r}") try: module = import_module(f'.{output_format.name.lower()}', __package__) - except ImportError as error: # pragma: no cover + except ImportError as error: raise ValueError(f'Unknown output_format: {output_format.name}') from error klass: Optional[Type[BaseValidator]] = getattr(module, f'{output_format.name.capitalize()}Validator', None) - if klass is None: # pragma: no cover + if klass is None: raise ValueError(f'Missing Validator for {output_format.name}') return klass(schema_version) From d6ed59552a954dd25b03e04e748ebe6a618fd664 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 18:14:39 +0200 Subject: [PATCH 02/16] typing wip Signed-off-by: Jan Kowalleck --- .mypy.ini | 2 +- cyclonedx/output/__init__.py | 7 +++++++ cyclonedx/output/json.py | 14 +++++++------- cyclonedx/output/xml.py | 4 ++-- cyclonedx/schema/schema.py | 10 +++++----- cyclonedx/validation/__init__.py | 8 +++++++- cyclonedx/validation/json.py | 4 ++-- cyclonedx/validation/xml.py | 4 ++-- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/.mypy.ini b/.mypy.ini index c15c04ab..f86c1953 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -1,6 +1,6 @@ [mypy] -files = cyclonedx/ +files = cyclonedx/, examples/ mypy_path = $MYPY_CONFIG_FILE_DIR/typings show_error_codes = True diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 8b521dd6..c69c5798 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -105,6 +105,13 @@ def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.XML] = ..., ... +@overload +def get_instance(bom: 'Bom', output_format: OutputFormat = ..., + schema_version: SchemaVersion = LATEST_SUPPORTED_SCHEMA_VERSION + ) -> Union['XmlOutputter', 'JsonOutputter']: + ... + + def get_instance(bom: 'Bom', output_format: OutputFormat = OutputFormat.XML, schema_version: SchemaVersion = LATEST_SUPPORTED_SCHEMA_VERSION) -> BaseOutput: """ diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 4f9f6fce..4043587c 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 Any, Dict, Optional, Type, Union +from typing import Any, Dict, Optional, Type, Union, Literal from ..exception.output import FormatNotSupportedException from ..model.bom import Bom @@ -45,7 +45,7 @@ def schema_version(self) -> SchemaVersion: return self.schema_version_enum @property - def output_format(self) -> OutputFormat: + def output_format(self) -> Literal[OutputFormat.JSON]: return OutputFormat.JSON def generate(self, force_regeneration: bool = False) -> None: @@ -85,31 +85,31 @@ def _get_schema_uri(self) -> Optional[str]: class JsonV1Dot0(Json, SchemaVersion1Dot0): - def _get_schema_uri(self) -> Optional[str]: + def _get_schema_uri(self) -> None: return None class JsonV1Dot1(Json, SchemaVersion1Dot1): - def _get_schema_uri(self) -> Optional[str]: + def _get_schema_uri(self) -> None: return None class JsonV1Dot2(Json, SchemaVersion1Dot2): - def _get_schema_uri(self) -> Optional[str]: + def _get_schema_uri(self) -> str: return 'http://cyclonedx.org/schema/bom-1.2b.schema.json' class JsonV1Dot3(Json, SchemaVersion1Dot3): - def _get_schema_uri(self) -> Optional[str]: + def _get_schema_uri(self) -> str: return 'http://cyclonedx.org/schema/bom-1.3a.schema.json' class JsonV1Dot4(Json, SchemaVersion1Dot4): - def _get_schema_uri(self) -> Optional[str]: + def _get_schema_uri(self) -> str: return 'http://cyclonedx.org/schema/bom-1.4.schema.json' diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index d1fd7fcd..381af0d3 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 Any, Dict, Optional, Type, Union +from typing import Any, Dict, Optional, Type, Union, Literal from xml.dom.minidom import parseString as dom_parseString from xml.etree.ElementTree import Element as XmlElement, tostring as xml_dumps @@ -44,7 +44,7 @@ def schema_version(self) -> SchemaVersion: return self.schema_version_enum @property - def output_format(self) -> OutputFormat: + def output_format(self) -> Literal[OutputFormat.XML]: return OutputFormat.XML def generate(self, force_regeneration: bool = False) -> None: diff --git a/cyclonedx/schema/schema.py b/cyclonedx/schema/schema.py index cb5d1e27..64212a55 100644 --- a/cyclonedx/schema/schema.py +++ b/cyclonedx/schema/schema.py @@ -70,9 +70,9 @@ def schema_version_enum(self) -> SchemaVersion: SCHEMA_VERSIONS: Dict[SchemaVersion, Type[BaseSchemaVersion]] = { - SchemaVersion.V1_4: SchemaVersion1Dot4, # type:ignore[type-abstract] - SchemaVersion.V1_3: SchemaVersion1Dot3, # type:ignore[type-abstract] - SchemaVersion.V1_2: SchemaVersion1Dot2, # type:ignore[type-abstract] - SchemaVersion.V1_1: SchemaVersion1Dot1, # type:ignore[type-abstract] - SchemaVersion.V1_0: SchemaVersion1Dot0, # type:ignore[type-abstract] + SchemaVersion.V1_4: SchemaVersion1Dot4, + SchemaVersion.V1_3: SchemaVersion1Dot3, + SchemaVersion.V1_2: SchemaVersion1Dot2, + SchemaVersion.V1_1: SchemaVersion1Dot1, + SchemaVersion.V1_0: SchemaVersion1Dot0, } diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index 93bb6b6a..46ae1cf7 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -14,7 +14,7 @@ from abc import ABC, abstractmethod from importlib import import_module -from typing import TYPE_CHECKING, Any, Literal, Optional, Protocol, Type, overload +from typing import TYPE_CHECKING, Any, Literal, Optional, Protocol, Type, overload, Union from ..schema import OutputFormat @@ -92,6 +92,12 @@ def get_instance(output_format: Literal[OutputFormat.XML], schema_version: 'Sche ... +@overload +def get_instance(output_format: OutputFormat, schema_version: 'SchemaVersion' + ) -> Union['JsonValidator', 'XmlValidator']: + ... + + def get_instance(output_format: OutputFormat, schema_version: 'SchemaVersion') -> BaseValidator: """get the default validator for a certain `OutputFormat` diff --git a/cyclonedx/validation/json.py b/cyclonedx/validation/json.py index d97d276c..4c385d30 100644 --- a/cyclonedx/validation/json.py +++ b/cyclonedx/validation/json.py @@ -16,7 +16,7 @@ from abc import ABC from json import loads as json_loads -from typing import TYPE_CHECKING, Any, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional, Tuple, Literal from ..schema import OutputFormat @@ -45,7 +45,7 @@ class _BaseJsonValidator(BaseValidator, ABC): @property - def output_format(self) -> OutputFormat: + def output_format(self) -> Literal[OutputFormat.JSON]: return OutputFormat.JSON def __init__(self, schema_version: 'SchemaVersion') -> None: diff --git a/cyclonedx/validation/xml.py b/cyclonedx/validation/xml.py index 66890316..a2715ee9 100644 --- a/cyclonedx/validation/xml.py +++ b/cyclonedx/validation/xml.py @@ -15,7 +15,7 @@ __all__ = ['XmlValidator'] from abc import ABC -from typing import TYPE_CHECKING, Any, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional, Tuple, Literal from ..exception import MissingOptionalDependencyException from ..schema import OutputFormat @@ -38,7 +38,7 @@ class _BaseXmlValidator(BaseValidator, ABC): @property - def output_format(self) -> OutputFormat: + def output_format(self) -> Literal[OutputFormat.XML]: return OutputFormat.XML def __init__(self, schema_version: 'SchemaVersion') -> None: From 65ec9efbf2b83ab9136e749bb35183925401e13c Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 18:36:15 +0200 Subject: [PATCH 03/16] typing wip Signed-off-by: Jan Kowalleck --- cyclonedx/output/json.py | 10 +++++----- cyclonedx/output/xml.py | 10 +++++----- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 4043587c..a5404aa3 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -114,9 +114,9 @@ def _get_schema_uri(self) -> str: BY_SCHEMA_VERSION: Dict[SchemaVersion, Type[Json]] = { - SchemaVersion.V1_4: JsonV1Dot4, # type:ignore[type-abstract] - SchemaVersion.V1_3: JsonV1Dot3, # type:ignore[type-abstract] - SchemaVersion.V1_2: JsonV1Dot2, # type:ignore[type-abstract] - SchemaVersion.V1_1: JsonV1Dot1, # type:ignore[type-abstract] - SchemaVersion.V1_0: JsonV1Dot0, # type:ignore[type-abstract] + SchemaVersion.V1_4: JsonV1Dot4, + SchemaVersion.V1_3: JsonV1Dot3, + SchemaVersion.V1_2: JsonV1Dot2, + SchemaVersion.V1_1: JsonV1Dot1, + SchemaVersion.V1_0: JsonV1Dot0, } diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 381af0d3..901681a2 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -109,9 +109,9 @@ class XmlV1Dot4(Xml, SchemaVersion1Dot4): BY_SCHEMA_VERSION: Dict[SchemaVersion, Type[Xml]] = { - SchemaVersion.V1_4: XmlV1Dot4, # type:ignore[type-abstract] - SchemaVersion.V1_3: XmlV1Dot3, # type:ignore[type-abstract] - SchemaVersion.V1_2: XmlV1Dot2, # type:ignore[type-abstract] - SchemaVersion.V1_1: XmlV1Dot1, # type:ignore[type-abstract] - SchemaVersion.V1_0: XmlV1Dot0, # type:ignore[type-abstract] + SchemaVersion.V1_4: XmlV1Dot4, + SchemaVersion.V1_3: XmlV1Dot3, + SchemaVersion.V1_2: XmlV1Dot2, + SchemaVersion.V1_1: XmlV1Dot1, + SchemaVersion.V1_0: XmlV1Dot0, } diff --git a/pyproject.toml b/pyproject.toml index 805eb347..6a77418c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ keywords = [ # ATTENTION: keep `deps.lowest.r` file in sync python = "^3.8" packageurl-python = ">= 0.11" -py-serializable = "^0.13.0" +py-serializable = { git = "https://github.com/madpah/serializable.git", branch = "fix/typehints-attempt1" } sortedcontainers = "^2.4.0" license-expression = "^30" jsonschema = { version = "^4.18", extras=['format'], optional=true } From 7375a932f90d3044f4d2c8f6471f449fc515ca08 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 18:38:24 +0200 Subject: [PATCH 04/16] isort Signed-off-by: Jan Kowalleck --- cyclonedx/output/json.py | 2 +- cyclonedx/output/xml.py | 2 +- cyclonedx/validation/__init__.py | 2 +- cyclonedx/validation/json.py | 2 +- cyclonedx/validation/xml.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index a5404aa3..d00eb0a5 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 Any, Dict, Optional, Type, Union, Literal +from typing import Any, Dict, Literal, Optional, Type, Union from ..exception.output import FormatNotSupportedException from ..model.bom import Bom diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 901681a2..6ea2c941 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 Any, Dict, Optional, Type, Union, Literal +from typing import Any, Dict, Literal, Optional, Type, Union from xml.dom.minidom import parseString as dom_parseString from xml.etree.ElementTree import Element as XmlElement, tostring as xml_dumps diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index 46ae1cf7..32e7cad7 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -14,7 +14,7 @@ from abc import ABC, abstractmethod from importlib import import_module -from typing import TYPE_CHECKING, Any, Literal, Optional, Protocol, Type, overload, Union +from typing import TYPE_CHECKING, Any, Literal, Optional, Protocol, Type, Union, overload from ..schema import OutputFormat diff --git a/cyclonedx/validation/json.py b/cyclonedx/validation/json.py index 4c385d30..47278c65 100644 --- a/cyclonedx/validation/json.py +++ b/cyclonedx/validation/json.py @@ -16,7 +16,7 @@ from abc import ABC from json import loads as json_loads -from typing import TYPE_CHECKING, Any, Optional, Tuple, Literal +from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple from ..schema import OutputFormat diff --git a/cyclonedx/validation/xml.py b/cyclonedx/validation/xml.py index a2715ee9..177c2987 100644 --- a/cyclonedx/validation/xml.py +++ b/cyclonedx/validation/xml.py @@ -15,7 +15,7 @@ __all__ = ['XmlValidator'] from abc import ABC -from typing import TYPE_CHECKING, Any, Optional, Tuple, Literal +from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple from ..exception import MissingOptionalDependencyException from ..schema import OutputFormat From 9b8cd4942a78897ded2a27690e5981196434fa2a Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 18:57:38 +0200 Subject: [PATCH 05/16] docs Signed-off-by: Jan Kowalleck --- examples/complex.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/examples/complex.py b/examples/complex.py index 4c291b2d..641c2ed2 100644 --- a/examples/complex.py +++ b/examples/complex.py @@ -30,6 +30,14 @@ from cyclonedx.validation.json import JsonStrictValidator from cyclonedx.validation import get_instance as get_validator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from cyclonedx.output.json import Json as JsonOutputter + from cyclonedx.output.xml import Xml as XmlOutputter + from cyclonedx.validation.xml import XmlValidator + + lc_factory = LicenseChoiceFactory(license_factory=LicenseFactory()) # region build the BOM @@ -68,11 +76,15 @@ # endregion build the BOM +# region JSON +"""demo with explicit instructions for SchemaVersion, outputter and validator""" -serialized_json = JsonV1Dot4(bom).output_as_string(indent=2) +my_json_outputter: 'JsonOutputter' = JsonV1Dot4(bom) +serialized_json = my_json_outputter.output_as_string(indent=2) print(serialized_json) +my_json_validator = JsonStrictValidator(SchemaVersion.V1_4) try: - validation_errors = JsonStrictValidator(SchemaVersion.V1_4).validate_str(serialized_json) + validation_errors = my_json_validator.validate_str(serialized_json) if validation_errors: print('JSON invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr) sys.exit(2) @@ -82,16 +94,22 @@ print('', '=' * 30, '', sep='\n') -my_outputter = get_outputter(bom, OutputFormat.XML, SchemaVersion.V1_4) -serialized_xml = my_outputter.output_as_string(indent=2) +# endregion JSON + +# region XML +"""demo with implicit instructions for SchemaVersion, outputter and validator. TypeCheckers will catch errors.""" + +my_xml_outputter: 'XmlOutputter' = get_outputter(bom, OutputFormat.XML) +serialized_xml = my_xml_outputter.output_as_string(indent=2) print(serialized_xml) +my_xml_validator: 'XmlValidator' = get_validator(my_xml_outputter.output_format, my_xml_outputter.schema_version) try: - validation_errors = get_validator(my_outputter.output_format, - my_outputter.schema_version - ).validate_str(serialized_xml) + validation_errors = my_xml_validator.validate_str(serialized_xml) if validation_errors: print('XML invalid', 'ValidationError:', repr(validation_errors), sep='\n', file=sys.stderr) sys.exit(2) print('XML valid') except MissingOptionalDependencyException as error: print('XML-validation was skipped due to', error) + +# endregion XML From a2bc7e234d310af9fbf3931455ff2c7bf8fe83ce Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 19:09:30 +0200 Subject: [PATCH 06/16] docs Signed-off-by: Jan Kowalleck --- cyclonedx/schema/__init__.py | 6 ++++-- cyclonedx/schema/schema.py | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cyclonedx/schema/__init__.py b/cyclonedx/schema/__init__.py index fbbe92d7..ed7a5dd6 100644 --- a/cyclonedx/schema/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -19,7 +19,8 @@ class OutputFormat(Enum): """Output formats. - Do not rely on the actual/literal values, just use enum cases. + Do not rely on the actual/literal values, just use enum cases, like so: + my_of = OutputFormat.XML """ JSON = auto() XML = auto() @@ -33,7 +34,8 @@ class SchemaVersion(Enum): Cases are hashable. Cases are comparable(!=,>=,>,==,<,<=) - Do not rely on the actual/literal values, just use enum cases. + Do not rely on the actual/literal values, just use enum cases, like so: + my_sv = SchemaVersion.V1_3 """ V1_4 = (1, 4) V1_3 = (1, 3) diff --git a/cyclonedx/schema/schema.py b/cyclonedx/schema/schema.py index 64212a55..84434212 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, Type +from typing import Dict, Literal, Type from serializable import ViewType @@ -37,35 +37,35 @@ def get_schema_version(self) -> str: class SchemaVersion1Dot4(BaseSchemaVersion): @property - def schema_version_enum(self) -> SchemaVersion: + def schema_version_enum(self) -> Literal[SchemaVersion.V1_4]: return SchemaVersion.V1_4 class SchemaVersion1Dot3(BaseSchemaVersion): @property - def schema_version_enum(self) -> SchemaVersion: + def schema_version_enum(self) -> Literal[SchemaVersion.V1_3]: return SchemaVersion.V1_3 class SchemaVersion1Dot2(BaseSchemaVersion): @property - def schema_version_enum(self) -> SchemaVersion: + def schema_version_enum(self) -> Literal[SchemaVersion.V1_2]: return SchemaVersion.V1_2 class SchemaVersion1Dot1(BaseSchemaVersion): @property - def schema_version_enum(self) -> SchemaVersion: + def schema_version_enum(self) -> Literal[SchemaVersion.V1_1]: return SchemaVersion.V1_1 class SchemaVersion1Dot0(BaseSchemaVersion): @property - def schema_version_enum(self) -> SchemaVersion: + def schema_version_enum(self) -> Literal[SchemaVersion.V1_0]: return SchemaVersion.V1_0 From db08f8069b630bde06dd7bd2746e70044f700206 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 19:19:58 +0200 Subject: [PATCH 07/16] refactor Signed-off-by: Jan Kowalleck --- cyclonedx/schema/__init__.py | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/cyclonedx/schema/__init__.py b/cyclonedx/schema/__init__.py index ed7a5dd6..1588deaf 100644 --- a/cyclonedx/schema/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -13,6 +13,7 @@ # SPDX-License-Identifier: Apache-2.0 from enum import Enum, auto, unique +from typing import Hashable @unique @@ -53,34 +54,31 @@ def to_version(self) -> str: return '.'.join(map(str, self.value)) def __ne__(self, other: object) -> bool: - return self.value != other.value \ - if isinstance(other, self.__class__) \ - else NotImplemented # type:ignore[return-value] + if isinstance(other, self.__class__): + return self.value != other.value + return NotImplemented # pragma: no cover def __lt__(self, other: object) -> bool: - return self.value < other.value \ - if isinstance(other, self.__class__) \ - else NotImplemented # type:ignore[return-value] + if isinstance(other, self.__class__): + return self.value < other.value + return NotImplemented # pragma: no cover def __le__(self, other: object) -> bool: - return self.value <= other.value \ - if isinstance(other, self.__class__) \ - else NotImplemented # type:ignore[return-value] + if isinstance(other, self.__class__): + return self.value <= other.value + return NotImplemented # pragma: no cover def __eq__(self, other: object) -> bool: - return self.value == other.value \ - if isinstance(other, self.__class__) \ - else NotImplemented # type:ignore[return-value] + if isinstance(other, self.__class__): + return self.value == other.value + return NotImplemented # pragma: no cover def __ge__(self, other: object) -> bool: - return self.value >= other.value \ - if isinstance(other, self.__class__) \ - else NotImplemented # type:ignore[return-value] + if isinstance(other, self.__class__): + return self.value >= other.value + return NotImplemented # pragma: no cover def __gt__(self, other: object) -> bool: - return self.value > other.value \ - if isinstance(other, self.__class__) \ - else NotImplemented # type:ignore[return-value] - - def __hash__(self) -> int: - return hash(self.name) + if isinstance(other, self.__class__): + return self.value > other.value + return NotImplemented # pragma: no cover From 6ba6531d717954f05980541a7040a6250f9a47f0 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 19:27:30 +0200 Subject: [PATCH 08/16] refactor Signed-off-by: Jan Kowalleck --- cyclonedx/schema/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cyclonedx/schema/__init__.py b/cyclonedx/schema/__init__.py index 1588deaf..e8a44c38 100644 --- a/cyclonedx/schema/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -20,12 +20,17 @@ class OutputFormat(Enum): """Output formats. + Cases are hashable. + Do not rely on the actual/literal values, just use enum cases, like so: my_of = OutputFormat.XML """ JSON = auto() XML = auto() + def __hash__(self) -> int: + return hash(self.name) + @unique class SchemaVersion(Enum): @@ -82,3 +87,6 @@ def __gt__(self, other: object) -> bool: if isinstance(other, self.__class__): return self.value > other.value return NotImplemented # pragma: no cover + + def __hash__(self) -> int: + return hash(self.name) From 06f62d146e3e71c738f7f8c8e92d98bd4e6b5c58 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 19:40:06 +0200 Subject: [PATCH 09/16] refactor Signed-off-by: Jan Kowalleck --- cyclonedx/schema/__init__.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cyclonedx/schema/__init__.py b/cyclonedx/schema/__init__.py index e8a44c38..77a2557a 100644 --- a/cyclonedx/schema/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -13,7 +13,9 @@ # SPDX-License-Identifier: Apache-2.0 from enum import Enum, auto, unique -from typing import Hashable +from typing import Any, Type, TypeVar + +_T = TypeVar('_T') @unique @@ -25,6 +27,7 @@ class OutputFormat(Enum): Do not rely on the actual/literal values, just use enum cases, like so: my_of = OutputFormat.XML """ + JSON = auto() XML = auto() @@ -43,6 +46,7 @@ class SchemaVersion(Enum): Do not rely on the actual/literal values, just use enum cases, like so: my_sv = SchemaVersion.V1_3 """ + V1_4 = (1, 4) V1_3 = (1, 3) V1_2 = (1, 2) @@ -50,40 +54,40 @@ class SchemaVersion(Enum): V1_0 = (1, 0) @classmethod - def from_version(cls, version: str) -> 'SchemaVersion': - """Return instance from a version string - e.g. `1.4`""" + def from_version(cls: Type[_T], version: str) -> _T: + """Return instance based of a version string - e.g. `1.4`""" return cls(tuple(map(int, version.split('.')))[:2]) def to_version(self) -> str: """Return as a version string - e.g. `1.4`""" return '.'.join(map(str, self.value)) - def __ne__(self, other: object) -> bool: + def __ne__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self.value != other.value return NotImplemented # pragma: no cover - def __lt__(self, other: object) -> bool: + def __lt__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self.value < other.value return NotImplemented # pragma: no cover - def __le__(self, other: object) -> bool: + def __le__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self.value <= other.value return NotImplemented # pragma: no cover - def __eq__(self, other: object) -> bool: + def __eq__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self.value == other.value return NotImplemented # pragma: no cover - def __ge__(self, other: object) -> bool: + def __ge__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self.value >= other.value return NotImplemented # pragma: no cover - def __gt__(self, other: object) -> bool: + def __gt__(self, other: Any) -> bool: if isinstance(other, self.__class__): return self.value > other.value return NotImplemented # pragma: no cover From e7cf31334d3cfbe08ca30595a3df5bf5d6dc3c50 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 5 Oct 2023 19:52:26 +0200 Subject: [PATCH 10/16] typing Signed-off-by: Jan Kowalleck --- cyclonedx/output/__init__.py | 2 +- cyclonedx/schema/__init__.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index c69c5798..9768fde5 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -107,7 +107,7 @@ def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.XML] = ..., @overload def get_instance(bom: 'Bom', output_format: OutputFormat = ..., - schema_version: SchemaVersion = LATEST_SUPPORTED_SCHEMA_VERSION + schema_version: SchemaVersion = ... ) -> Union['XmlOutputter', 'JsonOutputter']: ... diff --git a/cyclonedx/schema/__init__.py b/cyclonedx/schema/__init__.py index 77a2557a..9c592c25 100644 --- a/cyclonedx/schema/__init__.py +++ b/cyclonedx/schema/__init__.py @@ -15,8 +15,6 @@ from enum import Enum, auto, unique from typing import Any, Type, TypeVar -_T = TypeVar('_T') - @unique class OutputFormat(Enum): @@ -35,6 +33,9 @@ def __hash__(self) -> int: return hash(self.name) +_SV = TypeVar('_SV', bound='SchemaVersion') + + @unique class SchemaVersion(Enum): """ @@ -54,7 +55,7 @@ class SchemaVersion(Enum): V1_0 = (1, 0) @classmethod - def from_version(cls: Type[_T], version: str) -> _T: + 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]) From 63ab8d9cc81ed703ad00f9cf722abe312f30dfcb Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 6 Oct 2023 01:17:37 +0200 Subject: [PATCH 11/16] typing Signed-off-by: Jan Kowalleck --- cyclonedx/output/json.py | 13 ++++++++----- cyclonedx/output/xml.py | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index d00eb0a5..34644bde 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -17,10 +17,9 @@ from abc import abstractmethod from json import dumps as json_dumps, loads as json_loads -from typing import Any, Dict, Literal, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, Union from ..exception.output import FormatNotSupportedException -from ..model.bom import Bom from ..schema import OutputFormat, SchemaVersion from ..schema.schema import ( SCHEMA_VERSIONS, @@ -33,10 +32,13 @@ ) from . import BaseOutput +if TYPE_CHECKING: + from ..model.bom import Bom + class Json(BaseOutput, BaseSchemaVersion): - def __init__(self, bom: Bom) -> None: + def __init__(self, bom: 'Bom') -> None: super().__init__(bom=bom) self._bom_json: Dict[str, Any] = dict() @@ -63,9 +65,10 @@ def generate(self, force_regeneration: bool = False) -> None: 'specVersion': self.schema_version.to_version() } _view = SCHEMA_VERSIONS.get(self.schema_version_enum) - self.get_bom().validate() + bom = self.get_bom() + bom.validate() bom_json: Dict[str, Any] = json_loads( - self.get_bom().as_json( # type:ignore[attr-defined] + bom.as_json( # type:ignore[attr-defined] view_=_view)) bom_json.update(_json_core) self._bom_json = bom_json diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 6ea2c941..c0a7d572 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -16,11 +16,10 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Any, Dict, Literal, Optional, Type, Union +from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, Union from xml.dom.minidom import parseString as dom_parseString from xml.etree.ElementTree import Element as XmlElement, tostring as xml_dumps -from ..model.bom import Bom from ..schema import OutputFormat, SchemaVersion from ..schema.schema import ( SCHEMA_VERSIONS, @@ -33,9 +32,12 @@ ) from . import BaseOutput +if TYPE_CHECKING: + from ..model.bom import Bom + class Xml(BaseSchemaVersion, BaseOutput): - def __init__(self, bom: Bom) -> None: + def __init__(self, bom: 'Bom') -> None: super().__init__(bom=bom) self._bom_xml: str = '' @@ -52,10 +54,11 @@ def generate(self, force_regeneration: bool = False) -> None: return _view = SCHEMA_VERSIONS[self.schema_version_enum] - self.get_bom().validate() + bom = self.get_bom() + bom.validate() xmlns = self.get_target_namespace() self._bom_xml = '\n' + xml_dumps( - self.get_bom().as_xml( # type:ignore[attr-defined] + bom.as_xml( # type:ignore[attr-defined] _view, as_string=False, xmlns=xmlns), method='xml', default_namespace=xmlns, encoding='unicode', # `xml-declaration` is inconsistent/bugged in py38, especially on Windows it will print a non-UTF8 codepage. From 881d582a80b9dd5671b69d786696ed2223200484 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 6 Oct 2023 01:42:22 +0200 Subject: [PATCH 12/16] ignore coverage in typehecking Signed-off-by: Jan Kowalleck --- cyclonedx/model/bom.py | 2 +- cyclonedx/output/__init__.py | 2 +- cyclonedx/output/json.py | 2 +- cyclonedx/output/xml.py | 2 +- cyclonedx/spdx.py | 2 +- cyclonedx/validation/__init__.py | 2 +- cyclonedx/validation/json.py | 4 ++-- cyclonedx/validation/xml.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 67f04ed6..776fa834 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -50,7 +50,7 @@ from .service import Service from .vulnerability import Vulnerability -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from packageurl import PackageURL diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 9768fde5..fde15bc1 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -24,7 +24,7 @@ from ..schema import OutputFormat, SchemaVersion -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..model.bom import Bom from ..model.component import Component from .json import Json as JsonOutputter diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 34644bde..2fadb30e 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -32,7 +32,7 @@ ) from . import BaseOutput -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..model.bom import Bom diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index c0a7d572..b57e2db8 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -32,7 +32,7 @@ ) from . import BaseOutput -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..model.bom import Bom diff --git a/cyclonedx/spdx.py b/cyclonedx/spdx.py index 3eae6221..b7dfe052 100644 --- a/cyclonedx/spdx.py +++ b/cyclonedx/spdx.py @@ -24,7 +24,7 @@ from .schema._res import SPDX_JSON as __SPDX_JSON_SCHEMA -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from license_expression import Licensing # region init diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index 32e7cad7..daeb075f 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -18,7 +18,7 @@ from ..schema import OutputFormat -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..schema import SchemaVersion from .json import JsonValidator from .xml import XmlValidator diff --git a/cyclonedx/validation/json.py b/cyclonedx/validation/json.py index 47278c65..721dd5d7 100644 --- a/cyclonedx/validation/json.py +++ b/cyclonedx/validation/json.py @@ -20,7 +20,7 @@ from ..schema import OutputFormat -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..schema import SchemaVersion from ..exception import MissingOptionalDependencyException @@ -34,7 +34,7 @@ from referencing import Registry from referencing.jsonschema import DRAFT7 - if TYPE_CHECKING: + if TYPE_CHECKING: # pragma: no cover from jsonschema.protocols import Validator as JsonSchemaValidator # type: ignore[import] except ImportError as err: _missing_deps_error = MissingOptionalDependencyException( diff --git a/cyclonedx/validation/xml.py b/cyclonedx/validation/xml.py index 177c2987..6da23b2a 100644 --- a/cyclonedx/validation/xml.py +++ b/cyclonedx/validation/xml.py @@ -22,7 +22,7 @@ from ..schema._res import BOM_XML as _S_BOM from . import BaseValidator, ValidationError, Validator -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..schema import SchemaVersion _missing_deps_error: Optional[Tuple[MissingOptionalDependencyException, ImportError]] = None From c36f86562615326e5bbc43d4e79f7a2576bc9751 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 6 Oct 2023 01:47:18 +0200 Subject: [PATCH 13/16] ignore coverage in overloads Signed-off-by: Jan Kowalleck --- cyclonedx/output/__init__.py | 6 +++--- cyclonedx/validation/__init__.py | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index fde15bc1..9bfb2fb8 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -95,20 +95,20 @@ def output_to_file(self, filename: str, allow_overwrite: bool = False, *, @overload def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.JSON], - schema_version: SchemaVersion = ...) -> 'JsonOutputter': + schema_version: SchemaVersion = ...) -> 'JsonOutputter': # pragma: no cover ... @overload def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.XML] = ..., - schema_version: SchemaVersion = ...) -> 'XmlOutputter': + schema_version: SchemaVersion = ...) -> 'XmlOutputter': # pragma: no cover ... @overload def get_instance(bom: 'Bom', output_format: OutputFormat = ..., schema_version: SchemaVersion = ... - ) -> Union['XmlOutputter', 'JsonOutputter']: + ) -> Union['XmlOutputter', 'JsonOutputter']: # pragma: no cover ... diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index daeb075f..51287928 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -83,18 +83,20 @@ def _schema_file(self) -> Optional[str]: @overload -def get_instance(output_format: Literal[OutputFormat.JSON], schema_version: 'SchemaVersion') -> 'JsonValidator': +def get_instance(output_format: Literal[OutputFormat.JSON], schema_version: 'SchemaVersion' + ) -> 'JsonValidator': # pragma: no cover ... @overload -def get_instance(output_format: Literal[OutputFormat.XML], schema_version: 'SchemaVersion') -> 'XmlValidator': +def get_instance(output_format: Literal[OutputFormat.XML], schema_version: 'SchemaVersion' + ) -> 'XmlValidator': # pragma: no cover ... @overload def get_instance(output_format: OutputFormat, schema_version: 'SchemaVersion' - ) -> Union['JsonValidator', 'XmlValidator']: + ) -> Union['JsonValidator', 'XmlValidator']: # pragma: no cover ... From fd68bcc624175442c0e00e9b4deec3e9162f1b6f Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 6 Oct 2023 01:57:47 +0200 Subject: [PATCH 14/16] tidy Signed-off-by: Jan Kowalleck --- cyclonedx/output/__init__.py | 6 +++--- cyclonedx/validation/__init__.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 9bfb2fb8..fde15bc1 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -95,20 +95,20 @@ def output_to_file(self, filename: str, allow_overwrite: bool = False, *, @overload def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.JSON], - schema_version: SchemaVersion = ...) -> 'JsonOutputter': # pragma: no cover + schema_version: SchemaVersion = ...) -> 'JsonOutputter': ... @overload def get_instance(bom: 'Bom', output_format: Literal[OutputFormat.XML] = ..., - schema_version: SchemaVersion = ...) -> 'XmlOutputter': # pragma: no cover + schema_version: SchemaVersion = ...) -> 'XmlOutputter': ... @overload def get_instance(bom: 'Bom', output_format: OutputFormat = ..., schema_version: SchemaVersion = ... - ) -> Union['XmlOutputter', 'JsonOutputter']: # pragma: no cover + ) -> Union['XmlOutputter', 'JsonOutputter']: ... diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index 51287928..87f18335 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -84,19 +84,19 @@ def _schema_file(self) -> Optional[str]: @overload def get_instance(output_format: Literal[OutputFormat.JSON], schema_version: 'SchemaVersion' - ) -> 'JsonValidator': # pragma: no cover + ) -> 'JsonValidator': ... @overload def get_instance(output_format: Literal[OutputFormat.XML], schema_version: 'SchemaVersion' - ) -> 'XmlValidator': # pragma: no cover + ) -> 'XmlValidator': ... @overload def get_instance(output_format: OutputFormat, schema_version: 'SchemaVersion' - ) -> Union['JsonValidator', 'XmlValidator']: # pragma: no cover + ) -> Union['JsonValidator', 'XmlValidator']: ... From 48bbf338f5593fa2e78dac57e90689c75cb68a34 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 6 Oct 2023 13:01:51 +0200 Subject: [PATCH 15/16] migrate py-serializable Signed-off-by: Jan Kowalleck --- pyproject.toml | 2 +- tests/test_deserialize_xml.py | 6 ++---- tests/test_real_world_examples.py | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6a77418c..feeaf013 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ keywords = [ # ATTENTION: keep `deps.lowest.r` file in sync python = "^3.8" packageurl-python = ">= 0.11" -py-serializable = { git = "https://github.com/madpah/serializable.git", branch = "fix/typehints-attempt1" } +py-serializable = "^0.14.0" sortedcontainers = "^2.4.0" license-expression = "^30" jsonschema = { version = "^4.18", extras=['format'], optional=true } diff --git a/tests/test_deserialize_xml.py b/tests/test_deserialize_xml.py index f9502fb4..dc6e3a6e 100644 --- a/tests/test_deserialize_xml.py +++ b/tests/test_deserialize_xml.py @@ -20,7 +20,6 @@ from typing import cast from unittest.mock import Mock, patch from uuid import UUID -from xml.etree import ElementTree from cyclonedx.model.bom import Bom from cyclonedx.output import LATEST_SUPPORTED_SCHEMA_VERSION, SchemaVersion, get_instance @@ -686,11 +685,10 @@ def _validate_xml_bom(self, bom: Bom, schema_version: SchemaVersion, fixture: st if schema_version != LATEST_SUPPORTED_SCHEMA_VERSION: # Rewind the BOM to only have data supported by the SchemaVersion in question outputter = get_instance(bom=bom, output_format=OutputFormat.XML, schema_version=schema_version) - bom = cast(Bom, Bom.from_xml(data=ElementTree.fromstring(outputter.output_as_string()))) + bom = cast(Bom, Bom.from_xml(outputter.output_as_string())) with open(join(RELEVANT_TESTDATA_DIRECTORY, schema_version.to_version(), fixture)) as input_xml: - xml = input_xml.read() - deserialized_bom = cast(Bom, Bom.from_xml(data=ElementTree.fromstring(xml))) + deserialized_bom = cast(Bom, Bom.from_xml(input_xml)) self.assertEqual(bom.metadata, deserialized_bom.metadata) diff --git a/tests/test_real_world_examples.py b/tests/test_real_world_examples.py index 7be3b081..0a9e2b16 100644 --- a/tests/test_real_world_examples.py +++ b/tests/test_real_world_examples.py @@ -19,7 +19,6 @@ from os.path import join from typing import cast from unittest.mock import patch -from xml.etree import ElementTree from cyclonedx.model.bom import Bom from cyclonedx.schema import SchemaVersion @@ -43,5 +42,4 @@ def test_webgoat_6_1(self) -> None: def _attempt_load_example(self, schema_version: SchemaVersion, fixture: str) -> None: with open(join(RELEVANT_TESTDATA_DIRECTORY, schema_version.to_version(), fixture)) as input_xml: - xml = input_xml.read() - cast(Bom, Bom.from_xml(data=ElementTree.fromstring(xml))) + cast(Bom, Bom.from_xml(input_xml)) From 2832a0fd2af0aabcfba21ad2fe34cdf8e8bc8a21 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 6 Oct 2023 13:11:38 +0200 Subject: [PATCH 16/16] fix tests Signed-off-by: Jan Kowalleck --- tests/test_deserialize_xml.py | 3 ++- tests/test_real_world_examples.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_deserialize_xml.py b/tests/test_deserialize_xml.py index dc6e3a6e..2b32ed95 100644 --- a/tests/test_deserialize_xml.py +++ b/tests/test_deserialize_xml.py @@ -16,6 +16,7 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. from datetime import datetime +from io import StringIO from os.path import join from typing import cast from unittest.mock import Mock, patch @@ -685,7 +686,7 @@ def _validate_xml_bom(self, bom: Bom, schema_version: SchemaVersion, fixture: st if schema_version != LATEST_SUPPORTED_SCHEMA_VERSION: # Rewind the BOM to only have data supported by the SchemaVersion in question outputter = get_instance(bom=bom, output_format=OutputFormat.XML, schema_version=schema_version) - bom = cast(Bom, Bom.from_xml(outputter.output_as_string())) + bom = cast(Bom, Bom.from_xml(StringIO(outputter.output_as_string()))) with open(join(RELEVANT_TESTDATA_DIRECTORY, schema_version.to_version(), fixture)) as input_xml: deserialized_bom = cast(Bom, Bom.from_xml(input_xml)) diff --git a/tests/test_real_world_examples.py b/tests/test_real_world_examples.py index 0a9e2b16..795e38f4 100644 --- a/tests/test_real_world_examples.py +++ b/tests/test_real_world_examples.py @@ -14,6 +14,7 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. + import unittest from datetime import datetime from os.path import join