openepd 1.4.0__tar.gz → 1.6.0__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-1.4.0 → openepd-1.6.0}/PKG-INFO +1 -1
- {openepd-1.4.0 → openepd-1.6.0}/pyproject.toml +2 -2
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/__version__.py +1 -1
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/base.py +97 -13
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/common.py +16 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/epd.py +37 -18
- openepd-1.6.0/src/openepd/model/factory.py +48 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/org.py +8 -2
- openepd-1.6.0/src/openepd/model/specs/README.md +19 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/specs/__init__.py +8 -2
- openepd-1.6.0/src/openepd/model/specs/base.py +60 -0
- openepd-1.6.0/src/openepd/model/specs/concrete.py +316 -0
- openepd-1.6.0/src/openepd/model/specs/steel.py +146 -0
- openepd-1.6.0/src/openepd/model/validation/__init__.py +19 -0
- openepd-1.6.0/src/openepd/model/validation/common.py +52 -0
- openepd-1.6.0/src/openepd/model/validation/numbers.py +66 -0
- openepd-1.6.0/src/openepd/model/versioning.py +129 -0
- openepd-1.4.0/src/openepd/model/specs/concrete.py +0 -150
- {openepd-1.4.0 → openepd-1.6.0}/LICENSE +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/README.md +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/base_sync_client.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/category/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/category/dto.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/category/sync_api.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/common.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/dto/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/dto/base.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/dto/common.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/dto/meta.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/dto/mf.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/dto/params.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/epd/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/epd/dto.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/epd/sync_api.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/errors.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/pcr/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/pcr/dto.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/pcr/sync_api.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/sync_client.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/api/test/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/bundle/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/bundle/base.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/bundle/model.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/bundle/reader.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/bundle/writer.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/__init__.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/category.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/lcia.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/pcr.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/model/standard.py +0 -0
- {openepd-1.4.0 → openepd-1.6.0}/src/openepd/py.typed +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "openepd"
|
3
|
-
version = "1.
|
3
|
+
version = "1.6.0"
|
4
4
|
license = "Apache-2.0"
|
5
5
|
description = "Python library to work with OpenEPD format"
|
6
6
|
authors = ["C-Change Labs <support@c-change-labs.com>"]
|
@@ -58,7 +58,7 @@ api_client = ["requests"]
|
|
58
58
|
|
59
59
|
|
60
60
|
[tool.commitizen]
|
61
|
-
version = "1.
|
61
|
+
version = "1.6.0"
|
62
62
|
bump_version = "bump: version $current_version → $new_version"
|
63
63
|
update_changelog_on_bump = true
|
64
64
|
pre_bump_hooks = []
|
@@ -18,32 +18,62 @@
|
|
18
18
|
# Find out more at www.BuildingTransparency.org
|
19
19
|
#
|
20
20
|
import abc
|
21
|
+
from enum import StrEnum
|
21
22
|
import json
|
22
|
-
from typing import Any, Optional, Type, TypeVar
|
23
|
+
from typing import Any, Callable, Generic, Optional, Type, TypeVar
|
23
24
|
|
24
|
-
import pydantic
|
25
|
+
import pydantic as pyd
|
25
26
|
from pydantic.generics import GenericModel
|
26
27
|
|
27
|
-
|
28
|
+
from openepd.model.validation.common import validate_version_compatibility, validate_version_format
|
29
|
+
from openepd.model.versioning import OpenEpdVersions, Version
|
30
|
+
|
31
|
+
AnySerializable = int | str | bool | float | list | dict | pyd.BaseModel | None
|
28
32
|
TAnySerializable = TypeVar("TAnySerializable", bound=AnySerializable)
|
29
33
|
|
34
|
+
OPENEPD_VERSION_FIELD = "openepd_version"
|
35
|
+
"""Field name for the openEPD format version."""
|
36
|
+
|
37
|
+
|
38
|
+
class OpenEpdDoctypes(StrEnum):
|
39
|
+
"""Enum of supported openEPD document types."""
|
40
|
+
|
41
|
+
Epd = "openEPD"
|
42
|
+
|
43
|
+
|
44
|
+
def modify_pydantic_schema(schema_dict: dict, cls: type) -> dict:
|
45
|
+
"""
|
46
|
+
Modify the schema dictionary to add the required fields.
|
47
|
+
|
48
|
+
:param schema_dict: schema dictionary
|
49
|
+
:param cls: class for which the schema was generated
|
50
|
+
:return: modified schema dictionary
|
51
|
+
"""
|
52
|
+
ext = schema_dict.get("properties", {}).get("ext")
|
53
|
+
# move to bottom
|
54
|
+
if ext is not None:
|
55
|
+
del schema_dict["properties"]["ext"]
|
56
|
+
schema_dict["properties"]["ext"] = ext
|
57
|
+
return schema_dict
|
30
58
|
|
31
|
-
|
59
|
+
|
60
|
+
class BaseOpenEpdSchema(pyd.BaseModel):
|
32
61
|
"""Base class for all OpenEPD models."""
|
33
62
|
|
34
|
-
ext: dict[str, AnySerializable] | None =
|
63
|
+
ext: dict[str, AnySerializable] | None = pyd.Field(alias="ext", default=None)
|
35
64
|
|
36
65
|
class Config:
|
37
66
|
allow_mutation = True
|
38
67
|
validate_assignment = False
|
39
68
|
allow_population_by_field_name = True
|
40
69
|
use_enum_values = True
|
70
|
+
schema_extra: Callable | dict = modify_pydantic_schema
|
41
71
|
|
42
72
|
def to_serializable(self, *args, **kwargs) -> dict[str, Any]:
|
43
73
|
"""
|
44
74
|
Return a serializable dict representation of the DTO.
|
45
75
|
|
46
|
-
It expects the same arguments as the
|
76
|
+
It expects the same arguments as the pyd.BaseModel.json() method.
|
47
77
|
"""
|
48
78
|
return json.loads(self.json(*args, **kwargs))
|
49
79
|
|
@@ -83,7 +113,7 @@ class BaseOpenEpdSchema(pydantic.BaseModel):
|
|
83
113
|
value = self.get_ext_field(key, default)
|
84
114
|
if value is None:
|
85
115
|
return None # type: ignore
|
86
|
-
if issubclass(target_type,
|
116
|
+
if issubclass(target_type, pyd.BaseModel) and isinstance(value, dict):
|
87
117
|
return target_type.construct(**value) # type: ignore
|
88
118
|
elif isinstance(value, target_type):
|
89
119
|
return value
|
@@ -129,12 +159,6 @@ class BaseOpenEpdGenericSchema(GenericModel, BaseOpenEpdSchema):
|
|
129
159
|
pass
|
130
160
|
|
131
161
|
|
132
|
-
class BaseOpenEpdSpec(BaseOpenEpdSchema):
|
133
|
-
"""Base class for all OpenEPD specs."""
|
134
|
-
|
135
|
-
pass
|
136
|
-
|
137
|
-
|
138
162
|
class OpenEpdExtension(BaseOpenEpdSchema, metaclass=abc.ABCMeta):
|
139
163
|
"""Base class for OpenEPD extension models."""
|
140
164
|
|
@@ -148,3 +172,63 @@ class OpenEpdExtension(BaseOpenEpdSchema, metaclass=abc.ABCMeta):
|
|
148
172
|
TOpenEpdExtension = TypeVar("TOpenEpdExtension", bound=OpenEpdExtension)
|
149
173
|
TOpenEpdObject = TypeVar("TOpenEpdObject", bound=BaseOpenEpdSchema)
|
150
174
|
TOpenEpdObjectClass = TypeVar("TOpenEpdObjectClass", bound=Type[BaseOpenEpdSchema])
|
175
|
+
|
176
|
+
|
177
|
+
class RootDocument(abc.ABC, BaseOpenEpdSchema):
|
178
|
+
"""Base class for all objects representing openEPD root element. E.g. Epd, IndustryEpd, GenericEstimate, etc."""
|
179
|
+
|
180
|
+
_FORMAT_VERSION: str
|
181
|
+
"""Version of this document format. Must be defined in the concrete class."""
|
182
|
+
|
183
|
+
doctype: str = pyd.Field(
|
184
|
+
description='Describes the type and schema of the document. Must always always read "openEPD".',
|
185
|
+
default="OpenEPD",
|
186
|
+
)
|
187
|
+
openepd_version: str = pyd.Field(
|
188
|
+
description="Version of the document format, related to /doctype",
|
189
|
+
default=OpenEpdVersions.get_current().as_str(),
|
190
|
+
)
|
191
|
+
|
192
|
+
_version_format_validator = pyd.validator(OPENEPD_VERSION_FIELD, allow_reuse=True, check_fields=False)(
|
193
|
+
validate_version_format
|
194
|
+
)
|
195
|
+
_version_major_match_validator = pyd.validator(OPENEPD_VERSION_FIELD, allow_reuse=True, check_fields=False)(
|
196
|
+
validate_version_compatibility("_FORMAT_VERSION")
|
197
|
+
)
|
198
|
+
|
199
|
+
|
200
|
+
TRootDocument = TypeVar("TRootDocument", bound=RootDocument)
|
201
|
+
|
202
|
+
|
203
|
+
class BaseDocumentFactory(Generic[TRootDocument]):
|
204
|
+
"""
|
205
|
+
Base class for document factories.
|
206
|
+
|
207
|
+
Extend it to create a factory for a specific document type e.g. for industry epd, epd, etc.
|
208
|
+
"""
|
209
|
+
|
210
|
+
DOCTYPE_CONSTRAINT: str = ""
|
211
|
+
VERSION_MAP: dict[Version, type[TRootDocument]] = {}
|
212
|
+
|
213
|
+
@classmethod
|
214
|
+
def from_dict(cls, data: dict) -> TRootDocument:
|
215
|
+
"""Create a document from a dictionary."""
|
216
|
+
doctype: str | None = data.get("doctype")
|
217
|
+
if doctype is None:
|
218
|
+
raise ValueError("Doctype not found in the data.")
|
219
|
+
if doctype.lower() != cls.DOCTYPE_CONSTRAINT.lower():
|
220
|
+
raise ValueError(
|
221
|
+
f"Document type {doctype} not supported. This factory supports {cls.DOCTYPE_CONSTRAINT} only."
|
222
|
+
)
|
223
|
+
version = Version.parse_version(data.get(OPENEPD_VERSION_FIELD, ""))
|
224
|
+
for x, doc_cls in cls.VERSION_MAP.items():
|
225
|
+
if x.major == version.major:
|
226
|
+
if version.minor <= x.minor:
|
227
|
+
return doc_cls(**data)
|
228
|
+
else:
|
229
|
+
raise ValueError(
|
230
|
+
f"Unsupported version: {version}. "
|
231
|
+
f"The highest supported version from branch {x.major}.x is {x}"
|
232
|
+
)
|
233
|
+
supported_versions = ", ".join(f"{v.major}.x" for v in cls.VERSION_MAP.keys())
|
234
|
+
raise ValueError(f"Version {version} is not supported. Supported versions are: {supported_versions}")
|
@@ -17,6 +17,7 @@
|
|
17
17
|
# Charles Pankow Foundation, Microsoft Sustainability Fund, Interface, MKA Foundation, and others.
|
18
18
|
# Find out more at www.BuildingTransparency.org
|
19
19
|
#
|
20
|
+
from enum import StrEnum
|
20
21
|
from typing import Annotated, Any
|
21
22
|
|
22
23
|
import pydantic as pyd
|
@@ -137,3 +138,18 @@ class WithAltIdsMixin(BaseModel):
|
|
137
138
|
"""Set an alt_id if value is not None."""
|
138
139
|
if value is not None:
|
139
140
|
self.set_alt_id(domain_name, value)
|
141
|
+
|
142
|
+
|
143
|
+
class OpenEPDUnit(StrEnum):
|
144
|
+
"""OpenEPD allowed units."""
|
145
|
+
|
146
|
+
kg = "kg"
|
147
|
+
m2 = "m2"
|
148
|
+
m = "m"
|
149
|
+
M2_RSI = "m2 * RSI"
|
150
|
+
MJ = "MJ"
|
151
|
+
t_km = "t * km"
|
152
|
+
MPa = "MPa"
|
153
|
+
item = "item"
|
154
|
+
W = "W"
|
155
|
+
use = "use"
|
@@ -22,18 +22,31 @@ from typing import Annotated
|
|
22
22
|
|
23
23
|
import pydantic as pyd
|
24
24
|
|
25
|
-
from openepd.model.base import
|
25
|
+
from openepd.model.base import BaseDocumentFactory, RootDocument
|
26
26
|
from openepd.model.common import Amount, Ingredient, WithAltIdsMixin, WithAttachmentsMixin
|
27
27
|
from openepd.model.lcia import Impacts, OutputFlowSet, ResourceUseSet
|
28
28
|
from openepd.model.org import Org, Plant
|
29
29
|
from openepd.model.pcr import Pcr
|
30
30
|
from openepd.model.specs import Specs
|
31
31
|
from openepd.model.standard import Standard
|
32
|
+
from openepd.model.versioning import OpenEpdVersions, Version
|
32
33
|
|
33
34
|
|
34
|
-
class
|
35
|
+
class BaseEpd(RootDocument):
|
36
|
+
"""
|
37
|
+
Base class for EPD documents.
|
38
|
+
|
39
|
+
This class should not be used directly. Use Epd or EpdVx instead.
|
40
|
+
"""
|
41
|
+
|
42
|
+
pass
|
43
|
+
|
44
|
+
|
45
|
+
class EpdV0(WithAttachmentsMixin, WithAltIdsMixin, BaseEpd):
|
35
46
|
"""Represent an EPD."""
|
36
47
|
|
48
|
+
_FORMAT_VERSION = OpenEpdVersions.Version0.as_str()
|
49
|
+
|
37
50
|
# TODO: Add validator for open-xpd-uuid on this field
|
38
51
|
id: str | None = pyd.Field(
|
39
52
|
description="The unique ID for this EPD. To ensure global uniqueness, should be registered at "
|
@@ -41,10 +54,6 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
|
|
41
54
|
example="1u7zsed8",
|
42
55
|
default=None,
|
43
56
|
)
|
44
|
-
doctype: str = pyd.Field(
|
45
|
-
description='Describes the type and schema of the document. Must always always read "openEPD".',
|
46
|
-
default="OpenEPD",
|
47
|
-
)
|
48
57
|
product_name: str | None = pyd.Field(
|
49
58
|
max_length=200, description="The name of the product described by this EPD", example="Mix 12345AC"
|
50
59
|
)
|
@@ -53,7 +62,7 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
|
|
53
62
|
)
|
54
63
|
product_description: str | None = pyd.Field(
|
55
64
|
max_length=2000,
|
56
|
-
description="1-paragraph description of product.
|
65
|
+
description="1-paragraph description of product. Supports plain text or github flavored markdown.",
|
57
66
|
)
|
58
67
|
# TODO: add product_alt_names? E.g. ILCD has a list of synonymous names
|
59
68
|
product_classes: dict[str, str | list[str]] = pyd.Field(
|
@@ -234,28 +243,28 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
|
|
234
243
|
)
|
235
244
|
lca_discussion: str | None = pyd.Field(
|
236
245
|
max_length=20000,
|
237
|
-
description="""A rich text description containing information for experts reviewing the EPD contents.
|
238
|
-
Text descriptions required by ISO 14025, ISO 21930, EN 15804,, relevant PCRs, or program instructions and which do not
|
239
|
-
have specific openEPD fields should be entered here. This field may be large, and may contain multiple sections
|
246
|
+
description="""A rich text description containing information for experts reviewing the EPD contents.
|
247
|
+
Text descriptions required by ISO 14025, ISO 21930, EN 15804,, relevant PCRs, or program instructions and which do not
|
248
|
+
have specific openEPD fields should be entered here. This field may be large, and may contain multiple sections
|
240
249
|
separated by github flavored markdown formatting.""",
|
241
250
|
example="""# Packaging
|
242
251
|
|
243
|
-
Information on product-specific packaging: type, composition and possible reuse of packaging materials (paper,
|
244
|
-
strapping, pallets, foils, drums, etc.) shall be included in this Section. The EPD shall describe specific packaging
|
245
|
-
scenario assumptions, including disposition pathways for each packaging material by reuse, recycling, or landfill
|
252
|
+
Information on product-specific packaging: type, composition and possible reuse of packaging materials (paper,
|
253
|
+
strapping, pallets, foils, drums, etc.) shall be included in this Section. The EPD shall describe specific packaging
|
254
|
+
scenario assumptions, including disposition pathways for each packaging material by reuse, recycling, or landfill
|
246
255
|
disposal based on packaging type.*
|
247
256
|
|
248
257
|
# Product Installation
|
249
258
|
|
250
|
-
A description of the type of processing, machinery, tools, dust extraction equipment, auxiliary materials, etc.
|
251
|
-
to be used during installation shall be included. Information on industrial and environmental protection may be
|
252
|
-
included in this section. Any waste treatment included within the system boundary of installation waste should be
|
259
|
+
A description of the type of processing, machinery, tools, dust extraction equipment, auxiliary materials, etc.
|
260
|
+
to be used during installation shall be included. Information on industrial and environmental protection may be
|
261
|
+
included in this section. Any waste treatment included within the system boundary of installation waste should be
|
253
262
|
specified.
|
254
263
|
|
255
264
|
# Use Conditions
|
256
265
|
|
257
|
-
Use-stage environmental impacts of flooring products during building operations depend on product cleaning assumptions.
|
258
|
-
Information on cleaning frequency and cleaning products shall be provided based on the manufacturer’s recommendations.
|
266
|
+
Use-stage environmental impacts of flooring products during building operations depend on product cleaning assumptions.
|
267
|
+
Information on cleaning frequency and cleaning products shall be provided based on the manufacturer’s recommendations.
|
259
268
|
In the absence of primary data, cleaning assumptions shall be documented.
|
260
269
|
""",
|
261
270
|
)
|
@@ -278,3 +287,13 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
|
|
278
287
|
if v is None:
|
279
288
|
return []
|
280
289
|
return v
|
290
|
+
|
291
|
+
|
292
|
+
Epd = EpdV0
|
293
|
+
|
294
|
+
|
295
|
+
class EpdFactory(BaseDocumentFactory[BaseEpd]):
|
296
|
+
"""Factory for EPD objects."""
|
297
|
+
|
298
|
+
DOCTYPE_CONSTRAINT = "openEPD"
|
299
|
+
VERSION_MAP: dict[Version, type[BaseEpd]] = {OpenEpdVersions.Version0: EpdV0}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2024 by C Change Labs Inc. www.c-change-labs.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
# This software was developed with support from the Skanska USA,
|
17
|
+
# Charles Pankow Foundation, Microsoft Sustainability Fund, Interface, MKA Foundation, and others.
|
18
|
+
# Find out more at www.BuildingTransparency.org
|
19
|
+
#
|
20
|
+
from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes, RootDocument
|
21
|
+
from openepd.model.epd import EpdFactory
|
22
|
+
|
23
|
+
|
24
|
+
class DocumentFactory:
|
25
|
+
"""A factory for creating documents regardless of the type."""
|
26
|
+
|
27
|
+
DOCTYPE_TO_FACTORY: dict[str, type[BaseDocumentFactory]] = {
|
28
|
+
OpenEpdDoctypes.Epd: EpdFactory,
|
29
|
+
}
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def from_dict(cls, data: dict) -> RootDocument:
|
33
|
+
"""
|
34
|
+
Create a document from the dictionary.
|
35
|
+
|
36
|
+
Type of the document will be recognized from the `doctype` field.
|
37
|
+
:raise ValueError: if the document type is not specified or not supported.
|
38
|
+
"""
|
39
|
+
doctype = data.get("doctype")
|
40
|
+
if doctype is None:
|
41
|
+
raise ValueError("The document type is not specified.")
|
42
|
+
factory = cls.DOCTYPE_TO_FACTORY.get(doctype)
|
43
|
+
if factory is None:
|
44
|
+
raise ValueError(
|
45
|
+
f"The document of type `{doctype}` is not supported. Supported documents are: "
|
46
|
+
+ ", ".join(cls.DOCTYPE_TO_FACTORY.keys())
|
47
|
+
)
|
48
|
+
return factory.from_dict(data)
|
@@ -25,8 +25,8 @@ from openepd.model.base import BaseOpenEpdSchema
|
|
25
25
|
from openepd.model.common import Location, WithAltIdsMixin, WithAttachmentsMixin
|
26
26
|
|
27
27
|
|
28
|
-
class
|
29
|
-
"""
|
28
|
+
class OrgRef(BaseOpenEpdSchema):
|
29
|
+
"""Represents Organisation with minimal data."""
|
30
30
|
|
31
31
|
web_domain: str | None = pyd.Field(
|
32
32
|
description="A web domain owned by organization. Typically is the org's home website address", default=None
|
@@ -37,12 +37,18 @@ class Org(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
|
|
37
37
|
example="C Change Labs",
|
38
38
|
default=None,
|
39
39
|
)
|
40
|
+
|
41
|
+
|
42
|
+
class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
|
43
|
+
"""Represent an organization."""
|
44
|
+
|
40
45
|
alt_names: Annotated[list[str], pyd.conlist(pyd.constr(max_length=200), max_items=255)] | None = pyd.Field(
|
41
46
|
description="List of other names for organization",
|
42
47
|
example=["C-Change Labs", "C-Change Labs inc."],
|
43
48
|
default=None,
|
44
49
|
)
|
45
50
|
# TODO: NEW field, not in the spec
|
51
|
+
|
46
52
|
owner: Optional["Org"] = pyd.Field(description="Organization that controls this organization", default=None)
|
47
53
|
subsidiaries: Annotated[list[str], pyd.conlist(pyd.constr(max_length=200), max_items=255)] | None = pyd.Field(
|
48
54
|
description="Organizations controlled by this organization",
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Material Extensions
|
2
|
+
|
3
|
+
This package contains openEPD material extensions. They are used to represent the material properties of the openEPD
|
4
|
+
materials, and are a more dynamic, frequently changing part of the standard.
|
5
|
+
|
6
|
+
## Versioning
|
7
|
+
|
8
|
+
Extensions are versioned separately from the openEPD standard or openEPD API.
|
9
|
+
|
10
|
+
Each material extension is named after the corresponding EC3 product class, and is located in the relevant place
|
11
|
+
in the specs tree. For example, `RebarSteel` is nested under `Steel`.
|
12
|
+
|
13
|
+
Extensions are versioned as Major.Minor, for example "2.4".
|
14
|
+
|
15
|
+
Rules:
|
16
|
+
1. Minor versions for the same major version should be backwards compatible.
|
17
|
+
2. Major versions are not compatible between themselves.
|
18
|
+
3. Pydantic models representing versions are named in a pattern SpecNameV1, where 1 is major version and SpecName is
|
19
|
+
the name of the material extension.
|
@@ -17,13 +17,19 @@
|
|
17
17
|
# Charles Pankow Foundation, Microsoft Sustainability Fund, Interface, MKA Foundation, and others.
|
18
18
|
# Find out more at www.BuildingTransparency.org
|
19
19
|
#
|
20
|
+
|
20
21
|
import pydantic as pyd
|
21
22
|
|
22
23
|
from openepd.model.base import BaseOpenEpdSchema
|
23
|
-
from openepd.model.specs
|
24
|
+
from openepd.model.specs import concrete, steel
|
24
25
|
|
25
26
|
|
26
27
|
class Specs(BaseOpenEpdSchema):
|
27
28
|
"""Material specific specs."""
|
28
29
|
|
29
|
-
cmu: CmuSpec | None = pyd.Field(default=None, description="Concrete Masonry Unit-specific (CMU) specs")
|
30
|
+
cmu: concrete.CmuSpec | None = pyd.Field(default=None, description="Concrete Masonry Unit-specific (CMU) specs")
|
31
|
+
CMU: concrete.CmuSpec | None = pyd.Field(default=None, description="Concrete Masonry Unit-specific (CMU) specs")
|
32
|
+
Steel: steel.SteelV1 | None = pyd.Field(default=None, title="SteelV1", description="Steel-specific specs")
|
33
|
+
Concrete: concrete.ConcreteV1 | None = pyd.Field(
|
34
|
+
default=None, title="ConcreteV1", description="Concrete-specific specs"
|
35
|
+
)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2024 by C Change Labs Inc. www.c-change-labs.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
# This software was developed with support from the Skanska USA,
|
17
|
+
# Charles Pankow Foundation, Microsoft Sustainability Fund, Interface, MKA Foundation, and others.
|
18
|
+
# Find out more at www.BuildingTransparency.org
|
19
|
+
#
|
20
|
+
from typing import Any
|
21
|
+
|
22
|
+
import pydantic as pyd
|
23
|
+
|
24
|
+
from openepd.model.base import BaseOpenEpdSchema, Version
|
25
|
+
from openepd.model.validation.common import validate_version_compatibility, validate_version_format
|
26
|
+
from openepd.model.validation.numbers import QuantityValidator
|
27
|
+
from openepd.model.versioning import WithExtVersionMixin
|
28
|
+
|
29
|
+
|
30
|
+
class BaseOpenEpdSpec(BaseOpenEpdSchema):
|
31
|
+
"""Base class for all OpenEPD specs."""
|
32
|
+
|
33
|
+
class Config:
|
34
|
+
use_enum_values = False # we need to store enums as strings and not values
|
35
|
+
|
36
|
+
|
37
|
+
class BaseOpenEpdHierarchicalSpec(BaseOpenEpdSpec, WithExtVersionMixin):
|
38
|
+
"""Base class for new specs (hierarchical, versioned)."""
|
39
|
+
|
40
|
+
_QUANTITY_VALIDATOR: QuantityValidator | None = None
|
41
|
+
|
42
|
+
def __init__(self, **data: Any) -> None:
|
43
|
+
# ensure that all the concrete spec objects fail on creations if they dont have _EXT_VERSION declared to
|
44
|
+
# something meaningful
|
45
|
+
if not hasattr(self, "_EXT_VERSION") or self._EXT_VERSION is None:
|
46
|
+
raise ValueError(f"Class {self.__class__} must declare an extension version")
|
47
|
+
Version.parse_version(self._EXT_VERSION) # validate format correctness
|
48
|
+
super().__init__(**{"ext_version": self._EXT_VERSION, **data})
|
49
|
+
|
50
|
+
_version_format_validator = pyd.validator("ext_version", allow_reuse=True, check_fields=False)(
|
51
|
+
validate_version_format
|
52
|
+
)
|
53
|
+
_version_major_match_validator = pyd.validator("ext_version", allow_reuse=True, check_fields=False)(
|
54
|
+
validate_version_compatibility("_EXT_VERSION")
|
55
|
+
)
|
56
|
+
|
57
|
+
|
58
|
+
def setup_external_validators(quantity_validator: QuantityValidator):
|
59
|
+
"""Set the implementation unit validator for specs."""
|
60
|
+
BaseOpenEpdHierarchicalSpec._QUANTITY_VALIDATOR = quantity_validator
|