python-ort 0.8.16__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ort/models/__init__.py +2 -0
- ort/models/licenses/license_categorization.py +27 -0
- ort/models/licenses/license_category.py +26 -0
- ort/models/licenses/license_classifications.py +124 -0
- {python_ort-0.8.16.dist-info → python_ort-0.9.0.dist-info}/METADATA +1 -1
- {python_ort-0.8.16.dist-info → python_ort-0.9.0.dist-info}/RECORD +8 -5
- {python_ort-0.8.16.dist-info → python_ort-0.9.0.dist-info}/WHEEL +1 -1
- {python_ort-0.8.16.dist-info → python_ort-0.9.0.dist-info}/licenses/LICENSE +0 -0
ort/models/__init__.py
CHANGED
|
@@ -20,6 +20,7 @@ from .dependency_reference import DependencyReference
|
|
|
20
20
|
from .hash import Hash
|
|
21
21
|
from .identifier import Identifier
|
|
22
22
|
from .issue import Issue
|
|
23
|
+
from .licenses.license_classifications import LicenseClassifications
|
|
23
24
|
from .ort_result import OrtResult
|
|
24
25
|
from .package import Package
|
|
25
26
|
from .package_curation import PackageCuration
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
"Includes",
|
|
52
53
|
"Excludes",
|
|
53
54
|
"Issue",
|
|
55
|
+
"LicenseClassifications",
|
|
54
56
|
"OrtResult",
|
|
55
57
|
"Package",
|
|
56
58
|
"PackageCuration",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro <heliocastro@gmail.com>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LicenseCategorization(BaseModel):
|
|
8
|
+
"""
|
|
9
|
+
A class for configuring metadata for a specific license referred to by an SPDX license identifier.
|
|
10
|
+
|
|
11
|
+
The metadata consists of assignments to generic categories whose exact meaning is customer specific.
|
|
12
|
+
The categories a license belong to can be evaluated by other components, such as rules or templates,
|
|
13
|
+
which can decide - based on this information - how to handle a specific license.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
model_config = ConfigDict(
|
|
18
|
+
extra="forbid",
|
|
19
|
+
frozen=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
id: str = Field(description="The [SpdxSingleLicenseExpression] of this [LicenseCategorization].")
|
|
23
|
+
|
|
24
|
+
categories: set[str] = Field(
|
|
25
|
+
default_factory=set,
|
|
26
|
+
description="The identifiers of the [license categories][LicenseCategory] this license is assigned to.",
|
|
27
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro <heliocastro@gmail.com>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LicenseCategory(BaseModel):
|
|
8
|
+
"""
|
|
9
|
+
A category where licenses can be assigned to. The assignment is expressed via a [LicenseCategorization]. Categories
|
|
10
|
+
do not have any specific semantic, but users are free to define their own set of categories.
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(
|
|
15
|
+
extra="forbid",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
name: str = Field(
|
|
19
|
+
description="The name of this [LicenseCategory]. The name can be chosen freely, "
|
|
20
|
+
"but must be unique over all categories."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
description: str = Field(
|
|
24
|
+
default_factory=str,
|
|
25
|
+
description="A description for this [LicenseCategory].",
|
|
26
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Helio Chissini de Castro <heliocastro@gmail.com>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
5
|
+
|
|
6
|
+
from .license_categorization import LicenseCategorization
|
|
7
|
+
from .license_category import LicenseCategory
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LicenseClassifications(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
Classifications for licenses which allow assigning metadata to licenses. This allows defining rather generic
|
|
13
|
+
categories and assigning licenses to these. That way flexible classifications can be created based on
|
|
14
|
+
customizable categories. The available license categories need to be declared explicitly; when creating an
|
|
15
|
+
instance, it is checked that all the references from the [categorizations] point to existing [categories].
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
model_config = ConfigDict(
|
|
19
|
+
extra="forbid",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
categories: list[LicenseCategory] = Field(
|
|
23
|
+
default_factory=list,
|
|
24
|
+
description="Defines metadata for the license categories.",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
categorizations: list[LicenseCategorization] = Field(
|
|
28
|
+
default_factory=list,
|
|
29
|
+
description="Defines metadata for licenses.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
@model_validator(mode="after")
|
|
33
|
+
def _check_consistency(self) -> "LicenseClassifications":
|
|
34
|
+
category_names = [category.name for category in self.categories]
|
|
35
|
+
duplicate_categories = {name for name in category_names if category_names.count(name) > 1}
|
|
36
|
+
if duplicate_categories:
|
|
37
|
+
raise ValueError(f"Found multiple license categories with the same name: {sorted(duplicate_categories)}")
|
|
38
|
+
|
|
39
|
+
license_ids = [categorization.id for categorization in self.categorizations]
|
|
40
|
+
duplicate_ids = {license_id for license_id in license_ids if license_ids.count(license_id) > 1}
|
|
41
|
+
if duplicate_ids:
|
|
42
|
+
raise ValueError(f"Found multiple license categorizations with the same id: {sorted(duplicate_ids)}")
|
|
43
|
+
|
|
44
|
+
known_categories = set(category_names)
|
|
45
|
+
invalid_categorizations = {
|
|
46
|
+
categorization.id: sorted(categorization.categories - known_categories)
|
|
47
|
+
for categorization in self.categorizations
|
|
48
|
+
if categorization.categories - known_categories
|
|
49
|
+
}
|
|
50
|
+
if invalid_categorizations:
|
|
51
|
+
referenced_ids = ", ".join(invalid_categorizations.keys())
|
|
52
|
+
unknown_categories = sorted({name for names in invalid_categorizations.values() for name in names})
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Found licenses that reference non-existing categories: {referenced_ids}; "
|
|
55
|
+
f"unknown categories are {unknown_categories}."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def category_names(self) -> set[str]:
|
|
62
|
+
"""The names of all categories defined."""
|
|
63
|
+
return {category.name for category in self.categories}
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def licenses_by_category(self) -> dict[str, set[str]]:
|
|
67
|
+
"""A mapping for fast look-ups of the licenses assigned to a given category."""
|
|
68
|
+
result: dict[str, set[str]] = {}
|
|
69
|
+
for categorization in self.categorizations:
|
|
70
|
+
for category in categorization.categories:
|
|
71
|
+
result.setdefault(category, set()).add(categorization.id)
|
|
72
|
+
|
|
73
|
+
for category in self.categories:
|
|
74
|
+
result.setdefault(category.name, set())
|
|
75
|
+
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def categories_by_license(self) -> dict[str, set[str]]:
|
|
80
|
+
"""A mapping for fast look-ups of the categories assigned to a given license."""
|
|
81
|
+
return {categorization.id: categorization.categories for categorization in self.categorizations}
|
|
82
|
+
|
|
83
|
+
def __getitem__(self, license_id: str) -> set[str] | None:
|
|
84
|
+
"""Return the categories for the given license [license_id], or None if the license is not categorized."""
|
|
85
|
+
return self.categories_by_license.get(license_id)
|
|
86
|
+
|
|
87
|
+
def is_categorized(self, license_id: str) -> bool:
|
|
88
|
+
"""Check whether there is a categorization for the given license [license_id]."""
|
|
89
|
+
return license_id in self.categories_by_license
|
|
90
|
+
|
|
91
|
+
def merge(self, other: "LicenseClassifications") -> "LicenseClassifications":
|
|
92
|
+
"""Merge [other] into these classifications, overwriting any conflicting existing classifications."""
|
|
93
|
+
filtered_categories_by_license: dict[str, set[str]] = {}
|
|
94
|
+
|
|
95
|
+
# Remove categories that are also used in the other classification as different classifications might use
|
|
96
|
+
# different semantics for the same category name.
|
|
97
|
+
other_category_names = other.category_names
|
|
98
|
+
for categorization in self.categorizations:
|
|
99
|
+
filtered_categories = {
|
|
100
|
+
category for category in categorization.categories if category not in other_category_names
|
|
101
|
+
}
|
|
102
|
+
if filtered_categories:
|
|
103
|
+
filtered_categories_by_license[categorization.id] = filtered_categories
|
|
104
|
+
|
|
105
|
+
# Merge other into existing categories for each license.
|
|
106
|
+
for categorization in other.categorizations:
|
|
107
|
+
filtered_categories_by_license.setdefault(categorization.id, set()).update(categorization.categories)
|
|
108
|
+
|
|
109
|
+
used_categories = {
|
|
110
|
+
category for categories in filtered_categories_by_license.values() for category in categories
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
merged_categories: list[LicenseCategory] = []
|
|
114
|
+
for category in [*self.categories, *other.categories]:
|
|
115
|
+
if category.name in used_categories and category not in merged_categories:
|
|
116
|
+
merged_categories.append(category)
|
|
117
|
+
|
|
118
|
+
return LicenseClassifications(
|
|
119
|
+
categories=merged_categories,
|
|
120
|
+
categorizations=[
|
|
121
|
+
LicenseCategorization(id=license_id, categories=categories)
|
|
122
|
+
for license_id, categories in filtered_categories_by_license.items()
|
|
123
|
+
],
|
|
124
|
+
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
ort/__init__.py,sha256=lwG8GVwXZCnL4yGOKgja1Tvrt_JeOCABZw3F8ckXuuU,469
|
|
2
|
-
ort/models/__init__.py,sha256=
|
|
2
|
+
ort/models/__init__.py,sha256=v6jvd2vvIvuKIo_VN2EpyDvH_77M6LWuru-rLqQCk1I,2393
|
|
3
3
|
ort/models/advisor_capability.py,sha256=u1-yQI66jVWgrOacZdIEZ_zcK_A_qZ8ma0umJs01V1o,839
|
|
4
4
|
ort/models/advisor_details.py,sha256=wo5h3uKD_94ukw9nUGOBKGonCd8CF8YcPkcu7XoBR_I,754
|
|
5
5
|
ort/models/advisor_result.py,sha256=LgfZRPdERNDVcIW7hRqCPyudbWXJvlSTlBLWuAONrlM,1317
|
|
@@ -63,6 +63,9 @@ ort/models/identifier.py,sha256=H3NvxZlHLZQDI2hQn8AphqieL5QBkfj5Y6JTxEItSNY,2637
|
|
|
63
63
|
ort/models/issue.py,sha256=nUMKRABe8tF6pZzCdaPt2tjdYFf5zRgEipy-o68Ym5M,1153
|
|
64
64
|
ort/models/license_finding.py,sha256=8vlnBV0LMb8cerOpZl6YH1eYwVuViuh0VseD6Sai_q0,1897
|
|
65
65
|
ort/models/license_source.py,sha256=TY0HBFvOt0r9QxElpmobLF5SmjyA1wynGNPsqBxwHdo,727
|
|
66
|
+
ort/models/licenses/license_categorization.py,sha256=kHAS8Aowb3V2fRYWPOr256tBWYHE0s9Pu6zBjjk3Mtk,989
|
|
67
|
+
ort/models/licenses/license_category.py,sha256=ABV3QFTrEp_WiNYoGMRwUmg2QPIhhV_dT8hB4yzaQJQ,795
|
|
68
|
+
ort/models/licenses/license_classifications.py,sha256=2WTo21x72D66azYj8Y0Sy8IB5B9Ln8xBlt2HtSvqelU,5760
|
|
66
69
|
ort/models/ort_result.py,sha256=NQ7dsGW9C55ROGTQz2Up4lH-tJcR9vsP21i0jbeJUNg,2346
|
|
67
70
|
ort/models/package.py,sha256=9AMSSDK4gjOk6S-tWQgVIPMad75p4Ydm7AgvQncCgsk,5162
|
|
68
71
|
ort/models/package_curation.py,sha256=_F1GF7D_lgeCQs9j3Ft5ImSPO9RdBjepdEBo2JxuNPY,356
|
|
@@ -107,7 +110,7 @@ ort/utils/spdx/spdx_expression.py,sha256=K_ZsIGwLP_zNcS6haO2TFM1aRa2l79zFU7HZaB_
|
|
|
107
110
|
ort/utils/spdx/spdx_license_choice.py,sha256=hop1tcSAq1ZWMd-504tAaCJ8KxzX_fp9_-r_LvEE2wc,1227
|
|
108
111
|
ort/utils/validated_enum.py,sha256=UFAGP7BGzAx1nSBWEeMXn1X1teVb6507N-lfXBf5qGM,1643
|
|
109
112
|
ort/utils/yaml_loader.py,sha256=kOiuRV93_q_ZUnAqHoNohAvqL7fkUn66WrvrdbgZKHs,1290
|
|
110
|
-
python_ort-0.
|
|
111
|
-
python_ort-0.
|
|
112
|
-
python_ort-0.
|
|
113
|
-
python_ort-0.
|
|
113
|
+
python_ort-0.9.0.dist-info/licenses/LICENSE,sha256=koFhbHMglt1BNVuMKdBBlrQcgQsWLgo4pZW6cCtyGvA,1081
|
|
114
|
+
python_ort-0.9.0.dist-info/WHEEL,sha256=8ZlpUMJ7mlDirmlHRhDirEx_nPnARrwDjeE92mlk68E,81
|
|
115
|
+
python_ort-0.9.0.dist-info/METADATA,sha256=08LD-ojLhLWMc8_8s8MnmZ_7qkOmu546FFb2raRZd8o,1405
|
|
116
|
+
python_ort-0.9.0.dist-info/RECORD,,
|
|
File without changes
|