openepd 7.0.0__tar.gz → 7.0.1__tar.gz
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.
- {openepd-7.0.0 → openepd-7.0.1}/PKG-INFO +2 -2
- {openepd-7.0.0 → openepd-7.0.1}/pyproject.toml +3 -3
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/__version__.py +1 -1
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/category/sync_api.py +1 -1
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/base.py +1 -1
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/common.py +8 -5
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/epd.py +4 -4
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/generic_estimate.py +2 -2
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/lcia.py +30 -24
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/org.py +38 -26
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/base.py +3 -2
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/steel.py +3 -1
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/validation/quantity.py +37 -32
- openepd-7.0.0/src/openepd/__init__.py +0 -21
- {openepd-7.0.0 → openepd-7.0.1}/LICENSE +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/README.md +0 -0
- {openepd-7.0.0/src/openepd/api → openepd-7.0.1/src/openepd}/__init__.py +0 -0
- {openepd-7.0.0/src/openepd/api/average_dataset → openepd-7.0.1/src/openepd/api}/__init__.py +0 -0
- {openepd-7.0.0/src/openepd/api/category → openepd-7.0.1/src/openepd/api/average_dataset}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/average_dataset/generic_estimate_sync_api.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/average_dataset/industry_epd_sync_api.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/base_sync_client.py +0 -0
- {openepd-7.0.0/src/openepd/api/dto → openepd-7.0.1/src/openepd/api/category}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/category/dto.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/common.py +0 -0
- {openepd-7.0.0/src/openepd/api/epd → openepd-7.0.1/src/openepd/api/dto}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/dto/base.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/dto/common.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/dto/meta.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/dto/mf.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/dto/params.py +0 -0
- {openepd-7.0.0/src/openepd/api/pcr → openepd-7.0.1/src/openepd/api/epd}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/epd/dto.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/epd/sync_api.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/errors.py +0 -0
- {openepd-7.0.0/src/openepd/api/test → openepd-7.0.1/src/openepd/api/pcr}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/pcr/sync_api.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/sync_client.py +0 -0
- {openepd-7.0.0/src/openepd/bundle → openepd-7.0.1/src/openepd/api/test}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/api/utils.py +0 -0
- {openepd-7.0.0/src/openepd/model → openepd-7.0.1/src/openepd/bundle}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/bundle/base.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/bundle/model.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/bundle/reader.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/bundle/writer.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/m49/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/m49/const.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/m49/utils.py +0 -0
- {openepd-7.0.0/src/openepd/model/specs/singular/mixins → openepd-7.0.1/src/openepd/model}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/category.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/declaration.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/factory.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/geography.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/industry_epd.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/pcr.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/README.md +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/asphalt.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/concrete.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/enums.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/accessories.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/aggregates.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/aluminium.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/asphalt.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/bulk_materials.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/cast_decks_and_underlayment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/cladding.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/cmu.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/concrete.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/conveying_equipment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/electrical.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/electricity.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/finishes.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/fire_and_smoke_protection.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/furnishings.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/grouting.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/manufacturing_inputs.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/masonry.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/material_handling.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/mechanical.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/mechanical_insulation.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/network_infrastructure.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/openings.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/other_electrical_equipment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/other_materials.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/plumbing.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/precast_concrete.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/sheathing.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/thermal_moisture_protection.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/utility_piping.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/wood.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/range/wood_joists.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/accessories.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/aggregates.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/aluminium.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/asphalt.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/bulk_materials.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/cast_decks_and_underlayment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/cladding.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/cmu.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/common.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/concrete.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/conveying_equipment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/deprecated/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/deprecated/concrete.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/deprecated/steel.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/electrical.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/electrical_transmission_and_distribution_equipment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/electricity.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/finishes.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/fire_and_smoke_protection.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/furnishings.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/grouting.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/manufacturing_inputs.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/masonry.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/material_handling.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/mechanical.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/mechanical_insulation.py +0 -0
- {openepd-7.0.0/src/openepd/model/validation → openepd-7.0.1/src/openepd/model/specs/singular/mixins}/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/mixins/conduit_mixin.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/network_infrastructure.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/openings.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/other_electrical_equipment.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/other_materials.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/plumbing.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/precast_concrete.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/sheathing.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/steel.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/thermal_moisture_protection.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/utility_piping.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/wood.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/specs/singular/wood_joists.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/standard.py +0 -0
- /openepd-7.0.0/src/openepd/model/validation/numbers.py → /openepd-7.0.1/src/openepd/model/validation/__init__.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/validation/common.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/validation/enum.py +0 -0
- /openepd-7.0.0/src/openepd/patch_pydantic.py → /openepd-7.0.1/src/openepd/model/validation/numbers.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/model/versioning.py +0 -0
- {openepd-7.0.0 → openepd-7.0.1}/src/openepd/py.typed +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: openepd
|
3
|
-
Version: 7.0.
|
3
|
+
Version: 7.0.1
|
4
4
|
Summary: Python library to work with OpenEPD format
|
5
5
|
Home-page: https://github.com/cchangelabs/openepd
|
6
6
|
License: Apache-2.0
|
@@ -21,7 +21,7 @@ Requires-Dist: email-validator (>=1.3.1)
|
|
21
21
|
Requires-Dist: idna (>=3.7)
|
22
22
|
Requires-Dist: open-xpd-uuid (>=0.2.1,<0.3.0)
|
23
23
|
Requires-Dist: openlocationcode (>=1.0.1)
|
24
|
-
Requires-Dist: pydantic (>=
|
24
|
+
Requires-Dist: pydantic (>=2.0,<3)
|
25
25
|
Requires-Dist: requests (>=2.0) ; extra == "api-client"
|
26
26
|
Project-URL: Repository, https://github.com/cchangelabs/openepd
|
27
27
|
Description-Content-Type: text/markdown
|
@@ -1,11 +1,11 @@
|
|
1
1
|
[tool.ruff]
|
2
2
|
line-length = 120
|
3
|
-
target-version = "
|
3
|
+
target-version = "py311"
|
4
4
|
exclude = [".*pyi"]
|
5
5
|
|
6
6
|
[tool.poetry]
|
7
7
|
name = "openepd"
|
8
|
-
version = "7.0.
|
8
|
+
version = "7.0.1"
|
9
9
|
license = "Apache-2.0"
|
10
10
|
description = "Python library to work with OpenEPD format"
|
11
11
|
authors = ["C-Change Labs <support@c-change-labs.com>"]
|
@@ -26,7 +26,7 @@ exclude = ["**/test_*.py", "**/tests/**"]
|
|
26
26
|
|
27
27
|
[tool.poetry.dependencies]
|
28
28
|
python = "^3.11"
|
29
|
-
pydantic = ">=
|
29
|
+
pydantic = ">=2.0,<3"
|
30
30
|
email-validator = ">=1.3.1"
|
31
31
|
requests = { version = ">=2.0", optional = true }
|
32
32
|
idna = ">=3.7"
|
@@ -28,7 +28,7 @@ class CategoryApi(BaseApiMethodGroup):
|
|
28
28
|
:return: categories tree wrapped in OpenEpdApiResponse
|
29
29
|
"""
|
30
30
|
response = self._client.do_request("get", "/v2/categories/tree")
|
31
|
-
return CategoryTreeResponse.
|
31
|
+
return CategoryTreeResponse.model_validate_json(response.content)
|
32
32
|
|
33
33
|
def get_tree(self) -> Category:
|
34
34
|
"""
|
@@ -234,7 +234,7 @@ class BaseDocumentFactory(Generic[TRootDocument]):
|
|
234
234
|
for x, doc_cls in cls.VERSION_MAP.items():
|
235
235
|
if x.major == version.major:
|
236
236
|
if version.minor <= x.minor:
|
237
|
-
return doc_cls(
|
237
|
+
return doc_cls.model_validate(data)
|
238
238
|
else:
|
239
239
|
raise ValueError(
|
240
240
|
f"Unsupported version: {version}. The highest supported version from branch {x.major}.x is {x}"
|
@@ -14,7 +14,7 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
from enum import StrEnum
|
17
|
-
from typing import Annotated, Any
|
17
|
+
from typing import Annotated, Any, Self
|
18
18
|
|
19
19
|
import pydantic
|
20
20
|
import pydantic_core
|
@@ -32,12 +32,15 @@ class Amount(BaseOpenEpdSchema):
|
|
32
32
|
default=None,
|
33
33
|
)
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
model_config = pydantic.ConfigDict(from_attributes=True)
|
36
|
+
|
37
|
+
@pydantic.model_validator(mode="after")
|
38
|
+
def check_qty_or_unit(self) -> Self:
|
37
39
|
"""Ensure that qty or unit is provided."""
|
38
|
-
|
40
|
+
|
41
|
+
if self.qty is None and self.unit is None:
|
39
42
|
raise ValueError("Either qty or unit must be provided.")
|
40
|
-
return
|
43
|
+
return self
|
41
44
|
|
42
45
|
def to_quantity_str(self):
|
43
46
|
"""Return a string representation of the amount."""
|
@@ -282,10 +282,10 @@ class EpdWithDepsV0(EpdV0, title="EPD (with Dependencies)"):
|
|
282
282
|
some required fields in Org (like web_domain), and WithDeps would not.
|
283
283
|
"""
|
284
284
|
|
285
|
-
manufacturer: Org | None = pydantic.Field(description=MANUFACTURER_DESCRIPTION)
|
286
|
-
epd_developer: Org | None = pydantic.Field(description=DEVELOPER_DESCRIPTION, default=None)
|
287
|
-
program_operator: Org | None = pydantic.Field(description=PROGRAM_OPERATOR_DESCRIPTION)
|
288
|
-
third_party_verifier: Org | None = pydantic.Field(description=THIRD_PARTY_VERIFIER_DESCRIPTION)
|
285
|
+
manufacturer: Org | None = pydantic.Field(description=MANUFACTURER_DESCRIPTION, default=None) # type: ignore[assignment]
|
286
|
+
epd_developer: Org | None = pydantic.Field(description=DEVELOPER_DESCRIPTION, default=None) # type: ignore[assignment]
|
287
|
+
program_operator: Org | None = pydantic.Field(description=PROGRAM_OPERATOR_DESCRIPTION, default=None) # type: ignore[assignment]
|
288
|
+
third_party_verifier: Org | None = pydantic.Field(description=THIRD_PARTY_VERIFIER_DESCRIPTION, default=None) # type: ignore[assignment]
|
289
289
|
|
290
290
|
|
291
291
|
EpdWithDeps = EpdWithDepsV0
|
@@ -110,8 +110,8 @@ class GenericEstimateWithDepsV0(GenericEstimateV0, title="Generic Estimate (with
|
|
110
110
|
Contains related entities - orgs - with full fields, to support object matching in implementations.
|
111
111
|
"""
|
112
112
|
|
113
|
-
publisher: Org | None = pydantic.Field(description="Organization that published the LCA results.")
|
114
|
-
reviewer: Org | None = pydantic.Field(description="Org that performed a critical review of the LCA.")
|
113
|
+
publisher: Org | None = pydantic.Field(description="Organization that published the LCA results.", default=None) # type: ignore[assignment]
|
114
|
+
reviewer: Org | None = pydantic.Field(description="Org that performed a critical review of the LCA.", default=None) # type: ignore[assignment]
|
115
115
|
|
116
116
|
|
117
117
|
GenericEstimateWithDeps = GenericEstimateWithDepsV0
|
@@ -18,6 +18,7 @@ from typing import Any, ClassVar
|
|
18
18
|
|
19
19
|
import pydantic
|
20
20
|
from pydantic import ConfigDict
|
21
|
+
from typing_extensions import Self
|
21
22
|
|
22
23
|
from openepd.model.base import BaseOpenEpdSchema
|
23
24
|
from openepd.model.common import Measurement
|
@@ -200,15 +201,18 @@ class ScopeSet(BaseOpenEpdSchema):
|
|
200
201
|
description="Potential net benefits from reuse, recycling, and/or energy recovery beyond the system boundary.",
|
201
202
|
)
|
202
203
|
|
203
|
-
|
204
|
-
|
204
|
+
model_config = pydantic.ConfigDict(from_attributes=True)
|
205
|
+
|
206
|
+
@pydantic.model_validator(mode="after")
|
207
|
+
def _unit_validator(self) -> Self:
|
205
208
|
all_units = set()
|
206
209
|
|
207
|
-
for k
|
210
|
+
for k in self.model_fields:
|
211
|
+
v = getattr(self, k, None)
|
208
212
|
if isinstance(v, Measurement):
|
209
213
|
all_units.add(v.unit)
|
210
214
|
|
211
|
-
if not
|
215
|
+
if not self.allowed_units:
|
212
216
|
# For unknown units - only units should be the same across all measurements (textually)
|
213
217
|
if len(all_units) > 1:
|
214
218
|
raise ValueError("All scopes and measurements should be expressed in the same unit.")
|
@@ -221,9 +225,9 @@ class ScopeSet(BaseOpenEpdSchema):
|
|
221
225
|
ExternalValidationConfig.QUANTITY_VALIDATOR.validate_same_dimensionality(first, unit)
|
222
226
|
|
223
227
|
# can correctly validate unit
|
224
|
-
if
|
228
|
+
if self.allowed_units is not None and len(all_units) == 1 and ExternalValidationConfig.QUANTITY_VALIDATOR:
|
225
229
|
unit = next(iter(all_units))
|
226
|
-
allowed_units =
|
230
|
+
allowed_units = self.allowed_units if isinstance(self.allowed_units, tuple) else (self.allowed_units,)
|
227
231
|
|
228
232
|
matched_unit = False
|
229
233
|
for allowed_unit in allowed_units:
|
@@ -237,7 +241,7 @@ class ScopeSet(BaseOpenEpdSchema):
|
|
237
241
|
f"'{', '.join(allowed_units)}' is only allowed unit for this scopeset. Provided '{unit}'"
|
238
242
|
)
|
239
243
|
|
240
|
-
return
|
244
|
+
return self
|
241
245
|
|
242
246
|
|
243
247
|
class ScopesetByNameBase(BaseOpenEpdSchema, extra="allow"):
|
@@ -279,7 +283,7 @@ class ScopesetByNameBase(BaseOpenEpdSchema, extra="allow"):
|
|
279
283
|
def _extra_scopeset_validator(cls, values: dict[str, Any]) -> dict[str, Any]:
|
280
284
|
for f in values:
|
281
285
|
# only interested in validating the extra fields
|
282
|
-
if f in cls.
|
286
|
+
if f in cls.model_fields:
|
283
287
|
continue
|
284
288
|
|
285
289
|
# extra impact of an unknown type - engage validation of ScopeSet
|
@@ -298,97 +302,97 @@ class ScopesetByNameBase(BaseOpenEpdSchema, extra="allow"):
|
|
298
302
|
class ScopeSetGwp(ScopeSet):
|
299
303
|
"""ScopeSet measured in kgCO2e."""
|
300
304
|
|
301
|
-
allowed_units = "kgCO2e"
|
305
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "kgCO2e"
|
302
306
|
|
303
307
|
|
304
308
|
class ScopeSetOdp(ScopeSet):
|
305
309
|
"""ScopeSet measured in kgCFC11e."""
|
306
310
|
|
307
|
-
allowed_units = "kgCFC11e"
|
311
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "kgCFC11e"
|
308
312
|
|
309
313
|
|
310
314
|
class ScopeSetAp(ScopeSet):
|
311
315
|
"""ScopeSet measured in kgSO2e."""
|
312
316
|
|
313
|
-
allowed_units = ("kgSO2e", "molHe")
|
317
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = ("kgSO2e", "molHe")
|
314
318
|
|
315
319
|
|
316
320
|
class ScopeSetEpNe(ScopeSet):
|
317
321
|
"""ScopeSet measured in kgNe."""
|
318
322
|
|
319
|
-
allowed_units = "kgNe"
|
323
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "kgNe"
|
320
324
|
|
321
325
|
|
322
326
|
class ScopeSetPocp(ScopeSet):
|
323
327
|
"""ScopeSet measured in kgO3e."""
|
324
328
|
|
325
|
-
allowed_units = ("kgO3e", "kgNMVOCe")
|
329
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = ("kgO3e", "kgNMVOCe")
|
326
330
|
|
327
331
|
|
328
332
|
class ScopeSetEpFresh(ScopeSet):
|
329
333
|
"""ScopeSet measured in kgPO4e."""
|
330
334
|
|
331
|
-
allowed_units = "kgPO4e"
|
335
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "kgPO4e"
|
332
336
|
|
333
337
|
|
334
338
|
class ScopeSetEpTerr(ScopeSet):
|
335
339
|
"""ScopeSet measured in molNe."""
|
336
340
|
|
337
|
-
allowed_units = "molNe"
|
341
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "molNe"
|
338
342
|
|
339
343
|
|
340
344
|
class ScopeSetIrp(ScopeSet):
|
341
345
|
"""ScopeSet measured in kilo Becquerel equivalent of u235."""
|
342
346
|
|
343
|
-
allowed_units = "kBqU235e"
|
347
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "kBqU235e"
|
344
348
|
|
345
349
|
|
346
350
|
class ScopeSetCTUh(ScopeSet):
|
347
351
|
"""ScopeSet measured in CTUh."""
|
348
352
|
|
349
|
-
allowed_units = "CTUh"
|
353
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "CTUh"
|
350
354
|
|
351
355
|
|
352
356
|
class ScopeSetM3Aware(ScopeSet):
|
353
357
|
"""ScopeSet measured in m3AWARE Water consumption by AWARE method."""
|
354
358
|
|
355
|
-
allowed_units = "m3AWARE"
|
359
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "m3AWARE"
|
356
360
|
|
357
361
|
|
358
362
|
class ScopeSetCTUe(ScopeSet):
|
359
363
|
"""ScopeSet measured in CTUe."""
|
360
364
|
|
361
|
-
allowed_units = "CTUe"
|
365
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "CTUe"
|
362
366
|
|
363
367
|
|
364
368
|
class ScopeSetDiseaseIncidence(ScopeSet):
|
365
369
|
"""ScopeSet measuring disease incidence measured in AnnualPerCapita (cases)."""
|
366
370
|
|
367
|
-
allowed_units = "AnnualPerCapita"
|
371
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "AnnualPerCapita"
|
368
372
|
|
369
373
|
|
370
374
|
class ScopeSetMass(ScopeSet):
|
371
375
|
"""ScopeSet measuring mass in kg."""
|
372
376
|
|
373
|
-
allowed_units = "kg"
|
377
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "kg"
|
374
378
|
|
375
379
|
|
376
380
|
class ScopeSetVolume(ScopeSet):
|
377
381
|
"""ScopeSet measuring mass in kg."""
|
378
382
|
|
379
|
-
allowed_units = "m3"
|
383
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "m3"
|
380
384
|
|
381
385
|
|
382
386
|
class ScopeSetMassOrVolume(ScopeSet):
|
383
387
|
"""ScopeSet measuring mass in kg OR volume in m3, example: radioactive waste."""
|
384
388
|
|
385
|
-
allowed_units = ("kg", "m3")
|
389
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = ("kg", "m3")
|
386
390
|
|
387
391
|
|
388
392
|
class ScopeSetEnergy(ScopeSet):
|
389
393
|
"""ScopeSet measuring mass in kg."""
|
390
394
|
|
391
|
-
allowed_units = "MJ"
|
395
|
+
allowed_units: ClassVar[str | tuple[str, ...] | None] = "MJ"
|
392
396
|
|
393
397
|
|
394
398
|
class ImpactSet(ScopesetByNameBase):
|
@@ -484,6 +488,8 @@ class ImpactSet(ScopesetByNameBase):
|
|
484
488
|
description="Land use related impacts / Soil quality, in potential soil quality parameters.",
|
485
489
|
)
|
486
490
|
|
491
|
+
model_config = pydantic.ConfigDict(from_attributes=True)
|
492
|
+
|
487
493
|
|
488
494
|
class LCIAMethod(StrEnum):
|
489
495
|
"""A list of available LCA methods."""
|
@@ -13,11 +13,11 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
from typing import
|
16
|
+
from typing import Any, Optional
|
17
17
|
|
18
18
|
from openlocationcode import openlocationcode
|
19
19
|
import pydantic
|
20
|
-
from pydantic import ConfigDict
|
20
|
+
from pydantic import ConfigDict
|
21
21
|
|
22
22
|
from openepd.model.base import BaseOpenEpdSchema
|
23
23
|
from openepd.model.common import Location, WithAltIdsMixin, WithAttachmentsMixin
|
@@ -47,36 +47,46 @@ class OrgRef(BaseOpenEpdSchema):
|
|
47
47
|
class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
|
48
48
|
"""Represent an organization."""
|
49
49
|
|
50
|
-
alt_names: (
|
51
|
-
Annotated[
|
52
|
-
list[str],
|
53
|
-
Annotated[
|
54
|
-
List[Annotated[str, StringConstraints(max_length=200)]],
|
55
|
-
Field(max_length=255),
|
56
|
-
],
|
57
|
-
]
|
58
|
-
| None
|
59
|
-
) = pydantic.Field(
|
50
|
+
alt_names: list[str] | None = pydantic.Field(
|
60
51
|
description="List of other names for organization",
|
61
52
|
examples=[["C-Change Labs", "C-Change Labs inc."]],
|
62
53
|
default=None,
|
54
|
+
max_length=255,
|
63
55
|
)
|
64
56
|
# TODO: NEW field, not in the spec
|
65
57
|
|
58
|
+
@pydantic.field_validator("alt_names", mode="before")
|
59
|
+
def _validate_alt_names(cls, value: Any) -> list[str] | None:
|
60
|
+
if value is None:
|
61
|
+
return value
|
62
|
+
|
63
|
+
if not isinstance(value, list):
|
64
|
+
raise TypeError(f"Expected type list or None, got {type(value)}")
|
65
|
+
|
66
|
+
if any((len(item) > 200) for item in value):
|
67
|
+
raise ValueError("One or more alt_names are longer than 200 characters")
|
68
|
+
|
69
|
+
return value
|
70
|
+
|
66
71
|
owner: Optional["OrgRef"] = pydantic.Field(description="Organization that controls this organization", default=None)
|
67
|
-
subsidiaries: (
|
68
|
-
|
69
|
-
list["OrgRef"],
|
70
|
-
Annotated[
|
71
|
-
List[Annotated[str, StringConstraints(max_length=200)]],
|
72
|
-
Field(max_length=255),
|
73
|
-
],
|
74
|
-
]
|
75
|
-
| None
|
76
|
-
) = pydantic.Field(
|
77
|
-
description="Organizations controlled by this organization",
|
78
|
-
default=None,
|
72
|
+
subsidiaries: list["OrgRef"] | None = pydantic.Field(
|
73
|
+
description="Organizations controlled by this organization", default=None, max_length=255
|
79
74
|
)
|
75
|
+
|
76
|
+
@pydantic.field_validator("subsidiaries", mode="before")
|
77
|
+
def _validate_subsidiaries(cls, value: Any) -> list[str] | None:
|
78
|
+
if value is None:
|
79
|
+
return value
|
80
|
+
|
81
|
+
if not isinstance(value, list):
|
82
|
+
raise TypeError(f"Expected type list or None, got {type(value)}")
|
83
|
+
|
84
|
+
for item in value:
|
85
|
+
if len(item.name) > 200:
|
86
|
+
raise ValueError("One or more subsidiaries name are longer than 200 characters")
|
87
|
+
|
88
|
+
return value
|
89
|
+
|
80
90
|
hq_location: Location | None = pydantic.Field(
|
81
91
|
default=None,
|
82
92
|
description="Location of a place of business, preferably the corporate headquarters.",
|
@@ -111,8 +121,10 @@ class Plant(PlantRef, WithAttachmentsMixin, WithAltIdsMixin):
|
|
111
121
|
pluscode: str | None = pydantic.Field(
|
112
122
|
default=None,
|
113
123
|
description="(deprecated) Plus code (aka Open Location Code) of plant's location",
|
114
|
-
|
115
|
-
|
124
|
+
json_schema_extra={
|
125
|
+
"deprecated": "Pluscode field is deprecated. If users need a pluscode they can obtain it from "
|
126
|
+
"`id` like this: `id.spit('.', maxsplit=1)[0]`",
|
127
|
+
},
|
116
128
|
)
|
117
129
|
latitude: float | None = pydantic.Field(
|
118
130
|
default=None,
|
@@ -20,10 +20,10 @@ from typing import Any
|
|
20
20
|
import pydantic
|
21
21
|
from pydantic import ConfigDict
|
22
22
|
|
23
|
-
from openepd.model.base import BaseOpenEpdSchema
|
23
|
+
from openepd.model.base import BaseOpenEpdSchema
|
24
24
|
from openepd.model.validation.common import validate_version_compatibility, validate_version_format
|
25
25
|
from openepd.model.validation.quantity import ExternalValidationConfig, QuantityValidator
|
26
|
-
from openepd.model.versioning import WithExtVersionMixin
|
26
|
+
from openepd.model.versioning import Version, WithExtVersionMixin
|
27
27
|
|
28
28
|
|
29
29
|
class BaseOpenEpdSpec(BaseOpenEpdSchema):
|
@@ -35,6 +35,7 @@ class BaseOpenEpdSpec(BaseOpenEpdSchema):
|
|
35
35
|
class BaseOpenEpdHierarchicalSpec(BaseOpenEpdSpec, WithExtVersionMixin):
|
36
36
|
"""Base class for new specs (hierarchical, versioned)."""
|
37
37
|
|
38
|
+
# TODO: Refactor work with class-based and instance-based _EXT_VERSION
|
38
39
|
def __init__(self, **data: Any) -> None:
|
39
40
|
# ensure that all the concrete spec objects fail on creations if they dont have _EXT_VERSION declared to
|
40
41
|
# something meaningful
|
@@ -35,6 +35,8 @@ __all__ = (
|
|
35
35
|
"SteelRangeV1",
|
36
36
|
)
|
37
37
|
|
38
|
+
from typing import ClassVar
|
39
|
+
|
38
40
|
import pydantic
|
39
41
|
|
40
42
|
from openepd.model.common import RangeFloat, RangeRatioFloat
|
@@ -313,7 +315,7 @@ class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
|
|
313
315
|
Range version.
|
314
316
|
"""
|
315
317
|
|
316
|
-
_EXT_VERSION = "1.1"
|
318
|
+
_EXT_VERSION: ClassVar[str] = "1.1"
|
317
319
|
|
318
320
|
yield_tensile_str: AmountRangePressureMpa | None = pydantic.Field(
|
319
321
|
default=None,
|