From 8cb4fbbf968710238089b4a665e4088a135dd63b Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Mon, 10 Nov 2025 14:22:36 +0100 Subject: [PATCH] feat(package_config): Add classes for config package_configurations - Removed old tests pointing to old models - Added validators to proper parse Identifier Signed-off-by: Helio Chissini de Castro --- pyproject.toml | 2 +- schemas/repository-configuration-schema.json | 73 ++++++++++++------ src/ort/__init__.py | 10 --- src/ort/models/analyzer_configurations.py | 32 -------- .../models/config/analyzer_configuration.py | 77 +++++++++++++++++++ .../models/config/license_finding_curation.py | 18 ++++- .../models/config/package_configuration.py | 68 ++++++++++++++++ .../config/package_manager_configuration.py | 26 +++++++ src/ort/models/config/path_exclude.py | 32 ++++++++ src/ort/models/config/path_exclude_reason.py | 73 ++++++++++++++++++ src/ort/models/config/path_include_reason.py | 2 + .../repository_analyzer_configuration.py | 48 ++++++++++++ src/ort/models/config/vcsmatcher.py | 38 +++++++++ src/ort/models/identifier.py | 63 +++++++++++++++ src/ort/models/ort_configuration.py | 9 +-- src/ort/models/package_curation_data.py | 21 +++++ src/ort/models/package_managers.py | 55 ------------- src/ort/models/repository_configuration.py | 14 ++-- tests/data/example_simple_package_config.yml | 11 +++ tests/test_ort_configuration.py | 27 ------- tests/test_package_configuration.py | 19 +++++ tests/test_package_curation.py | 4 +- tests/utils/test_package_configuration.py | 0 uv.lock | 2 +- 24 files changed, 556 insertions(+), 168 deletions(-) delete mode 100644 src/ort/models/analyzer_configurations.py create mode 100644 src/ort/models/config/analyzer_configuration.py create mode 100644 src/ort/models/config/package_configuration.py create mode 100644 src/ort/models/config/package_manager_configuration.py create mode 100644 src/ort/models/config/path_exclude.py create mode 100644 src/ort/models/config/path_exclude_reason.py create mode 100644 src/ort/models/config/path_include_reason.py create mode 100644 src/ort/models/config/repository_analyzer_configuration.py create mode 100644 src/ort/models/config/vcsmatcher.py create mode 100644 src/ort/models/identifier.py delete mode 100644 src/ort/models/package_managers.py create mode 100644 tests/data/example_simple_package_config.yml delete mode 100644 tests/test_ort_configuration.py create mode 100644 tests/test_package_configuration.py create mode 100644 tests/utils/test_package_configuration.py diff --git a/pyproject.toml b/pyproject.toml index 043e186..d094830 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "uv_build" [project] name = "python-ort" -version = "0.3.1" +version = "0.4.0" description = "A Python Ort model serialization library" readme = "README.md" license = "MIT" diff --git a/schemas/repository-configuration-schema.json b/schemas/repository-configuration-schema.json index 9edcaa7..54f1e14 100644 --- a/schemas/repository-configuration-schema.json +++ b/schemas/repository-configuration-schema.json @@ -796,7 +796,7 @@ "vcs": { "anyOf": [ { - "$ref": "#/$defs/VcsInfo" + "$ref": "#/$defs/VcsInfoCurationData" }, { "type": "null" @@ -1263,49 +1263,72 @@ "title": "Sw360Configuration", "type": "object" }, - "VcsInfo": { + "VcsInfoCurationData": { "description": "Bundles general Version Control System information.\n\nAttributes:\n type(VcsType): The type of the VCS, for example Git, GitRepo, Mercurial, etc.\n url(AnyUrl): The URL to the VCS repository.\n revision(str): The VCS-specific revision (tag, branch, SHA1) that the version of the package maps to.\n path(str): The path inside the VCS to take into account.\n If the VCS supports checking out only a subdirectory, only this path is checked out.", "properties": { "type": { - "$ref": "#/$defs/VcsType", + "anyOf": [ + { + "$ref": "#/$defs/VcsType" + }, + { + "type": "null" + } + ], + "default": null, "description": "The type of the VCS, for example Git, GitRepo, Mercurial, etc." }, "url": { + "anyOf": [ + { + "format": "uri", + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The URL to the VCS repository.", - "format": "uri", - "minLength": 1, - "title": "Url", - "type": "string" + "title": "Url" }, "revision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The VCS-specific revision (tag, branch, SHA1) that the version of the package maps to.", - "title": "Revision", - "type": "string" + "title": "Revision" }, "path": { - "default": "", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The path inside the VCS to take into account.If the VCS supports checking out only a subdirectory, only this path is checked out.", - "title": "Path", - "type": "string" + "title": "Path" } }, - "required": [ - "url", - "revision" - ], - "title": "VcsInfo", + "title": "VcsInfoCurationData", "type": "object" }, "VcsType": { - "description": "A class for Version Control System types. Each type has one or more [aliases] associated to it,\nwhere the first alias is the definite name. This class is not implemented as an enum as\nconstructing from an unknown type should be supported while maintaining that type as the primary\nalias for the string representation.\n\nAttributes:\n aliases(list[str]): Primary name and aliases", + "description": "A class for Version Control System types. Each type has one or more [aliases] associated to it,\nwhere the first alias is the definite name. This class is not implemented as an enum as\nconstructing from an unknown type should be supported while maintaining that type as the primary\nalias for the string representation.\n\nAttributes:\n name(str): Primary name and aliases", "properties": { - "aliases": { - "description": "Primary name and aliases", - "items": { - "type": "string" - }, - "title": "Aliases", - "type": "array" + "name": { + "title": "Name", + "type": "string" } }, "title": "VcsType", diff --git a/src/ort/__init__.py b/src/ort/__init__.py index 3afc931..6d01aa6 100644 --- a/src/ort/__init__.py +++ b/src/ort/__init__.py @@ -2,18 +2,8 @@ # # SPDX-License-Identifier: MIT -from ort.models.analyzer_configurations import OrtAnalyzerConfigurations -from ort.models.ort_configuration import OrtConfiguration, Scanner, Severity, Storages -from ort.models.package_managers import OrtPackageManagerConfigurations, OrtPackageManagers from ort.models.repository_configuration import OrtRepositoryConfiguration __all__ = [ - "OrtAnalyzerConfigurations", - "OrtConfiguration", - "OrtPackageManagerConfigurations", - "OrtPackageManagers", "OrtRepositoryConfiguration", - "Scanner", - "Severity", - "Storages", ] diff --git a/src/ort/models/analyzer_configurations.py b/src/ort/models/analyzer_configurations.py deleted file mode 100644 index 4c4af04..0000000 --- a/src/ort/models/analyzer_configurations.py +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro -# SPDX-License-Identifier: MIT - - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field - -from .package_managers import OrtPackageManagerConfigurations, OrtPackageManagers - - -class Sw360Configuration(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - rest_url: AnyUrl = Field(..., alias="restUrl") - auth_url: AnyUrl = Field(..., alias="authUrl") - username: str - password: str | None = None - client_id: str = Field(..., alias="clientId") - client_password: str | None = Field(None, alias="clientPassword") - token: str | None = None - - -class OrtAnalyzerConfigurations(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - allow_dynamic_versions: bool | None = Field(None) - enabled_package_managers: list[OrtPackageManagers] | None = Field(None) - disabled_package_managers: list[OrtPackageManagers] | None = Field(None) - package_managers: OrtPackageManagerConfigurations | None = Field(None) - sw360_configuration: Sw360Configuration | None = Field(None) - skip_excluded: bool | None = Field(None) diff --git a/src/ort/models/config/analyzer_configuration.py b/src/ort/models/config/analyzer_configuration.py new file mode 100644 index 0000000..d3c6d8b --- /dev/null +++ b/src/ort/models/config/analyzer_configuration.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from ort.models.config.package_manager_configuration import PackageManagerConfiguration + +_package_managers: list[str] = [ + "Bazel", + "Bower", + "Bundler", + "Cargo", + "Carthage", + "CocoaPods", + "Composer", + "Conan", + "GoMod", + "GradleInspector", + "Maven", + "NPM", + "NuGet", + "PIP", + "Pipenv", + "PNPM", + "Poetry", + "Pub", + "SBT", + "SpdxDocumentFile", + "Stack", + "SwiftPM", + "Tycho", + "Unmanaged", + "Yarn", + "Yarn2", +] + + +class AnalyzerConfiguration(BaseModel): + """ + Enable the analysis of projects that use version ranges to declare their dependencies. If set to true, + dependencies of exactly the same project might change with another scan done at a later time if any of the + (transitive) dependencies are declared using version ranges and a new version of such a dependency was + published in the meantime. If set to false, analysis of projects that use version ranges will fail. Defaults to + false. + """ + + model_config = ConfigDict( + extra="forbid", + ) + allow_dynamic_versions: bool = Field( + default=False, + description="Enable the analysis of projects that use version ranges to declare their dependencies." + "If set to true, dependencies of exactly the same project might change with another scan done at a later time" + "if any of the (transitive) dependencies are declared using version ranges and a new version of such a" + "dependency was published in the meantime. If set to false, analysis of projects that use version ranges will" + "fail. Defaults to false.", + ) + enabled_package_managers: list[str] = Field( + default=_package_managers, + description="A list of the case-insensitive names of package managers that are enabled." + "Disabling a package manager in [disabledPackageManagers] overrides enabling it here.", + ) + disabled_package_managers: list[str] | None = Field( + default=None, + description="A list of the case-insensitive names of package managers that are disabled." + "Disabling a package manager in this list overrides [enabledPackageManagers].", + ) + package_managers: dict[str, PackageManagerConfiguration] | None = Field( + default=None, + description="Get a [PackageManagerConfiguration] from [packageManagers]. The difference to accessing the map" + "directly is that [packageManager] can be case-insensitive.", + ) + skip_excluded: bool = Field( + default=False, + description="A flag to control whether excluded scopes and paths should be skipped during the analysis.", + ) diff --git a/src/ort/models/config/license_finding_curation.py b/src/ort/models/config/license_finding_curation.py index 3e35263..faad3fe 100644 --- a/src/ort/models/config/license_finding_curation.py +++ b/src/ort/models/config/license_finding_curation.py @@ -2,7 +2,9 @@ # SPDX-License-Identifier: MIT -from pydantic import BaseModel, ConfigDict, Field +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field, field_validator from ort.models.config.license_finding_curation_reason import LicenseFindingCurationReason @@ -59,3 +61,17 @@ class LicenseFindingCuration(BaseModel): default=None, description="A comment explaining this [LicenseFindingCuration].", ) + + @field_validator("start_lines", mode="before") + @classmethod + def parse_start_lines(cls, value: Any) -> list[int] | None: + if value is None or value == "": + return None + if isinstance(value, str): + # CSV style split + return [int(x.strip()) for x in value.split(",") if x.strip()] + if isinstance(value, list): + return [int(x) for x in value] + if isinstance(value, int): + return [value] + raise ValueError("start_lines must be a comma-separated string or a list of integers") diff --git a/src/ort/models/config/package_configuration.py b/src/ort/models/config/package_configuration.py new file mode 100644 index 0000000..20f92ee --- /dev/null +++ b/src/ort/models/config/package_configuration.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from ort.models.config.license_finding_curation import LicenseFindingCuration +from ort.models.config.path_exclude import PathExclude +from ort.models.config.vcsmatcher import VcsMatcher +from ort.models.identifier import Identifier +from ort.models.source_code_origin import SourceCodeOrigin + + +class PackageConfiguration(BaseModel): + """ + A class used in the [OrtConfiguration] to configure [PathExclude]s and [LicenseFindingCuration]s for a specific + [Package]'s [Identifier] (and [Provenance]). + Note that [PathExclude]s and [LicenseFindingCuration]s for [Project]s are configured by a + [RepositoryConfiguration]'s excludes and curations properties instead. + + Attributes: + id (Identifier): The [Identifier] which must match with the identifier of the package in + order for this package curation to apply. The [version][Identifier.version] can be + either a plain version string matched for equality, or an Ivy-style version matchers. + * The other components of the [identifier][id] are matched by equality. + source_artifact_url (str | None): The source artifact this configuration applies to. + vcs (VcsMatcher | None): The vcs and revision this configuration applies to. + source_code_origin (SourceCodeOrigin | None): The source code origin this configuration + applies to. + path_excludes (list[PathExclude]): Path excludes. + license_finding_curations (list[LicenseFindingCuration]): License finding curations. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + id: Identifier = Field( + description="The [Identifier] which must match with the identifier of the package in order for this package" + "curation to apply. The [version][Identifier.version] can be either a plain version string matched for" + "equality, or an Ivy-style version matchers." + "* The other components of the [identifier][id] are matched by equality.", + ) + + source_artifact_url: str | None = Field( + default=None, + description="The source artifact this configuration applies to.", + ) + + vcs: VcsMatcher | None = Field( + default=None, + description="The vcs and revision this configuration applies to.", + ) + + source_code_origin: SourceCodeOrigin | None = Field( + default=None, + description="The source code origin this configuration applies to.", + ) + + path_excludes: list[PathExclude] = Field( + default_factory=list, + description="Path excludes.", + ) + + license_finding_curations: list[LicenseFindingCuration] = Field( + default_factory=list, + description="License finding curations.", + ) diff --git a/src/ort/models/config/package_manager_configuration.py b/src/ort/models/config/package_manager_configuration.py new file mode 100644 index 0000000..c41c7ee --- /dev/null +++ b/src/ort/models/config/package_manager_configuration.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + + +class PackageManagerConfiguration(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + + must_run_after: list[str] | None = Field( + default=None, + description="The configuration model for a package manager. This class is (de-)serialized in the following" + "places:" + "- Deserialized from config.yml as part of [OrtConfiguration] (via Hoplite)." + "- Deserialized from .ort.yml as part of [RepositoryAnalyzerConfiguration] (via Jackson)" + "- (De-)Serialized as part of [org.ossreviewtoolkit.model.OrtResult] (via Jackson).", + ) + + options: dict[str, str] | None = Field( + default=None, + description="Custom configuration options for the package manager. See the documentation of the respective" + "class for available options.", + ) diff --git a/src/ort/models/config/path_exclude.py b/src/ort/models/config/path_exclude.py new file mode 100644 index 0000000..ce044e3 --- /dev/null +++ b/src/ort/models/config/path_exclude.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from ort.models.config.path_exclude_reason import PathExcludeReason + + +class PathExclude(BaseModel): + """ + Defines paths which should be excluded. Each file or directory that is matched by the [glob][pattern] is marked as + excluded. If a project definition file is matched by the [pattern], the whole project is excluded. For details about + the glob syntax see the [FileMatcher] implementation. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + pattern: str = Field( + description="A glob to match the path of the project definition file, relative to the root of the repository." + ) + + reason: PathExcludeReason = Field( + description="The reason why the project is excluded, out of a predefined choice.", + ) + + comment: str = Field( + default_factory=str, + description="A comment to further explain why the [reason] is applicable here.", + ) diff --git a/src/ort/models/config/path_exclude_reason.py b/src/ort/models/config/path_exclude_reason.py new file mode 100644 index 0000000..b3ae8fb --- /dev/null +++ b/src/ort/models/config/path_exclude_reason.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from enum import Enum, auto + + +class PathExcludeReason(Enum): + """ + Possible reasons for excluding a path. + Attributes + BUILD_TOOL_OF + The path only contains tools used for building source code which are not included in + distributed build artifacts. + + DATA_FILE_OF + The path only contains data files such as fonts or images which are not included in + distributed build artifacts. + + DOCUMENTATION_OF + The path only contains documentation which is not included in distributed build artifacts. + + EXAMPLE_OF + The path only contains source code examples which are not included in distributed build + artifacts. + + OPTIONAL_COMPONENT_OF + The path only contains optional components for the code that is built which are not included + in distributed build artifacts. + + OTHER + Any other reason which cannot be represented by any other element of PathExcludeReason. + + PROVIDED_BY + The path only contains packages or sources for packages that have to be provided by the user + of distributed build artifacts. + + TEST_OF + The path only contains files used for testing source code which are not included in + distributed build artifacts. + + TEST_TOOL_OF + The path only contains tools used for testing source code which are not included in + distributed build artifacts. + """ + + # The path only contains tools used for building source code which are not included in distributed build artifacts. + BUILD_TOOL_OF = auto() + + # The path only contains data files such as fonts or images which are not included in distributed build artifacts. + DATA_FILE_OF = auto() + + # The path only contains documentation which is not included in distributed build artifacts. + DOCUMENTATION_OF = auto() + + # The path only contains source code examples which are not included in distributed build artifacts. + EXAMPLE_OF = auto() + + # The path only contains optional components for the code that is built which are not included + # in distributed build artifacts. + OPTIONAL_COMPONENT_OF = auto() + + # Any other reason which cannot be represented by any other element of PathExcludeReason. + OTHER = auto() + + # The path only contains packages or sources for packages that have to be provided by the user + # of distributed build artifacts. + PROVIDED_BY = auto() + + # The path only contains files used for testing source code which are not included in distributed build artifacts. + TEST_OF = auto() + + # The path only contains tools used for testing source code which are not included in distributed build artifacts. + TEST_TOOL_OF = auto() diff --git a/src/ort/models/config/path_include_reason.py b/src/ort/models/config/path_include_reason.py new file mode 100644 index 0000000..8229ded --- /dev/null +++ b/src/ort/models/config/path_include_reason.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT diff --git a/src/ort/models/config/repository_analyzer_configuration.py b/src/ort/models/config/repository_analyzer_configuration.py new file mode 100644 index 0000000..6a888c7 --- /dev/null +++ b/src/ort/models/config/repository_analyzer_configuration.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from ort.models.config.package_manager_configuration import PackageManagerConfiguration + + +class RepositoryAnalyzerConfiguration(BaseModel): + """ + Enable the analysis of projects that use version ranges to declare their dependencies. If set to true, + dependencies of exactly the same project might change with another scan done at a later time if any of the + (transitive) dependencies are declared using version ranges and a new version of such a dependency was + published in the meantime. If set to false, analysis of projects that use version ranges will fail. Defaults to + false. + """ + + model_config = ConfigDict( + extra="forbid", + ) + allow_dynamic_versions: bool | None = Field( + default=None, + description="Enable the analysis of projects that use version ranges to declare their dependencies." + "If set to true, dependencies of exactly the same project might change with another scan done at a later time" + "if any of the (transitive) dependencies are declared using version ranges and a new version of such a" + "dependency was published in the meantime. If set to false, analysis of projects that use version ranges will" + "fail. Defaults to false.", + ) + enabled_package_managers: list[str] | None = Field( + default=None, + description="A list of the case-insensitive names of package managers that are enabled." + "Disabling a package manager in [disabledPackageManagers] overrides enabling it here.", + ) + disabled_package_managers: list[str] | None = Field( + default=None, + description="A list of the case-insensitive names of package managers that are disabled." + "Disabling a package manager in this list overrides [enabledPackageManagers].", + ) + package_managers: dict[str, PackageManagerConfiguration] | None = Field( + default=None, + description="Get a [PackageManagerConfiguration] from [packageManagers]. The difference to accessing the map" + "directly is that [packageManager] can be case-insensitive.", + ) + skip_excluded: bool | None = Field( + default=None, + description="A flag to control whether excluded scopes and paths should be skipped during the analysis.", + ) diff --git a/src/ort/models/config/vcsmatcher.py b/src/ort/models/config/vcsmatcher.py new file mode 100644 index 0000000..a1d9d72 --- /dev/null +++ b/src/ort/models/config/vcsmatcher.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field + +from ort.models.vcstype import VcsType + + +class VcsMatcher(BaseModel): + """ + A matcher which matches its properties against a [RepositoryProvenance]. + + Attributes: + orttype (VcsType): The [type] to match for equality against [VcsInfo.type]. + url (AnyUrl): The [url] to match for equality against [VcsInfo.url]. + revision (str | None): The revision to match for equality against [RepositoryProvenance.resolvedRevision], + or null to match any revision. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + orttype: VcsType = Field( + alias="type", + description="The [type] to match for equality against [VcsInfo.type].", + ) + + url: AnyUrl = Field( + description="The [url] to match for equality against [VcsInfo.url].", + ) + + revision: str | None = Field( + default=None, + description="The revision to match for equality against [RepositoryProvenance.resolvedRevision]," + "or null to match anyrevision.", + ) diff --git a/src/ort/models/identifier.py b/src/ort/models/identifier.py new file mode 100644 index 0000000..927934c --- /dev/null +++ b/src/ort/models/identifier.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field, model_validator + + +class Identifier(BaseModel): + """ + A unique identifier for a software component. + + Attributes: + orttype (str): The type of component this identifier describes. When used in the context of a [Project], + the type equals the one of the package manager that manages the project (e.g. 'Gradle' + for a Gradle project). When used in the context of a [Package], the type is the name + of the artifact ecosystem (e.g. 'Maven' for a file from a Maven repository). + namespace (str): The namespace of the component, for example the group for 'Maven' or the scope for 'NPM'. + name (str): The name of the component. + version (str): The version of the component. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + orttype: str = Field( + alias="type", + description="The type of component this identifier describes. When used in the context of a [Project]," + "the type equals the one of the package manager that manages the project (e.g. 'Gradle' " + "for a Gradle project). When used in the context of a [Package], the type is the name" + "of the artifact ecosystem (e.g. 'Maven' for a file from a Maven repository).", + ) + + namespace: str = Field( + description="The namespace of the component, for examplethe group for 'Maven' or the scope for 'NPM'.", + ) + + name: str = Field( + description="The name of the component.", + ) + + version: str = Field( + description="The version of the component.", + ) + + @model_validator(mode="before") + @classmethod + def parse_string_or_dict(cls, value: Any): + if isinstance(value, dict): + return value + if isinstance(value, str): + parts = value.split(":") + if len(parts) != 4: + raise ValueError("Identifier string must be in the format 'type:namespace:name:version'") + return { + "type": parts[0], + "namespace": parts[1], + "name": parts[2], + "version": parts[3], + } + raise TypeError("Identifier must be a dict or a string in the correct format") diff --git a/src/ort/models/ort_configuration.py b/src/ort/models/ort_configuration.py index c8044a4..924208a 100644 --- a/src/ort/models/ort_configuration.py +++ b/src/ort/models/ort_configuration.py @@ -11,8 +11,6 @@ import yaml.parser from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel -from .package_managers import OrtPackageManagerConfigurations, OrtPackageManagers - class AdvisorConfig(RootModel[dict[str, dict[str, Any]] | None]): root: dict[str, dict[str, Any]] | None = None @@ -200,9 +198,10 @@ class AnalyzerConfigurationSchema(BaseModel): extra="forbid", ) allow_dynamic_versions: Annotated[bool | None, Field(alias="allowDynamicVersions")] = None - enabled_package_managers: Annotated[list[OrtPackageManagers] | None, Field(alias="enabledPackageManagers")] = None - disabled_package_managers: Annotated[list[OrtPackageManagers] | None, Field(alias="disabledPackageManagers")] = None - package_managers: Annotated[OrtPackageManagerConfigurations | None, Field(alias="packageManagers")] = None + # enabled_package_managers: Annotated[list[PackageManager] | None, Field(alias="enabledPackageManagers")] = None + # # disabled_package_managers: Annotated[list[OrtPackageManagers] | None, + # Field(alias="disabledPackageManagers")] = None + # package_managers: Annotated[OrtPackageManagerConfigurations | None, Field(alias="packageManagers")] = None sw360_configuration: Annotated[Sw360Configuration | None, Field(alias="sw360Configuration")] = None skip_excluded: Annotated[bool | None, Field(alias="skipExcluded")] = None diff --git a/src/ort/models/package_curation_data.py b/src/ort/models/package_curation_data.py index c9cdc33..1ae1a1d 100644 --- a/src/ort/models/package_curation_data.py +++ b/src/ort/models/package_curation_data.py @@ -16,6 +16,27 @@ class CurationArtifact(BaseModel): class PackageCurationData(BaseModel): + """ + Data model for package curation data. + + Attributes: + comment (str | None): Optional comment about the curation. + purl (str | None): The package URL (PURL) identifying the package. + cpe (str | None): The Common Platform Enumeration (CPE) identifier. + authors (list[str] | None): List of authors of the package. + concluded_license (str | None): The license concluded for the package. + description (str | None): Description of the package. + homepage_url (str | None): URL of the package's homepage. + binary_artifact (CurationArtifact | None): Information about the binary artifact. + source_artifact (CurationArtifact | None): Information about the source artifact. + vcs (VcsInfoCurationData | None): Version control system information. + is_metadata_only (bool | None): Whether the curation is metadata only. + is_modified (bool | None): Whether the package has been modified. + declared_license_mapping (dict[str, Any]): Mapping of declared licenses. + source_code_origins (list[SourceCodeOrigin] | None): List of source code origins. + labels (dict[str, str]): Additional labels for the package. + """ + model_config = ConfigDict( extra="forbid", ) diff --git a/src/ort/models/package_managers.py b/src/ort/models/package_managers.py deleted file mode 100644 index 8b22ba6..0000000 --- a/src/ort/models/package_managers.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro -# SPDX-License-Identifier: MIT - - -from enum import Enum -from typing import Any - -from pydantic import BaseModel, ConfigDict, Field, RootModel - - -class OrtPackageManagers(Enum): - """ - Enumeration of supported package managers in ORT. - - This enum represents a variety of package managers across different programming ecosystems. - """ - - bazel = "Bazel" - bower = "Bower" - bundler = "Bundler" - cargo = "Cargo" - carthage = "Carthage" - cocoa_pods = "CocoaPods" - composer = "Composer" - conan = "Conan" - go_mod = "GoMod" - gradle = "Gradle" - gradle_inspector = "GradleInspector" - maven = "Maven" - npm = "NPM" - nu_get = "NuGet" - pip = "PIP" - pipenv = "Pipenv" - pnpm = "PNPM" - poetry = "Poetry" - pub = "Pub" - sbt = "SBT" - spdx_document_file = "SpdxDocumentFile" - stack = "Stack" - swift_pm = "SwiftPM" - unmanaged = "Unmanaged" - yarn = "Yarn" - yarn2 = "Yarn2" - - -class PackageManagerConfigs(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - must_run_after: list[OrtPackageManagers] | None = Field(None, alias="mustRunAfter") - options: Any | None = None - - -class OrtPackageManagerConfigurations(RootModel[dict[str, PackageManagerConfigs]]): - root: dict[str, PackageManagerConfigs] diff --git a/src/ort/models/repository_configuration.py b/src/ort/models/repository_configuration.py index 45528f6..2c517bb 100644 --- a/src/ort/models/repository_configuration.py +++ b/src/ort/models/repository_configuration.py @@ -7,9 +7,9 @@ from pydantic import BaseModel, Field, RootModel -from ort.models.analyzer_configurations import OrtAnalyzerConfigurations from ort.models.config.curations import Curations -from ort.models.package_managers import OrtPackageManagerConfigurations, PackageManagerConfigs +from ort.models.config.package_configuration import PackageConfiguration +from ort.models.config.repository_analyzer_configuration import RepositoryAnalyzerConfiguration class OrtRepositoryConfigurationLicenseChoicesPackageLicenseChoiceLicenseChoice(BaseModel): @@ -170,10 +170,6 @@ class OrtRepositoryConfigurationSnippetChoice(BaseModel): choices: list[OrtRepositoryConfigurationSnippetChoiceChoice] -class PackageManagerConfigurationSchema(RootModel[dict[str, PackageManagerConfigs]]): - root: dict[str, PackageManagerConfigs] - - class ResolutionsSchemaResolutionsSchemaIssue(BaseModel): message: str reason: IssueResolutionReason @@ -277,7 +273,7 @@ class OrtRepositoryConfiguration(BaseModel): Each field corresponds to a specific aspect of the repository's configuration. """ - analyzer: OrtAnalyzerConfigurations | None = Field( + analyzer: RepositoryAnalyzerConfiguration | None = Field( None, description="Define Analyzer specific options", ) @@ -295,8 +291,8 @@ class OrtRepositoryConfiguration(BaseModel): description="Defines curations for packages used as dependencies by projects in this repository," " or curations for license findings in the source code of a project in this repository.", ) - package_configurations: list[OrtPackageManagerConfigurations] | None = Field( - None, + package_configurations: list[PackageConfiguration] = Field( + default_factory=list, description="A configuration for a specific package and provenance.", ) license_choices: OrtRepositoryConfigurationLicenseChoices | None = Field( diff --git a/tests/data/example_simple_package_config.yml b/tests/data/example_simple_package_config.yml new file mode 100644 index 0000000..581674b --- /dev/null +++ b/tests/data/example_simple_package_config.yml @@ -0,0 +1,11 @@ +package_configurations: + - id: 'Maven:com.example:package:1.2.3' + source_artifact_url: 'https://repo.maven.apache.org/maven2/com/example/package/1.2.3/package-1.2.3-sources.jar' + license_finding_curations: + - path: 'path/to/problematic/file.java' + start_lines: 22 + line_count: 1 + detected_license: 'GPL-2.0-only' + reason: 'CODE' + comment: 'The scanner matches a variable named `gpl`.' + concluded_license: 'Apache-2.0' diff --git a/tests/test_ort_configuration.py b/tests/test_ort_configuration.py deleted file mode 100644 index 57d5434..0000000 --- a/tests/test_ort_configuration.py +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro -# SPDX-License-Identifier: MIT - -from pathlib import Path - -from ort import OrtConfiguration, Scanner, Severity - -CONFIG_PATH = Path(__file__).parent / "data" / "ort_config_reference.yml" - - -def test_ort_reference_config() -> None: - try: - data: OrtConfiguration = OrtConfiguration(CONFIG_PATH) - except ValueError as e: - raise ValueError(e) - - if not hasattr(data, "ort"): - raise ValueError("No ort attribute in OrtConfiguration object.") - - if not isinstance(data.ort.severe_issue_threshold, Severity): - raise ValueError("Incorrect severe_issue_threshold object.") - - if data.ort.severe_issue_threshold != Severity.ERROR: - raise ValueError(f"Unexpected severity value in severe_issue_threshold {data.ort.severe_issue_threshold}") - - if not hasattr(data.ort, "scanner") and not isinstance(data.ort.scanner, Scanner): - raise ValueError("scanner object is not valid.") diff --git a/tests/test_package_configuration.py b/tests/test_package_configuration.py new file mode 100644 index 0000000..b096f4e --- /dev/null +++ b/tests/test_package_configuration.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +import pytest +from pydantic import ValidationError + +from ort.models.config.package_configuration import PackageConfiguration +from tests.utils.load_yaml_config import load_yaml_config # type: ignore + + +def test_ort_docs_simple_package_configuration(): + config_data = load_yaml_config("example_simple_package_config.yml") + + try: + for data in config_data.get("package_configurations"): + PackageConfiguration(**data) + except ValidationError as e: + pytest.fail(f"Failed to instantiate PackageConfiguration: {e}") diff --git a/tests/test_package_curation.py b/tests/test_package_curation.py index db94f5e..ebeaa8b 100644 --- a/tests/test_package_curation.py +++ b/tests/test_package_curation.py @@ -19,7 +19,7 @@ def test_ort_docs_simple_curation_example(): try: Curations(packages=config_data) except ValidationError as e: - pytest.fail(f"Failed to instantiate OrtRepositoryConfiguration: {e}") + pytest.fail(f"Failed to instantiate Curations: {e}") def test_ort_docs_curation_example(): @@ -32,4 +32,4 @@ def test_ort_docs_curation_example(): try: Curations(packages=config_data) except ValidationError as e: - pytest.fail(f"Failed to instantiate OrtRepositoryConfiguration: {e}") + pytest.fail(f"Failed to instantiate Curations: {e}") diff --git a/tests/utils/test_package_configuration.py b/tests/utils/test_package_configuration.py new file mode 100644 index 0000000..e69de29 diff --git a/uv.lock b/uv.lock index 3d5219d..6f6fe87 100644 --- a/uv.lock +++ b/uv.lock @@ -644,7 +644,7 @@ wheels = [ [[package]] name = "python-ort" -version = "0.3.1" +version = "0.4.0" source = { editable = "." } dependencies = [ { name = "pydantic" },