diff --git a/src/ort/__init__.py b/src/ort/__init__.py index e5c2f0b..f78d4b8 100644 --- a/src/ort/__init__.py +++ b/src/ort/__init__.py @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: MIT -from ort.models.analyzer_result import AnalyzerResult -from ort.models.ort_result import OrtResult -from ort.models.repository_configuration import RepositoryConfiguration +from .models.analyzer_result import AnalyzerResult +from .models.config.repository_configuration import RepositoryConfiguration +from .models.ort_result import OrtResult __all__ = [ "AnalyzerResult", diff --git a/src/ort/models/__init__.py b/src/ort/models/__init__.py index f4d4356..85f2eae 100644 --- a/src/ort/models/__init__.py +++ b/src/ort/models/__init__.py @@ -6,6 +6,13 @@ from .advisor_run import AdvisorRun from .analyzer_result import AnalyzerResult from .analyzer_run import AnalyzerRun +from .config.excludes import Excludes +from .config.includes import Includes +from .config.path_exclude import PathExclude +from .config.path_exclude_reason import PathExcludeReason +from .config.path_include import PathInclude +from .config.path_include_reason import PathIncludeReason +from .config.repository_configuration import RepositoryConfiguration from .dependency_graph import DependencyGraph from .dependency_graph_edge import DependencyGraphEdge from .dependency_graph_node import DependencyGraphNode @@ -23,7 +30,6 @@ from .project import Project from .remote_artifact import RemoteArtifact from .repository import Repository -from .repository_configuration import RepositoryConfiguration from .root_dependency_index import RootDependencyIndex from .scope import Scope from .source_code_origin import SourceCodeOrigin @@ -44,6 +50,8 @@ "Hash", "HashAlgorithm", "Identifier", + "Includes", + "Excludes", "Issue", "OrtResult", "Package", @@ -51,6 +59,10 @@ "PackageCurationData", "PackageLinkage", "PackageReference", + "PathExcludeReason", + "PathIncludeReason", + "PathExclude", + "PathInclude", "Project", "RemoteArtifact", "Repository", diff --git a/src/ort/models/config/excludes.py b/src/ort/models/config/excludes.py new file mode 100644 index 0000000..ccea15f --- /dev/null +++ b/src/ort/models/config/excludes.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from .path_exclude import PathExclude +from .scope_exclude import ScopeExclude + + +class Excludes(BaseModel): + """ + Defines which parts of a repository should be excluded. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + paths: list[PathExclude] = Field( + default_factory=list, + description="Path excludes.", + ) + + scopes: list[ScopeExclude] = Field( + default_factory=list, + description="Scopes that will be excluded from all projects.", + ) diff --git a/src/ort/models/config/includes.py b/src/ort/models/config/includes.py new file mode 100644 index 0000000..c2da018 --- /dev/null +++ b/src/ort/models/config/includes.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from .path_include import PathInclude + + +class Includes(BaseModel): + """ + Defines which parts of a repository should be excluded. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + paths: list[PathInclude] = Field( + default_factory=list, + description="Path includes.", + ) diff --git a/src/ort/models/config/issue_resolution.py b/src/ort/models/config/issue_resolution.py new file mode 100644 index 0000000..1e93e2b --- /dev/null +++ b/src/ort/models/config/issue_resolution.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from .issue_resolution_reason import IssueResolutionReason + + +class IssueResolution(BaseModel): + """ + Defines the resolution of an [Issue]. This can be used to silence false positives, or issues that have been + identified as not being relevant. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + message: str = Field( + description="A regular expression string to match the messages of issues to resolve. Whitespace in the message" + "will be [collapsed][collapseWhitespace] and it will be converted to a [Regex] using" + "[RegexOption.DOT_MATCHES_ALL].", + ) + + reason: IssueResolutionReason = Field( + description="The reason why the issue is resolved.", + ) + + comment: str = Field( + description="A comment to further explain why the [reason] is applicable here.", + ) diff --git a/src/ort/models/config/issue_resolution_reason.py b/src/ort/models/config/issue_resolution_reason.py new file mode 100644 index 0000000..e943724 --- /dev/null +++ b/src/ort/models/config/issue_resolution_reason.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from enum import IntEnum + + +class IssueResolutionReason(IntEnum): + """ + Possible reasons for resolving an Issue using an IssueResolution. + + properties: + BUILD_TOOL_ISSUE: + The issue originates from the build tool used by the project. + CANT_FIX_ISSUE: + The issue can not be fixed. + For example, it requires a change to be made by a third party that is not responsive. + SCANNER_ISSUE: + The issue is due to an irrelevant scanner issue. + For example, a time out on a large file that is not distributed. + """ + + BUILD_TOOL_ISSUE = 1 + CANT_FIX_ISSUE = 2 + SCANNER_ISSUE = 3 diff --git a/src/ort/models/config/license_choice.py b/src/ort/models/config/license_choice.py new file mode 100644 index 0000000..262d963 --- /dev/null +++ b/src/ort/models/config/license_choice.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT +from pydantic import BaseModel, ConfigDict, Field + +from ...utils.spdx.spdx_license_choice import SpdxLicenseChoice +from ..identifier import Identifier + + +class PackageLicenseChoice(BaseModel): + """ + SpdxLicenseChoice]s defined for an artifact. + """ + + model_config = ConfigDict( + extra="forbid", + ) + package_id: Identifier = Field( + ..., + description="Package ID", + ) + license_choice: list[SpdxLicenseChoice] = Field( + default_factory=list, + description="List of spdx license", + ) + + +class LicenseChoice(BaseModel): + """ + [SpdxLicenseChoice]s that are applied to all packages in the repository. As the [SpdxLicenseChoice] is applied to + each package that offers this license as a choice, [SpdxLicenseChoice.given] can not be null. This helps only + applying the choice to a wanted [SpdxLicenseChoice.given] as opposed to all licenses with that choice, which + could lead to unwanted applied choices. + """ + + model_config = ConfigDict( + extra="forbid", + ) + repository_license_choices: list[SpdxLicenseChoice] = Field( + default_factory=list, + description="SPDX", + ) + package_license_choice: list[PackageLicenseChoice] = Field( + default_factory=list, + description="Package", + ) diff --git a/src/ort/models/config/path_exclude.py b/src/ort/models/config/path_exclude.py index ce044e3..a313e44 100644 --- a/src/ort/models/config/path_exclude.py +++ b/src/ort/models/config/path_exclude.py @@ -1,10 +1,12 @@ -# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro # SPDX-License-Identifier: MIT -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, field_validator -from ort.models.config.path_exclude_reason import PathExcludeReason +from ort.utils import convert_enum + +from .path_exclude_reason import PathExcludeReason class PathExclude(BaseModel): @@ -30,3 +32,8 @@ class PathExclude(BaseModel): default_factory=str, description="A comment to further explain why the [reason] is applicable here.", ) + + @field_validator("reason", mode="before") + @classmethod + def validate_reason(cls, value): + return convert_enum(PathExcludeReason, value) diff --git a/src/ort/models/config/path_exclude_reason.py b/src/ort/models/config/path_exclude_reason.py index b3ae8fb..4750f29 100644 --- a/src/ort/models/config/path_exclude_reason.py +++ b/src/ort/models/config/path_exclude_reason.py @@ -1,73 +1,47 @@ # SPDX-FileCopyrightText: 2025 Helio Chissini de Castro # SPDX-License-Identifier: MIT -from enum import Enum, auto +from enum import IntEnum -class PathExcludeReason(Enum): +class PathExcludeReason(IntEnum): """ Possible reasons for excluding a path. - Attributes + + 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() + BUILD_TOOL_OF = 1 + DATA_FILE_OF = 2 + DOCUMENTATION_OF = 3 + EXAMPLE_OF = 4 + OPTIONAL_COMPONENT_OF = 5 + OTHER = 6 + PROVIDED_BY = 7 + TEST_OF = 8 + TEST_TOOL_OF = 9 diff --git a/src/ort/models/config/path_include.py b/src/ort/models/config/path_include.py new file mode 100644 index 0000000..7955996 --- /dev/null +++ b/src/ort/models/config/path_include.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field, field_validator + +from ort.utils import convert_enum + +from .path_include_reason import PathIncludeReason + + +class PathInclude(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: PathIncludeReason = Field( + description="The reason why the project is included, out of a predefined choice.", + ) + + comment: str = Field( + default_factory=str, + description="A comment to further explain why the [reason] is applicable here.", + ) + + @field_validator("reason", mode="before") + @classmethod + def validate_reason(cls, value): + return convert_enum(PathIncludeReason, value) diff --git a/src/ort/models/config/path_include_reason.py b/src/ort/models/config/path_include_reason.py index 8229ded..972c1ba 100644 --- a/src/ort/models/config/path_include_reason.py +++ b/src/ort/models/config/path_include_reason.py @@ -1,2 +1,19 @@ -# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro # SPDX-License-Identifier: MIT + +from enum import IntEnum + + +class PathIncludeReason(IntEnum): + """ + Possible reasons for including a path. + + Attributes: + SOURCE_OF + The path contains source code used to build distributed build artifacts. + OTHER + A fallback reason for the [PathIncludeReason] when none of the other reasons apply. + """ + + SOURCE_OF = 1 + OTHER = 2 diff --git a/src/ort/models/config/repository_configuration.py b/src/ort/models/config/repository_configuration.py new file mode 100644 index 0000000..092e0ec --- /dev/null +++ b/src/ort/models/config/repository_configuration.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from .curations import Curations +from .excludes import Excludes +from .includes import Includes +from .license_choice import LicenseChoice +from .package_configuration import PackageConfiguration +from .repository_analyzer_configuration import RepositoryAnalyzerConfiguration +from .resolutions import Resolutions +from .snippet.snippet_choice import SnippetChoice + + +class RepositoryConfiguration(BaseModel): + """ + Represents the configuration for an OSS-Review-Toolkit (ORT) repository. + + This class defines various configuration options for analyzing, including, excluding, + resolving, and curating artifacts in a repository. It also provides settings for package + configurations, license choices, and snippet choices. + + Usage: + Instantiate this class to specify repository-level configuration for ORT analysis. + Each field corresponds to a specific aspect of the repository's configuration. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + analyzer: RepositoryAnalyzerConfiguration | None = Field( + default=None, + description="Define Analyzer specific options", + ) + includes: Includes | None = Field( + default=None, + description="Defines which parts of a repository should be included.", + ) + excludes: Excludes | None = Field( + default=None, + description="Defines which parts of a repository should be excluded.", + ) + resolutions: Resolutions | None = Field( + default=None, + description="Defines resolutions for issues with this repository.", + ) + curations: Curations | None = Field( + default=None, + 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[PackageConfiguration] = Field( + default_factory=list, + description="A configuration for a specific package and provenance.", + ) + license_choices: LicenseChoice | None = Field( + None, + description="A configuration to select a license from a multi-licensed package.", + ) + snippet_choices: list[SnippetChoice] = Field( + default_factory=list, + description="A configuration to select a snippet from a package with multiple snippet findings.", + ) diff --git a/src/ort/models/config/resolutions.py b/src/ort/models/config/resolutions.py new file mode 100644 index 0000000..5b4e28a --- /dev/null +++ b/src/ort/models/config/resolutions.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from .issue_resolution import IssueResolution +from .rule_violation_resolution import RuleViolationResolution +from .vulnerability_resolution import VulnerabilityResolution + + +class Resolutions(BaseModel): + """ + Resolutions for issues with a repository. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + issues: list[IssueResolution] = Field( + default_factory=list, + description="Resolutions for issues with the analysis or scan of the projects" + "in this repository and their dependencies.", + ) + + rule_violations: list[RuleViolationResolution] = Field( + default_factory=list, + description="Resolutions for license policy violations.", + ) + + vulnerabilities: list[VulnerabilityResolution] = Field( + default_factory=list, + description="Resolutions for vulnerabilities provided by the advisor.", + ) diff --git a/src/ort/models/config/rule_violation_reason.py b/src/ort/models/config/rule_violation_reason.py new file mode 100644 index 0000000..6571aef --- /dev/null +++ b/src/ort/models/config/rule_violation_reason.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from enum import IntEnum + + +class RuleViolationResolutionReason(IntEnum): + """ + Properties: + CANT_FIX_EXCEPTION: + The rule violation cannot be fixed and is acceptable in this case. + DYNAMIC_LINKAGE_EXCEPTION: + The rule violation is acceptable given the fact that the dependency it relates to is + dynamically linked. + EXAMPLE_OF_EXCEPTION: + The rule violation is due to an inclusion of example code into a file and is acceptable + in this case. + LICENSE_ACQUIRED_EXCEPTION: + The rule violation is acceptable because the license for the respective package has been + acquired. + NOT_MODIFIED_EXCEPTION: + The rule violation is acceptable given the fact that the code it relates to has not been + modified. + PATENT_GRANT_EXCEPTION: + The implied patent grant is acceptable in this case. + """ + + CANT_FIX_EXCEPTION = 1 + DYNAMIC_LINKAGE_EXCEPTION = 2 + EXAMPLE_OF_EXCEPTION = 3 + LICENSE_ACQUIRED_EXCEPTION = 4 + NOT_MODIFIED_EXCEPTION = 5 + PATENT_GRANT_EXCEPTION = 6 diff --git a/src/ort/models/config/rule_violation_resolution.py b/src/ort/models/config/rule_violation_resolution.py new file mode 100644 index 0000000..e58634f --- /dev/null +++ b/src/ort/models/config/rule_violation_resolution.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field, field_validator + +from ort.utils import convert_enum + +from .rule_violation_reason import RuleViolationResolutionReason + + +class RuleViolationResolution(BaseModel): + """ + Defines the resolution of a [RuleViolation]. This can be used to silence rule violations that + have been identified as not being relevant or are acceptable / approved. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + message: str = Field( + description="A regular expression string to match the messages of rule violations to resolve." + "Whitespace in the message will be [collapsed][collapseWhitespace] and it will be converted to" + "a [Regex] using [RegexOption.DOT_MATCHES_ALL]." + ) + + reason: RuleViolationResolutionReason = Field( + description="The reason why the rule violation is resolved.", + ) + + comment: str = Field( + description="A comment to further explain why the [reason] is applicable here.", + ) + + @field_validator("reason", mode="before") + @classmethod + def validate_reason(cls, value): + return convert_enum(RuleViolationResolutionReason, value) diff --git a/src/ort/models/config/scope_exclude.py b/src/ort/models/config/scope_exclude.py new file mode 100644 index 0000000..792be3e --- /dev/null +++ b/src/ort/models/config/scope_exclude.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field, field_validator + +from ort.models.config.scope_exclude_reason import ScopeExcludeReason +from ort.utils import convert_enum + + +class ScopeExclude(BaseModel): + """ + Defines a scope that should be excluded. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + pattern: str = Field( + description="A regular expression to match the names of scopes to exclude.", + ) + + reason: ScopeExcludeReason = Field( + description="The reason for excluding the scope.", + ) + + comment: str = Field( + default_factory=str, + description="A comment to further explain why the [reason] is applicable here.", + ) + + @field_validator("reason", mode="before") + @classmethod + def validate_reason(cls, value): + return convert_enum(ScopeExcludeReason, value) diff --git a/src/ort/models/config/scope_exclude_reason.py b/src/ort/models/config/scope_exclude_reason.py new file mode 100644 index 0000000..7c1b683 --- /dev/null +++ b/src/ort/models/config/scope_exclude_reason.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from enum import IntEnum + + +class ScopeExcludeReason(IntEnum): + """ + Possible reasons for excluding a scope. + + Attributes: + BUILD_TOOL_OF: + The scope only contains packages used for building source code which are not included in + distributed build artifacts. + BUILD_DEPENDENCY_OF: + The scope only contains packages used for building source code which are not included in + distributed build. + DEV_DEPENDENCY_OF: + The scope only contains packages used for development which are not included in + distributed build. + DOCUMENTATION_DEPENDENCY_OF: + The scope only contains packages used for building the documentation. + PROVIDED_BY: + The scope only contains packages that have to be provided by the user of distributed + build artifacts. + PROVIDED_DEPENDENCY_OF: + The scope only contains packages that have to be provided by the user of distributed + build artifacts. + TEST_TOOL_OF: + The scope only contains packages used for testing source code which are not included in + distributed build artifacts. + TEST_DEPENDENCY_OF: + The scope only contains packages used for testing which are not included in distributed + build. + RUNTIME_DEPENDENCY_OF: + The scope only contains packages that have to be provided by the user during the + execution of the artifacts but are not included in distributed build artifacts. + """ + + BUILD_TOOL_OF = 1 + BUILD_DEPENDENCY_OF = 2 + DEV_DEPENDENCY_OF = 3 + DOCUMENTATION_DEPENDENCY_OF = 4 + PROVIDED_BY = 5 + PROVIDED_DEPENDENCY_OF = 6 + TEST_TOOL_OF = 7 + TEST_DEPENDENCY_OF = 8 + RUNTIME_DEPENDENCY_OF = 9 diff --git a/src/ort/models/config/snippet/Provenance.py b/src/ort/models/config/snippet/Provenance.py new file mode 100644 index 0000000..9e6e97c --- /dev/null +++ b/src/ort/models/config/snippet/Provenance.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT +from pydantic import AnyUrl, BaseModel, ConfigDict, Field + + +class Provenance(BaseModel): + """ + The URL of the [RepositoryProvenance] the snippet choice applies to. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + url: AnyUrl = Field( + ..., + description="The URL of the [RepositoryProvenance] the snippet choice applies to.", + ) diff --git a/src/ort/models/config/snippet/snippet_choice.py b/src/ort/models/config/snippet/snippet_choice.py new file mode 100644 index 0000000..eea095c --- /dev/null +++ b/src/ort/models/config/snippet/snippet_choice.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from pydantic import BaseModel, ConfigDict, Field + +from ...text_location import TextLocation +from .snippet_choice_reason import SnippetChoiceReason + + +class Given(BaseModel): + """ + A source file criteria for which the snippet choice is made. + """ + + model_config = ConfigDict( + extra="forbid", + ) + source_location: TextLocation = Field( + ..., + description="The source file for which the snippet choice is made.", + ) + + +class Choice(BaseModel): + """ + A snippet criteria to make the snippet choice. + """ + + model_config = ConfigDict( + extra="forbid", + ) + purl: str = Field( + ..., + description="The purl of the snippet chosen by this snippet choice." + "If [reason] is [SnippetChoiceReason.NO_RELEVANT_FINDING], it is null.", + ) + reason: SnippetChoiceReason = Field( + ..., + description="The reason why this snippet choice was made.", + ) + comment: str | None = Field( + None, + description="An optional comment describing the snippet choice.", + ) + + +class SnippetChoice(BaseModel): + """ + A snippet choice for a given source file. + """ + + model_config = ConfigDict( + extra="forbid", + ) + given: Given = Field(..., description="The source file criteria for which the snippet choice is made.") + choice: Choice = Field(..., description="The snippet criteria to make the snippet choice.") diff --git a/src/ort/models/config/snippet/snippet_choice_reason.py b/src/ort/models/config/snippet/snippet_choice_reason.py new file mode 100644 index 0000000..6e352ae --- /dev/null +++ b/src/ort/models/config/snippet/snippet_choice_reason.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from enum import IntEnum + + +class SnippetChoiceReason(IntEnum): + """ + The reason for which the snippet choice has been made. + + properties: + NO_RELEVANT_FINDING: + No relevant finding has been found for the corresponding source file. All snippets will be ignored. + ORIGINAL_FINDING: + One snippet finding is relevant for the corresponding source file. All other snippets will be ignored. + OTHER: + A fallback reason for the [SnippetChoiceReason] when none of the other reasons apply. + + """ + + NO_RELEVANT_FINDING = 1 + ORIGINAL_FINDING = 2 + OTHER = 3 diff --git a/src/ort/models/config/vulnerability_resolution.py b/src/ort/models/config/vulnerability_resolution.py new file mode 100644 index 0000000..a9a9942 --- /dev/null +++ b/src/ort/models/config/vulnerability_resolution.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +from pydantic import BaseModel, ConfigDict, Field + +from .vulnerability_resolution_reason import VulnerabilityResolutionReason + + +class VulnerabilityResolution(BaseModel): + """ + Defines the resolution of an [Vulnerability]. This can be used to silence false positives, or + vulnerabilities that have been identified as not being relevant. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + message: str = Field( + description="A regular expression to match the id, e.g. a CVE, of the vulnerability to resolve." + "Will be converted to a [Regex] using [RegexOption.DOT_MATCHES_ALL]." + ) + + reason: VulnerabilityResolutionReason = Field( + description="The reason why the vulnerability is resolved.", + ) + + comment: str = Field( + description="A comment to further explain why the [reason] is applicable here.", + ) diff --git a/src/ort/models/config/vulnerability_resolution_reason.py b/src/ort/models/config/vulnerability_resolution_reason.py new file mode 100644 index 0000000..3d13d40 --- /dev/null +++ b/src/ort/models/config/vulnerability_resolution_reason.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from enum import IntEnum + + +class VulnerabilityResolutionReason(IntEnum): + """ + Possible reasons for resolving a Vulnerability using a VulnerabilityResolution. + + Properties: + CANT_FIX_VULNERABILITY: + No remediation is available for this vulnerability. + For example, it requires a change to be made by a third party that is not responsive. + + INEFFECTIVE_VULNERABILITY: + The code in which the vulnerability was found is neither invoked in the project's code + nor indirectly via another open source component. + + INVALID_MATCH_VULNERABILITY: + The vulnerability is irrelevant due to a tooling or database mismatch. + For example, the package version used does not match the version for which the + vulnerability provider has reported a vulnerability. + + MITIGATED_VULNERABILITY: + The vulnerability is valid but has been mitigated. + For example, measures have been taken to ensure this vulnerability cannot be exploited. + + NOT_A_VULNERABILITY: + The vulnerability was reported and got a CVE assigned. + However, the CVE was later deemed to not be a vulnerability. + + WILL_NOT_FIX_VULNERABILITY: + This vulnerability will never be fixed. + For example, the package which is affected is orphaned, declared end-of-life, + or otherwise deprecated. + + WORKAROUND_FOR_VULNERABILITY: + The vulnerability is valid but a temporary workaround has been put in place + to avoid exposure to the vulnerability. + """ + + CANT_FIX_VULNERABILITY = 1 + INEFFECTIVE_VULNERABILITY = 2 + INVALID_MATCH_VULNERABILITY = 3 + MITIGATED_VULNERABILITY = 4 + NOT_A_VULNERABILITY = 5 + WILL_NOT_FIX_VULNERABILITY = 6 + WORKAROUND_FOR_VULNERABILITY = 7 diff --git a/src/ort/models/issue.py b/src/ort/models/issue.py index 83a392c..a949226 100644 --- a/src/ort/models/issue.py +++ b/src/ort/models/issue.py @@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator from ort.severity import Severity +from ort.utils import convert_enum class Issue(BaseModel): @@ -38,16 +39,4 @@ class Issue(BaseModel): @field_validator("severity", mode="before") @classmethod def convert_severity(cls, v): - def _convert(item): - if isinstance(item, str): - try: - return Severity[item] - except KeyError: - raise ValueError(f"Invalid severity: {item}") - return item - - if isinstance(v, (list, set)): - return {_convert(item) for item in v} - if isinstance(v, str): - return _convert(v) - return v + return convert_enum(Severity, v) diff --git a/src/ort/models/repository.py b/src/ort/models/repository.py index 3c97a3f..3bd2264 100644 --- a/src/ort/models/repository.py +++ b/src/ort/models/repository.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, ConfigDict, Field -from .repository_configuration import RepositoryConfiguration +from .config.repository_configuration import RepositoryConfiguration from .vcsinfo import VcsInfo diff --git a/src/ort/models/repository_configuration.py b/src/ort/models/repository_configuration.py deleted file mode 100644 index 5031bd9..0000000 --- a/src/ort/models/repository_configuration.py +++ /dev/null @@ -1,305 +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, Field, RootModel - -from ort.models.config.curations import Curations -from ort.models.config.package_configuration import PackageConfiguration -from ort.models.config.repository_analyzer_configuration import RepositoryAnalyzerConfiguration - - -class OrtRepositoryConfigurationLicenseChoicesPackageLicenseChoiceLicenseChoice(BaseModel): - given: str | None = None - choice: str - - -class OrtRepositoryConfigurationLicenseChoicesPackageLicenseChoice(BaseModel): - package_id: str - license_choices: list[OrtRepositoryConfigurationLicenseChoicesPackageLicenseChoiceLicenseChoice] - - -class OrtRepositoryConfigurationLicenseChoices(BaseModel): - package_license_choices: list[OrtRepositoryConfigurationLicenseChoicesPackageLicenseChoice] | None = None - repository_license_choices: list[Any] | None = None - - -class OrtRepositoryConfigurationSnippetChoiceProvenance(BaseModel): - url: str - - -class OrtRepositoryConfigurationSnippetChoiceChoiceGivenSourceLocation(BaseModel): - path: str - start_line: int - end_line: int - - -class OrtRepositoryConfigurationSnippetChoiceChoiceGiven(BaseModel): - source_location: OrtRepositoryConfigurationSnippetChoiceChoiceGivenSourceLocation | None = None - - -class IssueResolutionReason(Enum): - build_tool_issue = "BUILD_TOOL_ISSUE" - cant_fix_issue = "CANT_FIX_ISSUE" - scanner_issue = "SCANNER_ISSUE" - - -class RuleViolationResolutionReason(Enum): - cant_fix_exception = "CANT_FIX_EXCEPTION" - dynamic_linkage_exception = "DYNAMIC_LINKAGE_EXCEPTION" - example_of_exception = "EXAMPLE_OF_EXCEPTION" - license_acquired_exception = "LICENSE_ACQUIRED_EXCEPTION" - not_modified_exception = "NOT_MODIFIED_EXCEPTION" - patent_grant_exception = "PATENT_GRANT_EXCEPTION" - - -class VulnerabilityResolutionReason(Enum): - cant_fix_vulnerability = "CANT_FIX_VULNERABILITY" - ineffective_vulnerability = "INEFFECTIVE_VULNERABILITY" - invalid_match_vulnerability = "INVALID_MATCH_VULNERABILITY" - mitigated_vulnerability = "MITIGATED_VULNERABILITY" - not_a_vulnerability = "NOT_A_VULNERABILITY" - will_not_fix_vulnerability = "WILL_NOT_FIX_VULNERABILITY" - workaround_for_vulnerability = "WORKAROUND_FOR_VULNERABILITY" - - -class PackageConfigurationSchemaSourceCodeOrigin(Enum): - vcs = "VCS" - artifact = "ARTIFACT" - - -class PackageConfigurationSchemaLicenseFindingCurationReason(Enum): - code = "CODE" - data_of = "DATA_OF" - documentation_of = "DOCUMENTATION_OF" - incorrect = "INCORRECT" - not_detected = "NOT_DETECTED" - reference = "REFERENCE" - - -class LicenseFindingCurations(BaseModel): - comment: str | None = None - concluded_license: str - detected_license: str | None = None - line_count: int | None = None - path: str - reason: PackageConfigurationSchemaLicenseFindingCurationReason - start_lines: int | str | None = None - - -class PathExcludeReason(Enum): - build_tool_of = "BUILD_TOOL_OF" - data_file_of = "DATA_FILE_OF" - documentation_of = "DOCUMENTATION_OF" - example_of = "EXAMPLE_OF" - optional_component_of = "OPTIONAL_COMPONENT_OF" - other = "OTHER" - provided_by = "PROVIDED_BY" - test_of = "TEST_OF" - test_tool_of = "TEST_TOOL_OF" - - -class PathIncludeReason(Enum): - source_of = "SOURCE_OF" - - -class ScopeExcludeReason(Enum): - build_dependency_of = "BUILD_DEPENDENCY_OF" - dev_dependency_of = "DEV_DEPENDENCY_OF" - documentation_dependency_of = "DOCUMENTATION_DEPENDENCY_OF" - provided_dependency_of = "PROVIDED_DEPENDENCY_OF" - test_dependency_of = "TEST_DEPENDENCY_OF" - runtime_dependency_of = "RUNTIME_DEPENDENCY_OF" - - -class SnippetChoiceReason(Enum): - no_relevant_finding = "NO_RELEVANT_FINDING" - original_finding = "ORIGINAL_FINDING" - other = "OTHER" - - -class OrtRepositoryConfigurationIncludesPath(BaseModel): - pattern: str = Field( - ..., - description="A glob to match the path of the project definition file, relative to the root of the repository.", - ) - reason: PathIncludeReason - comment: str | None = None - - -class OrtRepositoryConfigurationIncludes(BaseModel): - paths: list[OrtRepositoryConfigurationIncludesPath] | None = None - - -class OrtRepositoryConfigurationExcludesPath(BaseModel): - pattern: str = Field( - ..., - description="A glob to match the path of the project definition file, relative to the root of the repository.", - ) - reason: PathExcludeReason - comment: str | None = None - - -class OrtRepositoryConfigurationExcludesScope(BaseModel): - pattern: str - reason: ScopeExcludeReason - comment: str | None = None - - -class OrtRepositoryConfigurationExcludes(BaseModel): - paths: list[OrtRepositoryConfigurationExcludesPath] | None = None - scopes: list[OrtRepositoryConfigurationExcludesScope] | None = None - - -class OrtRepositoryConfigurationSnippetChoiceChoiceChoice(BaseModel): - purl: str | None = None - reason: SnippetChoiceReason - comment: str | None = None - - -class OrtRepositoryConfigurationSnippetChoiceChoice(BaseModel): - given: OrtRepositoryConfigurationSnippetChoiceChoiceGiven - choice: OrtRepositoryConfigurationSnippetChoiceChoiceChoice - - -class OrtRepositoryConfigurationSnippetChoice(BaseModel): - provenance: OrtRepositoryConfigurationSnippetChoiceProvenance - choices: list[OrtRepositoryConfigurationSnippetChoiceChoice] - - -class ResolutionsSchemaResolutionsSchemaIssue(BaseModel): - message: str - reason: IssueResolutionReason - comment: str | None = None - - -class ResolutionsSchemaResolutionsSchemaRuleViolation(BaseModel): - message: str - reason: RuleViolationResolutionReason - comment: str | None = None - - -class ResolutionsSchemaResolutionsSchemaVulnerability(BaseModel): - id: str - reason: VulnerabilityResolutionReason - comment: str | None = None - - -class ResolutionsSchemaResolutionsSchema(BaseModel): - issues: list[ResolutionsSchemaResolutionsSchemaIssue] - rule_violations: list[ResolutionsSchemaResolutionsSchemaRuleViolation] | None = None - vulnerabilities: list[ResolutionsSchemaResolutionsSchemaVulnerability] | None = None - - -ResolutionsSchemaResolutionsSchema1Issue = ResolutionsSchemaResolutionsSchemaIssue - - -ResolutionsSchemaResolutionsSchema1RuleViolation = ResolutionsSchemaResolutionsSchemaRuleViolation - - -ResolutionsSchemaResolutionsSchema1Vulnerability = ResolutionsSchemaResolutionsSchemaVulnerability - - -class ResolutionsSchemaResolutionsSchema1(BaseModel): - issues: list[ResolutionsSchemaResolutionsSchema1Issue] | None = None - rule_violations: list[ResolutionsSchemaResolutionsSchema1RuleViolation] - vulnerabilities: list[ResolutionsSchemaResolutionsSchema1Vulnerability] | None = None - - -ResolutionsSchemaResolutionsSchema2Issue = ResolutionsSchemaResolutionsSchemaIssue - - -ResolutionsSchemaResolutionsSchema2RuleViolation = ResolutionsSchemaResolutionsSchemaRuleViolation - - -ResolutionsSchemaResolutionsSchema2Vulnerability = ResolutionsSchemaResolutionsSchemaVulnerability - - -class ResolutionsSchemaResolutionsSchema2(BaseModel): - issues: list[ResolutionsSchemaResolutionsSchema2Issue] | None = None - rule_violations: list[ResolutionsSchemaResolutionsSchema2RuleViolation] | None = None - vulnerabilities: list[ResolutionsSchemaResolutionsSchema2Vulnerability] - - -class ResolutionsSchema( - RootModel[ - ResolutionsSchemaResolutionsSchema | ResolutionsSchemaResolutionsSchema1 | ResolutionsSchemaResolutionsSchema2 - ] -): - root: ( - ResolutionsSchemaResolutionsSchema | ResolutionsSchemaResolutionsSchema1 | ResolutionsSchemaResolutionsSchema2 - ) = Field( - ..., - description="The OSS-Review-Toolkit (ORT) provides a possibility to resolve issues, rule violations and " - "security vulnerabilities in a resolutions file. A full list of all available options can be found at " - "https://oss-review-toolkit.org/ort/docs/configuration/resolutions.", - title="ORT resolutions", - ) - - -class LicenseFindingCurationsModel(BaseModel): - path: str - start_lines: int | str | None = None - line_count: int | None = None - detected_license: str | None = None - concluded_license: str - reason: PackageConfigurationSchemaLicenseFindingCurationReason - comment: str | None = None - - -class OrtRepositoryConfigurationH(BaseModel): - license_findings: list[LicenseFindingCurationsModel] - packages: Curations | None = None - - -class OrtRepositoryConfigurationCurations1(BaseModel): - license_findings: list[LicenseFindingCurationsModel] | None = None - packages: Curations - - -class RepositoryConfiguration(BaseModel): - """ - Represents the configuration for an OSS-Review-Toolkit (ORT) repository. - - This class defines various configuration options for analyzing, including, excluding, - resolving, and curating artifacts in a repository. It also provides settings for package - configurations, license choices, and snippet choices. - - Usage: - Instantiate this class to specify repository-level configuration for ORT analysis. - Each field corresponds to a specific aspect of the repository's configuration. - """ - - analyzer: RepositoryAnalyzerConfiguration | None = Field( - None, - description="Define Analyzer specific options", - ) - includes: OrtRepositoryConfigurationIncludes | None = Field( - None, - description="Defines which parts of a repository should be included.", - ) - excludes: OrtRepositoryConfigurationExcludes | None = Field( - None, - description="Defines which parts of a repository should be excluded.", - ) - resolutions: ResolutionsSchema | None = None - curations: Curations | None = Field( - None, - 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[PackageConfiguration] = Field( - default_factory=list, - description="A configuration for a specific package and provenance.", - ) - license_choices: OrtRepositoryConfigurationLicenseChoices | None = Field( - None, - description="A configuration to select a license from a multi-licensed package.", - ) - snippet_choices: list[OrtRepositoryConfigurationSnippetChoice] | None = Field( - None, - description="A configuration to select a snippet from a package with multiple snippet findings.", - ) diff --git a/src/ort/models/text_location.py b/src/ort/models/text_location.py new file mode 100644 index 0000000..f3ec526 --- /dev/null +++ b/src/ort/models/text_location.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from pydantic import BaseModel, ConfigDict, Field, field_validator + + +class TextLocation(BaseModel): + """ + A [TextLocation] references text located in a file. + """ + + model_config = ConfigDict( + extra="forbid", + ) + + path: str = Field( + ..., + description="The path (with invariant separators) of the file that contains the text.", + ) + start_line: int = Field( + ..., + description="The line the text is starting at.", + ) + end_line: int = Field( + ..., + description="The line the text is ending at.", + ) + + @field_validator("start_line", "end_line", mode="before") + @classmethod + def validate_line_numbers(cls, value): + if isinstance(value, str): + value = int(value) + if value < 0: + raise ValueError("Line numbers must be greater than or equal to 0.") + return value diff --git a/src/ort/utils/__init__.py b/src/ort/utils/__init__.py index 6567d53..d6bcaa2 100644 --- a/src/ort/utils/__init__.py +++ b/src/ort/utils/__init__.py @@ -1,10 +1,12 @@ # SPDX-FileCopyrightText: 2025 Helio Chissini de Castro # SPDX-License-Identifier: MIT +from ort.utils.convert_enum import convert_enum from ort.utils.environment import Environment from ort.utils.processed_declared_license import ProcessedDeclaredLicense __all__ = [ + "convert_enum", "Environment", "ProcessedDeclaredLicense", ] diff --git a/src/ort/utils/convert_enum.py b/src/ort/utils/convert_enum.py new file mode 100644 index 0000000..bc7b5c1 --- /dev/null +++ b/src/ort/utils/convert_enum.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + + +def convert_enum(enum_cls, v): + def _convert(item): + if isinstance(item, str): + try: + return enum_cls[item] + except KeyError: + raise ValueError(f"Invalid value for {enum_cls.__name__}: {item}") + return item + + if isinstance(v, (list, set)): + return {_convert(item) for item in v} + if isinstance(v, str): + return _convert(v) + return v diff --git a/src/ort/utils/spdx/__init__.py b/src/ort/utils/spdx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ort/utils/spdx/spdx_expression.py b/src/ort/utils/spdx/spdx_expression.py new file mode 100644 index 0000000..1e9a468 --- /dev/null +++ b/src/ort/utils/spdx/spdx_expression.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT + +from enum import IntEnum + + +class SpdxExpression(IntEnum): + """ + The level of strictness to apply when validating an SpdxExpression. + + Attributes: + ALLOW_ANY: + Any license identifier string is leniently allowed. The expression is not limited to + SPDX license identifier strings or LicenseRefs. + ALLOW_DEPRECATED: + All SPDX license identifier strings, including deprecated ones, and LicenseRefs are + allowed. Arbitrary license identifier strings are not allowed. + ALLOW_CURRENT: + Only current SPDX license identifier strings and LicenseRefs are allowed. This excludes + deprecated SPDX license identifier strings and arbitrary license identifier strings. + ALLOW_LICENSEREF_EXCEPTIONS: + This is the same as ALLOW_CURRENT, but additionally allows LicenseRefs that contain the + "exception" string to be used as license exceptions after the WITH operator. + """ + + ALLOW_ANY = 0 + ALLOW_DEPRECATED = 1 + ALLOW_CURRENT = 2 + ALLOW_LICENSEREF_EXCEPTIONS = 3 diff --git a/src/ort/utils/spdx/spdx_license_choice.py b/src/ort/utils/spdx/spdx_license_choice.py new file mode 100644 index 0000000..2207be5 --- /dev/null +++ b/src/ort/utils/spdx/spdx_license_choice.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro +# SPDX-License-Identifier: MIT +from pydantic import BaseModel, ConfigDict, Field + + +class SpdxLicenseChoice(BaseModel): + """ + An individual license choice. + [given] is the complete license expression, or a sub-expression of the license, where [choice] + is going to be applied + on. If no [given] is supplied, the [choice] will be applied to the complete expression of the package. + e.g.: with [given] as complete expression + ``` + -> Complete license expression: (A OR B) AND C + given: (A OR B) AND C + choice: A AND C + -> result: A AND C + ``` + e.g.: with [given] as sub-expression + ``` + -> Complete license expression: (A OR B) AND C + given: (A OR B) + choice: A + -> result: A AND C + ``` + e.g.: without [given] + ``` + -> Complete license expression: (A OR B) AND (C OR D) + choice: A AND C + -> result: A AND C + ``` + """ + + model_config = ConfigDict( + extra="forbid", + ) + given: str | None = Field( + default=None, + description="SPDX", + ) + choice: str = Field( + ..., + description="Package", + ) diff --git a/tests/test_ort_repository_configuration.py b/tests/test_repository_configuration.py similarity index 82% rename from tests/test_ort_repository_configuration.py rename to tests/test_repository_configuration.py index 9043c94..e7eaf78 100644 --- a/tests/test_ort_repository_configuration.py +++ b/tests/test_repository_configuration.py @@ -4,12 +4,8 @@ import pytest -from ort.models.repository_configuration import ( - OrtRepositoryConfigurationIncludes, - OrtRepositoryConfigurationIncludesPath, - PathIncludeReason, - RepositoryConfiguration, -) +from ort.models import Includes, PathInclude, PathIncludeReason +from ort.models.config.repository_configuration import RepositoryConfiguration from tests.utils.load_yaml_config import load_yaml_config @@ -36,16 +32,16 @@ def test_only_include_valid(): # Instantiate the model and check values try: - includes_model = OrtRepositoryConfigurationIncludes( + includes_model = Includes( paths=[ - OrtRepositoryConfigurationIncludesPath( - pattern=path_cfg["pattern"], reason=PathIncludeReason.source_of, comment=path_cfg["comment"] + PathInclude( + pattern=path_cfg["pattern"], reason=PathIncludeReason.SOURCE_OF, comment=path_cfg["comment"] ) ] ) repo_config = RepositoryConfiguration(includes=includes_model) except Exception as e: - pytest.fail(f"Failed to instantiate OrtRepositoryConfiguration: {e}") + pytest.fail(f"Failed to instantiate RepositoryConfiguration: {e}") if not repo_config.includes or not getattr(repo_config.includes, "paths", None): pytest.fail("No path includes are provided.") @@ -56,7 +52,7 @@ def test_only_include_valid(): path_obj = paths[0] if path_obj.pattern != "test/**": pytest.fail(f"Pattern mismatch: {path_obj.pattern}") - if path_obj.reason != PathIncludeReason.source_of: + if path_obj.reason != PathIncludeReason.SOURCE_OF: pytest.fail(f"Reason mismatch: {path_obj.reason}") if path_obj.comment != "Included for test": pytest.fail(f"Comment mismatch: {path_obj.comment}") @@ -65,7 +61,7 @@ def test_only_include_valid(): def test_only_include_reason_fail(): """ Test that providing an invalid 'reason' value in the path configuration - raises a ValueError when instantiating OrtRepositoryConfigurationIncludesPath. + raises a ValueError when instantiating RepositoryConfigurationIncludesPath. The test expects failure when 'reason' is not a valid PathIncludeReason enum. """ config_data = load_yaml_config("only_include_reason_fail.yml", "repo_config") @@ -82,9 +78,9 @@ def test_only_include_reason_fail(): # Try to instantiate the model and expect failure if "BINARY_OF" is not a valid enum with pytest.raises(ValueError): - OrtRepositoryConfigurationIncludes( + Includes( paths=[ - OrtRepositoryConfigurationIncludesPath( + PathInclude( pattern=path_cfg["pattern"], reason=path_cfg["reason"], # This should fail, not a valid PathIncludeReason comment=path_cfg["comment"],