python-ort 0.8.15__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 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",
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
 
5
- from pydantic import BaseModel, ConfigDict, Field, field_serializer
5
+ from pydantic import BaseModel, ConfigDict, Field
6
6
 
7
7
  from ort.models.config.license_finding_curation import LicenseFindingCuration
8
8
  from ort.models.config.path_exclude import PathExclude
@@ -66,7 +66,3 @@ class PackageConfiguration(BaseModel):
66
66
  default_factory=list,
67
67
  description="License finding curations.",
68
68
  )
69
-
70
- @field_serializer("id")
71
- def serialize_id(self, value: Identifier):
72
- return str(value)
ort/models/identifier.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  from typing import Any
6
6
 
7
- from pydantic import BaseModel, ConfigDict, Field, model_validator
7
+ from pydantic import BaseModel, ConfigDict, Field, model_serializer, model_validator
8
8
 
9
9
 
10
10
  class Identifier(BaseModel):
@@ -65,3 +65,7 @@ class Identifier(BaseModel):
65
65
 
66
66
  def __str__(self) -> str:
67
67
  return ":".join([self.orttype, self.namespace, self.name, self.version])
68
+
69
+ @model_serializer(mode="plain")
70
+ def serialize(self) -> str:
71
+ return str(self)
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-ort
3
- Version: 0.8.15
3
+ Version: 0.9.0
4
4
  Summary: A Python Ort model serialization library
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,5 +1,5 @@
1
1
  ort/__init__.py,sha256=lwG8GVwXZCnL4yGOKgja1Tvrt_JeOCABZw3F8ckXuuU,469
2
- ort/models/__init__.py,sha256=IAqyDq-u3qlbJYbEdAbovL7V8e4r2tqjz6NOhqiCVWs,2294
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
@@ -24,7 +24,7 @@ ort/models/config/license_choices.py,sha256=Zlwzl6ZYLoIYyt8ZJsHYVV23u6FT7el29jcK
24
24
  ort/models/config/license_finding_curation.py,sha256=ZTdUv0TQVCqwXjQqN9YXoBuiOzBB21tK6IHZmz3yXik,3542
25
25
  ort/models/config/license_finding_curation_reason.py,sha256=PN_PixQH8BB_ilnMVBC_jZEQo1XqdrjAEbJb-_SWlfk,1099
26
26
  ort/models/config/local_file_storage_configuration.py,sha256=mB5_B3ly9spjwpIkHsC1t_vbsUyxlVz2s0sFW2UxCNE,653
27
- ort/models/config/package_configuration.py,sha256=WP2A5tlmJq1Sy75YYdJt-OWINAuNDVG-sSjgmI43O_Q,2995
27
+ ort/models/config/package_configuration.py,sha256=EFujo40w_OmTgRNhbOxJNP6dtcq2EKMMOCUSWsgv8KQ,2875
28
28
  ort/models/config/package_manager_configuration.py,sha256=xpVwOzLNuBhGfEY2_Fz-6GagJXpKQI59kg5yzvZKHpI,1345
29
29
  ort/models/config/path_exclude.py,sha256=_2vpEwj-nxnV2qnbjx-_etPrZQTpETpm_ZVzuFesLc8,1063
30
30
  ort/models/config/path_exclude_reason.py,sha256=at0nrNOqiOlHS8q_rJEIM-tDViwKB7B1kZXz_10IsMk,1837
@@ -59,10 +59,13 @@ ort/models/dependency_reference.py,sha256=9fuqrF38c8bBW63Cp6sTnot-h2D3sGdkEuXR8S
59
59
  ort/models/evaluator_run.py,sha256=LsJJ-GMQKqdvJNdj3wnHMJuKD7EqojJ5VQKAPTzPFzY,467
60
60
  ort/models/file_list.py,sha256=SKHqLNutldJefNIzt7jKo6_aAdF9hlUd_Z7cQxAj_H4,1627
61
61
  ort/models/hash.py,sha256=fE1pPgx98WkmGid-SK3AuTBHMZrD6No1SJEEudrbP80,1160
62
- ort/models/identifier.py,sha256=GcP65v1KWL3uX5Ee7ZyTaTGNbIFuBo5kEZZ86d-oDkU,2525
62
+ 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.8.15.dist-info/licenses/LICENSE,sha256=koFhbHMglt1BNVuMKdBBlrQcgQsWLgo4pZW6cCtyGvA,1081
111
- python_ort-0.8.15.dist-info/WHEEL,sha256=XkDrRXQq-qVsrKMtsDUOHeLkiG7UK4Ds0JuG05OqKU4,81
112
- python_ort-0.8.15.dist-info/METADATA,sha256=0aKQZhyUnmwWc28toDxk4KQwhEYjAccgG773ovj9g84,1406
113
- python_ort-0.8.15.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.13
2
+ Generator: uv 0.11.21
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any