openepd 4.11.2__py3-none-any.whl → 4.12.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.
- openepd/__version__.py +1 -1
- openepd/api/base_sync_client.py +2 -1
- openepd/api/errors.py +14 -1
- openepd/model/base.py +1 -1
- openepd/model/common.py +1 -1
- openepd/model/declaration.py +2 -1
- openepd/model/epd.py +19 -2
- openepd/model/generic_estimate.py +2 -1
- openepd/model/industry_epd.py +3 -1
- openepd/model/lcia.py +1 -1
- openepd/model/validation/quantity.py +29 -2
- {openepd-4.11.2.dist-info → openepd-4.12.0.dist-info}/METADATA +1 -1
- {openepd-4.11.2.dist-info → openepd-4.12.0.dist-info}/RECORD +15 -15
- {openepd-4.11.2.dist-info → openepd-4.12.0.dist-info}/LICENSE +0 -0
- {openepd-4.11.2.dist-info → openepd-4.12.0.dist-info}/WHEEL +0 -0
openepd/__version__.py
CHANGED
openepd/api/base_sync_client.py
CHANGED
@@ -151,6 +151,7 @@ class SyncHttpClient:
|
|
151
151
|
|
152
152
|
HTTP_DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %Z"
|
153
153
|
DEFAULT_RETRY_INTERVAL_SEC = 10
|
154
|
+
DEFAULT_TIMEOUT_SEC = (15, 2 * 60)
|
154
155
|
|
155
156
|
def __init__(
|
156
157
|
self,
|
@@ -183,7 +184,7 @@ class SyncHttpClient:
|
|
183
184
|
else throttle_retry_timeout.total_seconds()
|
184
185
|
)
|
185
186
|
self.user_agent = user_agent
|
186
|
-
self.timeout = timeout_sec
|
187
|
+
self.timeout = timeout_sec or self.DEFAULT_TIMEOUT_SEC
|
187
188
|
self._session: Session | None = None
|
188
189
|
self._auth: AuthBase | None = auth
|
189
190
|
self._retry_count: int = retry_count
|
openepd/api/errors.py
CHANGED
@@ -55,9 +55,22 @@ class ValidationError(ApiError):
|
|
55
55
|
super().__init__(http_status, error_summary, response, error_code)
|
56
56
|
self.validation_errors: dict[str, list[str]] = validation_errors or {}
|
57
57
|
|
58
|
+
@staticmethod
|
59
|
+
def __flatten(nested_errors: dict[str, Any], parent_key: str = "") -> dict[str, list[str]]:
|
60
|
+
result: dict[str, list[str]] = {}
|
61
|
+
for key, value in nested_errors.items():
|
62
|
+
full_key = f"{parent_key}.{key}" if parent_key else key
|
63
|
+
if isinstance(value, dict):
|
64
|
+
nested_result = ValidationError.__flatten(value, full_key)
|
65
|
+
for nested_key, nested_errors in nested_result.items(): # type: ignore[assignment]
|
66
|
+
result.setdefault(nested_key, []).extend(nested_errors)
|
67
|
+
else:
|
68
|
+
result.setdefault(full_key, []).extend(value)
|
69
|
+
return result
|
70
|
+
|
58
71
|
def __str__(self) -> str:
|
59
72
|
result: list[str] = ["Validation errors:"]
|
60
|
-
for code, errors in self.validation_errors.items():
|
73
|
+
for code, errors in self.__flatten(self.validation_errors).items(): # type: ignore[arg-type]
|
61
74
|
result.append(f"{code}:")
|
62
75
|
for e in errors:
|
63
76
|
result.append(f" {e}")
|
openepd/model/base.py
CHANGED
@@ -183,7 +183,7 @@ class RootDocument(abc.ABC, BaseOpenEpdSchema):
|
|
183
183
|
|
184
184
|
doctype: str = pyd.Field(
|
185
185
|
description='Describes the type and schema of the document. Must always always read "openEPD".',
|
186
|
-
default="
|
186
|
+
default="openEPD",
|
187
187
|
)
|
188
188
|
openepd_version: str = pyd.Field(
|
189
189
|
description="Version of the document format, related to /doctype",
|
openepd/model/common.py
CHANGED
@@ -29,7 +29,7 @@ class Amount(BaseOpenEpdSchema):
|
|
29
29
|
@pyd.root_validator
|
30
30
|
def check_qty_or_unit(cls, values: dict[str, Any]):
|
31
31
|
"""Ensure that qty or unit is provided."""
|
32
|
-
if values
|
32
|
+
if values.get("qty") is None and values.get("unit") is None:
|
33
33
|
raise ValueError("Either qty or unit must be provided.")
|
34
34
|
return values
|
35
35
|
|
openepd/model/declaration.py
CHANGED
@@ -24,6 +24,7 @@ from openepd.model.org import Org
|
|
24
24
|
from openepd.model.pcr import Pcr
|
25
25
|
from openepd.model.standard import Standard
|
26
26
|
from openepd.model.validation.common import ReferenceStr
|
27
|
+
from openepd.model.validation.quantity import AmountMass
|
27
28
|
|
28
29
|
DEVELOPER_DESCRIPTION = "The organization responsible for the underlying LCA (and subsequent summarization as EPD)."
|
29
30
|
PROGRAM_OPERATOR_DESCRIPTION = "JSON object for program operator Org"
|
@@ -61,7 +62,7 @@ class BaseDeclaration(RootDocument, abc.ABC):
|
|
61
62
|
"utilized, the declared unit shall refer to the amount of "
|
62
63
|
"product associated with the A1-A3 life cycle stage."
|
63
64
|
)
|
64
|
-
kg_per_declared_unit:
|
65
|
+
kg_per_declared_unit: AmountMass | None = pyd.Field(
|
65
66
|
default=None,
|
66
67
|
description="Mass of the product, in kilograms, per declared unit",
|
67
68
|
example=Amount(qty=12.5, unit="kg").to_serializable(exclude_unset=True),
|
openepd/model/epd.py
CHANGED
@@ -30,6 +30,7 @@ from openepd.model.declaration import (
|
|
30
30
|
from openepd.model.lcia import WithLciaMixin
|
31
31
|
from openepd.model.org import Org, Plant
|
32
32
|
from openepd.model.specs import Specs
|
33
|
+
from openepd.model.validation.quantity import AmountMass
|
33
34
|
from openepd.model.versioning import OpenEpdVersions, Version
|
34
35
|
|
35
36
|
MANUFACTURER_DESCRIPTION = (
|
@@ -53,6 +54,11 @@ class EpdPreviewV0(
|
|
53
54
|
|
54
55
|
"""
|
55
56
|
|
57
|
+
doctype: str = pyd.Field(
|
58
|
+
description='Describes the type and schema of the document. Must always always read "openEPD".',
|
59
|
+
default="openEPD",
|
60
|
+
)
|
61
|
+
|
56
62
|
product_name: str | None = pyd.Field(
|
57
63
|
max_length=200, description="The name of the product described by this EPD", example="Mix 12345AC", default=None
|
58
64
|
)
|
@@ -79,14 +85,14 @@ class EpdPreviewV0(
|
|
79
85
|
description="List of object(s) for one or more plant(s) that this declaration applies to.",
|
80
86
|
default_factory=list,
|
81
87
|
)
|
82
|
-
kg_C_per_declared_unit:
|
88
|
+
kg_C_per_declared_unit: AmountMass | None = pyd.Field(
|
83
89
|
default=None,
|
84
90
|
description="Mass of elemental carbon, per declared unit, contained in the product itself at the manufacturing "
|
85
91
|
"facility gate. Used (among other things) to check a carbon balance or calculate incineration "
|
86
92
|
"emissions. The source of carbon (e.g. biogenic) is not relevant in this field.",
|
87
93
|
example=Amount(qty=8.76, unit="kg"),
|
88
94
|
)
|
89
|
-
kg_C_biogenic_per_declared_unit:
|
95
|
+
kg_C_biogenic_per_declared_unit: AmountMass | None = pyd.Field(
|
90
96
|
default=None,
|
91
97
|
description="Mass of elemental carbon from biogenic sources, per declared unit, contained in the product "
|
92
98
|
"itself at the manufacturing facility gate. It may be presumed that any biogenic carbon content "
|
@@ -149,6 +155,17 @@ class EpdPreviewV0(
|
|
149
155
|
default_factory=list,
|
150
156
|
)
|
151
157
|
|
158
|
+
@pyd.validator("doctype")
|
159
|
+
def validate_doctype(cls, v: str | None) -> str:
|
160
|
+
"""
|
161
|
+
Handle possible mixed case options for doctype.
|
162
|
+
|
163
|
+
Required for backward compatibility as some code might have already used 'doctype: OpenEPD' instead of 'openEPD'
|
164
|
+
"""
|
165
|
+
if not v or v.lower() == "openepd":
|
166
|
+
return "openEPD"
|
167
|
+
raise ValueError("Invalid doctype")
|
168
|
+
|
152
169
|
|
153
170
|
EpdPreview = EpdPreviewV0
|
154
171
|
|
@@ -14,6 +14,7 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
from enum import StrEnum
|
17
|
+
from typing import Literal
|
17
18
|
|
18
19
|
from openepd.compat.pydantic import pyd
|
19
20
|
from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes
|
@@ -60,7 +61,7 @@ class GenericEstimatePreviewV0(
|
|
60
61
|
|
61
62
|
_FORMAT_VERSION = OpenEpdVersions.Version0.as_str()
|
62
63
|
|
63
|
-
doctype:
|
64
|
+
doctype: Literal["openGenericEstimate"] = pyd.Field(
|
64
65
|
description='Describes the type and schema of the document. Must always be "openGenericEstimate"',
|
65
66
|
default="openGenericEstimate",
|
66
67
|
)
|
openepd/model/industry_epd.py
CHANGED
@@ -13,6 +13,8 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
+
from typing import Literal
|
17
|
+
|
16
18
|
from openepd.compat.pydantic import pyd
|
17
19
|
from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes
|
18
20
|
from openepd.model.common import WithAltIdsMixin, WithAttachmentsMixin
|
@@ -51,7 +53,7 @@ class IndustryEpdPreviewV0(
|
|
51
53
|
|
52
54
|
_FORMAT_VERSION = OpenEpdVersions.Version0.as_str()
|
53
55
|
|
54
|
-
doctype:
|
56
|
+
doctype: Literal["openIndustryEpd"] = pyd.Field(
|
55
57
|
description='Describes the type and schema of the document. Must always be "openIndustryEpd"',
|
56
58
|
default="openIndustryEpd",
|
57
59
|
)
|
openepd/model/lcia.py
CHANGED
@@ -204,7 +204,7 @@ class ScopesetByNameBase(BaseOpenEpdSchema):
|
|
204
204
|
|
205
205
|
:return: set of names, for example ['gwp', 'odp]
|
206
206
|
"""
|
207
|
-
return [self.__fields__[f].alias or f for f in self.__fields_set__]
|
207
|
+
return [self.__fields__[f].alias or f for f in self.__fields_set__ if f not in ("ext",)]
|
208
208
|
|
209
209
|
def get_scopeset_by_name(self, name: str) -> ScopeSet | None:
|
210
210
|
"""
|
@@ -14,9 +14,10 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
from abc import ABC, abstractmethod
|
17
|
-
from typing import TYPE_CHECKING, Callable, ClassVar
|
17
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar
|
18
18
|
|
19
|
-
from openepd.
|
19
|
+
from openepd.compat.pydantic import pyd
|
20
|
+
from openepd.model.common import Amount, OpenEPDUnit
|
20
21
|
|
21
22
|
if TYPE_CHECKING:
|
22
23
|
from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
|
@@ -127,6 +128,32 @@ def validate_quantity_for_new_validator(max_value: str) -> Callable:
|
|
127
128
|
# todo these types should be replaced by Annotated[str, AfterValidator...] as we move completely to pydantic 2
|
128
129
|
|
129
130
|
|
131
|
+
class AmountWithDimensionality(Amount, ABC):
|
132
|
+
"""Class for dimensionality-validated amounts."""
|
133
|
+
|
134
|
+
dimensionality_unit: ClassVar[str | None] = None
|
135
|
+
|
136
|
+
# Unit for dimensionality to validate against, for example "kg"
|
137
|
+
|
138
|
+
@pyd.root_validator
|
139
|
+
def check_dimensionality_matches(cls, values: dict[str, Any]) -> dict[str, Any]:
|
140
|
+
"""Check that this amount conforms to the same dimensionality as dimensionality_unit."""
|
141
|
+
if not cls.dimensionality_unit:
|
142
|
+
return values
|
143
|
+
|
144
|
+
from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
|
145
|
+
|
146
|
+
str_repr = f"{values['qty']} {values['unit']}"
|
147
|
+
validate_unit_factory(cls.dimensionality_unit)(BaseOpenEpdHierarchicalSpec, str_repr)
|
148
|
+
return values
|
149
|
+
|
150
|
+
|
151
|
+
class AmountMass(AmountWithDimensionality):
|
152
|
+
"""Amount of mass, measured in kg, t, etc."""
|
153
|
+
|
154
|
+
dimensionality_unit = OpenEPDUnit.kg
|
155
|
+
|
156
|
+
|
130
157
|
class QuantityStr(str):
|
131
158
|
"""
|
132
159
|
Quantity string type.
|
@@ -1,10 +1,10 @@
|
|
1
1
|
openepd/__init__.py,sha256=Shkfh0Kun0YRhmRDw7LkUj2eQL3X-HnP55u2THOEALw,794
|
2
|
-
openepd/__version__.py,sha256=
|
2
|
+
openepd/__version__.py,sha256=21tmVPyiMCbot4ApEc5ODeqH2q7qPuAfknYig3smndc,639
|
3
3
|
openepd/api/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
4
4
|
openepd/api/average_dataset/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
5
5
|
openepd/api/average_dataset/generic_estimate_sync_api.py,sha256=DM7i8gugUIEAutEsakEUxFjwgtKmcytkjvNAvfhVYUs,7690
|
6
6
|
openepd/api/average_dataset/industry_epd_sync_api.py,sha256=xoicuZdclf4BTBa2ndG1K2Aog-iZd7_jOqdDPZSCLq4,6286
|
7
|
-
openepd/api/base_sync_client.py,sha256=
|
7
|
+
openepd/api/base_sync_client.py,sha256=IX-q6JdWLMqYHC-hKLXUFRPs-DZZ9Co0NRZ5pSAipAs,20992
|
8
8
|
openepd/api/category/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
9
9
|
openepd/api/category/dto.py,sha256=tDojagSwT7CTtcYq31Qe_c0P3xKKUWXKdzT5iN6odtk,850
|
10
10
|
openepd/api/category/sync_api.py,sha256=VHHOVbblZGyc4AtbsgQza00trSLuaCO6KfQw6r8vzgg,1371
|
@@ -18,7 +18,7 @@ openepd/api/dto/params.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
|
18
18
|
openepd/api/epd/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
19
19
|
openepd/api/epd/dto.py,sha256=NZ76vfUkCEkwDibQd2QCEQP5DZms_NFc9tjoP-mcw3o,4874
|
20
20
|
openepd/api/epd/sync_api.py,sha256=5wuPiM_zVciVtTvsGaaRdf7T6q2CUp7QMZCW0DAjQng,7072
|
21
|
-
openepd/api/errors.py,sha256=
|
21
|
+
openepd/api/errors.py,sha256=Iipd0QW2tbhUu_UjUbmiFWgmrPVgOFHc3uxZN-SX-g4,2868
|
22
22
|
openepd/api/pcr/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
23
23
|
openepd/api/pcr/sync_api.py,sha256=Riu77h8uLJngKpITOiXYmO7mzjAHpYskUJ6ynyfNG78,1557
|
24
24
|
openepd/api/sync_client.py,sha256=IurnhZrkBQoQIbbfon6TPuhjGpAV_CSTJNeXjIiN0QI,3105
|
@@ -33,16 +33,16 @@ openepd/compat/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,62
|
|
33
33
|
openepd/compat/compat_functional_validators.py,sha256=yz6DfWeg7knBHEN_enpCGGTLRknEsecXfpzD1FDlywY,834
|
34
34
|
openepd/compat/pydantic.py,sha256=DOjSixsylLqMtFAIARu50sGcT4VPXN_c473q_2JwZQ0,1146
|
35
35
|
openepd/model/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
36
|
-
openepd/model/base.py,sha256=
|
36
|
+
openepd/model/base.py,sha256=h3lUv5XuROAuCyJ68FZ0hkoyiqDRxXpjfxp2c0CGvOU,9044
|
37
37
|
openepd/model/category.py,sha256=IQXNGQFQmFZ_H9PRONloX_UOSf1sTMDq1rM1yz8JR0Y,1639
|
38
|
-
openepd/model/common.py,sha256=
|
39
|
-
openepd/model/declaration.py,sha256=
|
40
|
-
openepd/model/epd.py,sha256=
|
38
|
+
openepd/model/common.py,sha256=hXnz2QiQDx3Z7F4BMHlHQ2iddq3OonKrveltl-nOiEI,5468
|
39
|
+
openepd/model/declaration.py,sha256=W4bTm423_vMf8i0nsrEy9iw0fwNGOD-9yvpuRlXt-4o,8999
|
40
|
+
openepd/model/epd.py,sha256=fZJxgbpPUc8IINfrVpHG9fUWSf_XWJfMPK9lMhN0qME,9368
|
41
41
|
openepd/model/factory.py,sha256=XP7eeQNW5tqwX_4hfuEb3lK6BFQDb4KB0fSN0r8-lCU,2656
|
42
|
-
openepd/model/generic_estimate.py,sha256=
|
42
|
+
openepd/model/generic_estimate.py,sha256=bbU0cR4izSqjZcfxUHNbdO4pllqqd8OaUFikrEgCFoA,3992
|
43
43
|
openepd/model/geography.py,sha256=eCt15zXKDtiteNwXQ675cFwBXQqSpiGpIqwDo4nkOek,42091
|
44
|
-
openepd/model/industry_epd.py,sha256=
|
45
|
-
openepd/model/lcia.py,sha256=
|
44
|
+
openepd/model/industry_epd.py,sha256=rgXhCUDAgzZ9eGio7ExqE3ymP3zTXnrrwcIDvg5YP1A,3285
|
45
|
+
openepd/model/lcia.py,sha256=pVh4LbZgzDuLuM3epdCNLlAp5B0BnUrwggaaDiuPccM,18848
|
46
46
|
openepd/model/org.py,sha256=FHcYh2WOOQrCMyzm0Ow-iP79jMTBPcneidjH6NXIklA,3760
|
47
47
|
openepd/model/pcr.py,sha256=SwqLWMj9k_jqIzxz5mh6ttqvtLCspKSpywF5YTBOMsA,5397
|
48
48
|
openepd/model/specs/README.md,sha256=W5LSMpZuW5x36cKS4HRfeFsClsRf8J9yHMMICghdc0s,862
|
@@ -93,11 +93,11 @@ openepd/model/standard.py,sha256=QhGpWN3U27fDcS0Yy1Dk8ElJfD0etet6i_PzoTD6B48,131
|
|
93
93
|
openepd/model/validation/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
94
94
|
openepd/model/validation/common.py,sha256=FLYqK8gYFagx08LCkS0jy3qo4-Zq9VAv5i8ZwF2svkc,2435
|
95
95
|
openepd/model/validation/numbers.py,sha256=tgirqrDGgrSo6APGlW1ozNuVV8mJz_4HCAXS2OUENq0,888
|
96
|
-
openepd/model/validation/quantity.py,sha256=
|
96
|
+
openepd/model/validation/quantity.py,sha256=h4AXXMijFtJRFUb5bi_iRzK2v7v1CtggLi7CyK5bA4s,8349
|
97
97
|
openepd/model/versioning.py,sha256=R_zm6rCrgF3vlJQYbpyWhirdS_Oek16cv_mvZmpuE8I,4473
|
98
98
|
openepd/patch_pydantic.py,sha256=xrkzblatmU9HBzukWkp1cPq9ZSuohoz1p0pQqVKSlKs,4122
|
99
99
|
openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
100
|
-
openepd-4.
|
101
|
-
openepd-4.
|
102
|
-
openepd-4.
|
103
|
-
openepd-4.
|
100
|
+
openepd-4.12.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
101
|
+
openepd-4.12.0.dist-info/METADATA,sha256=1mNyCyep7uh_aBL0WoQIM303D5gIkA30Upv_E3pHSx4,8705
|
102
|
+
openepd-4.12.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
103
|
+
openepd-4.12.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|