diff --git a/.isort.cfg b/.isort.cfg index 2b28db04..7a30df96 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -20,3 +20,5 @@ src_paths = cyclonedx tests typings + examples + tools diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75fb111b..b8b34b7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,6 @@ But please read the [CycloneDX contributing guidelines](https://github.com/CycloneDX/.github/blob/master/CONTRIBUTING.md) first. -See also: [contrib docs](docs/contributing.rst) - ## Setup This project uses [poetry]. Have it installed and setup first. diff --git a/cyclonedx/exception/__init__.py b/cyclonedx/exception/__init__.py index dc915bfb..1845b006 100644 --- a/cyclonedx/exception/__init__.py +++ b/cyclonedx/exception/__init__.py @@ -25,3 +25,8 @@ class CycloneDxException(Exception): Root exception thrown by this library. """ pass + + +class MissingOptionalDependencyException(CycloneDxException): + """Validation did not happen, due to missing dependencies.""" + pass diff --git a/cyclonedx/validation/__init__.py b/cyclonedx/validation/__init__.py index 4afd457d..78c96845 100644 --- a/cyclonedx/validation/__init__.py +++ b/cyclonedx/validation/__init__.py @@ -15,17 +15,20 @@ # SPDX-License-Identifier: Apache-2.0 from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Protocol if TYPE_CHECKING: from ..schema import SchemaVersion -class MissingOptionalDependencyException(BaseException): - pass +class ValidationError: + """Validation failed with this specific error. + Use :attr:`~data` to access the content. + """ + + data: Any -class ValidationError: def __init__(self, data: Any) -> None: self.data = data @@ -36,22 +39,29 @@ def __str__(self) -> str: return str(self.data) -class _BaseValidator(ABC): +class Validator(Protocol): + """Validator protocol""" + + def validate_str(self, data: str) -> Optional[ValidationError]: + """Validate a string""" + ... + + +class _BaseValidator(Validator, ABC): + """this class is non-public. changes without notice may happen.""" def __init__(self, schema_version: 'SchemaVersion') -> None: self.__schema_version = schema_version if not self._schema_file: raise ValueError(f'unsupported schema: {schema_version}') - @property - @abstractmethod - def _schema_file(self) -> Optional[str]: - ... - @property def schema_version(self) -> 'SchemaVersion': + """get the schema version.""" return self.__schema_version + @property @abstractmethod - def validate_str(self, data: str) -> Optional[ValidationError]: + def _schema_file(self) -> Optional[str]: + """get the schema file according to schema version.""" ... diff --git a/cyclonedx/validation/json.py b/cyclonedx/validation/json.py index e181443a..8b7cc1ee 100644 --- a/cyclonedx/validation/json.py +++ b/cyclonedx/validation/json.py @@ -20,8 +20,12 @@ from json import loads as json_loads from typing import TYPE_CHECKING, Any, Optional, Tuple +if TYPE_CHECKING: + from ..schema import SchemaVersion + +from ..exception import MissingOptionalDependencyException 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 MissingOptionalDependencyException, ValidationError, _BaseValidator +from . import ValidationError, _BaseValidator _missing_deps_error: Optional[Tuple[MissingOptionalDependencyException, ImportError]] = None try: @@ -39,7 +43,16 @@ ), err -class __BaseJsonValidator(_BaseValidator, ABC): +class _BaseJsonValidator(_BaseValidator, ABC): + + def __init__(self, schema_version: 'SchemaVersion') -> None: + # this is the def that is used for generating the documentation + super().__init__(schema_version) + + def validate_str(self, data: str) -> Optional[ValidationError]: + """Validate a string according to the schema version.""" + # this is the def that is used for generating the documentation + if _missing_deps_error: __MDERROR = _missing_deps_error @@ -84,15 +97,20 @@ def __make_validator_registry() -> Registry[Any]: ]) -class JsonValidator(__BaseJsonValidator): +class JsonValidator(_BaseJsonValidator): + """Validator for CycloneDX documents in JSON format.""" @property def _schema_file(self) -> Optional[str]: return _S_BOM.get(self.schema_version) -class JsonStrictValidator(__BaseJsonValidator): +class JsonStrictValidator(_BaseJsonValidator): + """Strict validator for CycloneDX documents in JSON format. + In contrast to :class:`~JsonValidator`, + the document must not have additional or unknown JSON properties. + """ @property def _schema_file(self) -> Optional[str]: return _S_BOM_STRICT.get(self.schema_version) diff --git a/cyclonedx/validation/xml.py b/cyclonedx/validation/xml.py index 65a3e5ad..c7775546 100644 --- a/cyclonedx/validation/xml.py +++ b/cyclonedx/validation/xml.py @@ -17,10 +17,14 @@ __all__ = ['XmlValidator'] from abc import ABC -from typing import Any, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional, Tuple +from ..exception import MissingOptionalDependencyException from ..schema._res import BOM_XML as _S_BOM -from . import MissingOptionalDependencyException, ValidationError, _BaseValidator +from . import ValidationError, _BaseValidator + +if TYPE_CHECKING: + from ..schema import SchemaVersion _missing_deps_error: Optional[Tuple[MissingOptionalDependencyException, ImportError]] = None try: @@ -32,7 +36,16 @@ ), err -class __BaseXmlValidator(_BaseValidator, ABC): +class _BaseXmlValidator(_BaseValidator, ABC): + + def __init__(self, schema_version: 'SchemaVersion') -> None: + # this is the def that is used for generating the documentation + super().__init__(schema_version) + + def validate_str(self, data: str) -> Optional[ValidationError]: + """Validate a string according to the schema version.""" + # this is the def that is used for generating the documentation + if _missing_deps_error: __MDERROR = _missing_deps_error @@ -67,7 +80,9 @@ def _validator(self) -> 'XMLSchema': return self.__validator -class XmlValidator(__BaseXmlValidator): +class XmlValidator(_BaseXmlValidator): + """Validator for CycloneDX documents in XML format.""" + @property def _schema_file(self) -> Optional[str]: return _S_BOM.get(self.schema_version) diff --git a/docs/conf.py b/docs/conf.py index 9faa5c7a..5abdefc5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,15 +31,18 @@ # ones. extensions = [ "sphinx.ext.autodoc", - "sphinx.ext.viewcode", + # "sphinx.ext.viewcode", "autoapi.extension", "sphinx_rtd_theme", "m2r2" ] + # Document Python Code autoapi_type = 'python' autoapi_dirs = ['../cyclonedx'] +# see https://sphinx-autoapi.readthedocs.io/en/latest/reference/config.html#confval-autoapi_options +autoapi_options = ['show-module-summary', 'members', 'undoc-members', 'inherited-members', 'show-inheritance'] source_suffix = ['.rst', '.md'] diff --git a/docs/contributing.rst b/docs/contributing.rst index ff1705b3..4fc50161 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -1,83 +1 @@ -Contributing -==================================================== - -Pull requests are welcome, but please read these contributing guidelines first. - -Setup ----------------------------------------------------- - -This project uses `poetry`_. Have it installed and setup first. - -To install dev-dependencies and tools: - -.. code-block:: - - poetry install --all-extras - -Code style ----------------------------------------------------- - -This project uses `PEP8`_ Style Guide for Python Code. This project loves sorted imports. Get it all applied via: - -.. code-block:: - - poetry run isort . - poetry run autopep8 -ir cyclonedx/ tests/ typings/ examples/ - - -Documentation ----------------------------------------------------- - -This project uses `Sphinx`_ to generate documentation which is automatically published to `readthedocs.io`_. - -Source for documentation is stored in the ``docs`` folder in `RST`_ format. - -You can generate the documentation locally by running: - -.. code-block:: - - cd docs - pip install -r requirements.txt - make html - - -Testing ----------------------------------------------------- - -Run all tests in dedicated environments, via: - -.. code-block:: - - poetry run tox run - - -Sign your commits ----------------------------------------------------- - -Please sign your commits, to show that you agree to publish your changes under the current terms and licenses of the -project. We can't accept contributions, however great, if you do not sign your commits. - -.. code-block:: - - git commit --signed-off ... - - -Pre-commit hooks ----------------------------------------------------- - -If you like to take advantage of `pre-commit hooks`_, you can do so to cover most of the topics on this page when -contributing. - -.. code-block:: - - pre-commit install - -All our pre-commit checks will run locally before you can commit! - - -.. _poetry: https://python-poetry.org -.. _PEP8: https://www.python.org/dev/peps/pep-0008 -.. _Sphinx: https://www.sphinx-doc.org/ -.. _readthedocs.io: https://cyclonedx-python-library.readthedocs.io/ -.. _RST: https://en.wikipedia.org/wiki/ReStructuredText -.. _pre-commit hooks: https://pre-commit.com +.. mdinclude:: ../CONTRIBUTING.md diff --git a/docs/requirements.txt b/docs/requirements.txt index db0c3d0f..dfba7e14 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ m2r2>=0.3.2 -Sphinx>=4.3.2 -sphinx-autoapi>=1.8.4 -sphinx-rtd-theme>=1.0.0 \ No newline at end of file +Sphinx>=7.2.6,<8 +sphinx-autoapi>=2.1.1,<3 +sphinx-rtd-theme>=1.3.0,<2 diff --git a/examples/complex.py b/examples/complex.py index f39460c3..f64b1652 100644 --- a/examples/complex.py +++ b/examples/complex.py @@ -1,5 +1,8 @@ import sys +from packageurl import PackageURL + +from cyclonedx.exception import MissingOptionalDependencyException from cyclonedx.factory.license import LicenseChoiceFactory, LicenseFactory from cyclonedx.model import OrganizationalEntity, XsUri from cyclonedx.model.bom import Bom @@ -7,10 +10,8 @@ from cyclonedx.output.json import JsonV1Dot4 from cyclonedx.output.xml import XmlV1Dot4 from cyclonedx.schema import SchemaVersion -from cyclonedx.validation import MissingOptionalDependencyException from cyclonedx.validation.json import JsonValidator from cyclonedx.validation.xml import XmlValidator -from packageurl import PackageURL lc_factory = LicenseChoiceFactory(license_factory=LicenseFactory()) diff --git a/tests/base.py b/tests/base.py index a0bbb2b2..fff18cdf 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,8 +28,8 @@ from xmldiff import main from xmldiff.actions import MoveNode +from cyclonedx.exception import MissingOptionalDependencyException from cyclonedx.output import SchemaVersion -from cyclonedx.validation import MissingOptionalDependencyException from cyclonedx.validation.json import JsonValidator from cyclonedx.validation.xml import XmlValidator diff --git a/tests/test_validation_json.py b/tests/test_validation_json.py index 2e919e60..ab00304e 100644 --- a/tests/test_validation_json.py +++ b/tests/test_validation_json.py @@ -24,8 +24,8 @@ from ddt import data, ddt, idata, unpack +from cyclonedx.exception import MissingOptionalDependencyException from cyclonedx.schema import SchemaVersion -from cyclonedx.validation import MissingOptionalDependencyException from cyclonedx.validation.json import JsonStrictValidator, JsonValidator from . import TESTDATA_DIRECTORY