openepd 2.0.0__py3-none-any.whl → 3.0.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.
Files changed (59) hide show
  1. openepd/__init__.py +1 -1
  2. openepd/__version__.py +2 -2
  3. openepd/api/__init__.py +19 -0
  4. openepd/api/base_sync_client.py +550 -0
  5. openepd/api/category/__init__.py +19 -0
  6. openepd/api/category/dto.py +25 -0
  7. openepd/api/category/sync_api.py +44 -0
  8. openepd/api/common.py +239 -0
  9. openepd/api/dto/__init__.py +19 -0
  10. openepd/api/dto/base.py +41 -0
  11. openepd/api/dto/common.py +115 -0
  12. openepd/api/dto/meta.py +69 -0
  13. openepd/api/dto/mf.py +59 -0
  14. openepd/api/dto/params.py +19 -0
  15. openepd/api/epd/__init__.py +19 -0
  16. openepd/api/epd/dto.py +121 -0
  17. openepd/api/epd/sync_api.py +105 -0
  18. openepd/api/errors.py +86 -0
  19. openepd/api/pcr/__init__.py +19 -0
  20. openepd/api/pcr/dto.py +41 -0
  21. openepd/api/pcr/sync_api.py +49 -0
  22. openepd/api/sync_client.py +67 -0
  23. openepd/api/test/__init__.py +19 -0
  24. openepd/bundle/__init__.py +1 -1
  25. openepd/bundle/base.py +1 -1
  26. openepd/bundle/model.py +5 -6
  27. openepd/bundle/reader.py +5 -5
  28. openepd/bundle/writer.py +5 -4
  29. openepd/compat/__init__.py +19 -0
  30. openepd/compat/pydantic.py +29 -0
  31. openepd/model/__init__.py +1 -1
  32. openepd/model/base.py +114 -15
  33. openepd/model/category.py +39 -0
  34. openepd/model/common.py +33 -25
  35. openepd/model/epd.py +97 -78
  36. openepd/model/factory.py +48 -0
  37. openepd/model/lcia.py +24 -13
  38. openepd/model/org.py +28 -18
  39. openepd/model/pcr.py +42 -14
  40. openepd/model/specs/README.md +19 -0
  41. openepd/model/specs/__init__.py +20 -4
  42. openepd/model/specs/aluminium.py +67 -0
  43. openepd/model/specs/asphalt.py +87 -0
  44. openepd/model/specs/base.py +60 -0
  45. openepd/model/specs/concrete.py +453 -23
  46. openepd/model/specs/glass.py +404 -0
  47. openepd/model/specs/steel.py +193 -0
  48. openepd/model/specs/wood.py +130 -0
  49. openepd/model/standard.py +2 -3
  50. openepd/model/validation/__init__.py +19 -0
  51. openepd/model/validation/common.py +59 -0
  52. openepd/model/validation/numbers.py +26 -0
  53. openepd/model/validation/quantity.py +131 -0
  54. openepd/model/versioning.py +129 -0
  55. {openepd-2.0.0.dist-info → openepd-3.0.0.dist-info}/METADATA +36 -5
  56. openepd-3.0.0.dist-info/RECORD +59 -0
  57. openepd-2.0.0.dist-info/RECORD +0 -22
  58. {openepd-2.0.0.dist-info → openepd-3.0.0.dist-info}/LICENSE +0 -0
  59. {openepd-2.0.0.dist-info → openepd-3.0.0.dist-info}/WHEEL +0 -0
openepd/model/pcr.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright 2023 by C Change Labs Inc. www.c-change-labs.com
2
+ # Copyright 2024 by C Change Labs Inc. www.c-change-labs.com
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -18,65 +18,93 @@
18
18
  # Find out more at www.BuildingTransparency.org
19
19
  #
20
20
  import datetime
21
- from typing import Optional
22
-
23
- import pydantic as pyd
21
+ from enum import StrEnum
22
+ from typing import Annotated, Optional
24
23
 
24
+ from openepd.compat.pydantic import pyd
25
25
  from openepd.model.base import BaseOpenEpdSchema
26
26
  from openepd.model.common import WithAltIdsMixin, WithAttachmentsMixin
27
27
  from openepd.model.org import Org
28
28
 
29
29
 
30
+ class PcrStatus(StrEnum):
31
+ """Status of a PCR."""
32
+
33
+ InDevelopment = "InDevelopment"
34
+ Published = "Published"
35
+ NonPublic = "NonPublic"
36
+ Expired = "Expired"
37
+ Sunset = "Sunset"
38
+
39
+
30
40
  class Pcr(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
31
41
  """Represent a PCR (Product Category Rules)."""
32
42
 
33
43
  id: str | None = pyd.Field(
34
44
  description="The unique ID for this PCR. To ensure global uniqueness, should be registered "
35
45
  "at open-xpd-uuid.cqd.io/register or a coordinating registry.",
46
+ example="ec3xpgq2",
36
47
  default=None,
37
- json_schema_extra={"example": "ec3xpgq2"},
38
48
  )
39
49
  issuer: Org | None = pyd.Field(description="Organization issuing this PCR", default=None)
40
50
  issuer_doc_id: str | None = pyd.Field(
41
51
  max_length=40,
42
52
  default=None,
43
53
  description="Document ID or code created by issuer",
44
- json_schema_extra=dict(example="c-PCR-003"),
54
+ example="c-PCR-003",
45
55
  )
46
56
  name: str | None = pyd.Field(
47
57
  max_length=200,
48
58
  default=None,
49
59
  description="Full document name as listed in source document",
50
- json_schema_extra=dict(example="c-PCR-003 Concrete and concrete elements (EN 16757)"),
60
+ example="c-PCR-003 Concrete and concrete elements (EN 16757)",
51
61
  )
52
62
  short_name: str | None = pyd.Field(
53
- max_length=40,
54
63
  default=None,
55
64
  description="A shortened name without boilerplate text.",
56
- json_schema_extra=dict(example="Concrete and Concrete Elements"),
65
+ example="Concrete and Concrete Elements",
57
66
  )
58
67
  version: str | None = pyd.Field(
59
68
  description="Document version, as expressed in document.",
69
+ example="1.0.2",
60
70
  default=None,
61
- json_schema_extra=dict(example="1.0.2"),
62
71
  )
63
- date_of_issue: datetime.date | None = pyd.Field(
72
+ date_of_issue: datetime.datetime | None = pyd.Field(
73
+ example=datetime.date(day=11, month=2, year=2022),
64
74
  default=None,
65
75
  description="First day on which the document is valid",
66
- json_schema_extra=dict(example=datetime.date(day=11, month=2, year=2022)),
67
76
  )
68
- valid_until: datetime.date | None = pyd.Field(
77
+ valid_until: datetime.datetime | None = pyd.Field(
78
+ example=datetime.date(day=11, month=2, year=2024),
69
79
  default=None,
70
80
  description="Last day on which the document is valid",
71
- json_schema_extra=dict(example=datetime.date(day=11, month=2, year=2024)),
72
81
  )
82
+ doc: str | None = pyd.Field(default=None, description="URL to original document, preferably directly to a PDF.")
73
83
  parent: Optional["Pcr"] = pyd.Field(
74
84
  description="The parent PCR, base PCR, `Part A` PCR",
75
85
  default=None,
76
86
  )
87
+ status: PcrStatus | None = pyd.Field(
88
+ default=None,
89
+ description="The current release status of this PCR. "
90
+ "A PCR with valid_until in the past must have status Expired or Sunset; a PCR with valid_until "
91
+ "more than 5 years in the past must have status Sunset. Compliant systems should automatically "
92
+ "update these fields within 24 hours.",
93
+ )
77
94
  product_classes: dict[str, str | list[str]] = pyd.Field(
78
95
  description="List of classifications, including Masterformat and UNSPC", default_factory=dict
79
96
  )
97
+ applicable_in: list[Annotated[str, pyd.Field(min_length=2, max_length=2)]] | None = pyd.Field(
98
+ max_items=100,
99
+ default=None,
100
+ description="Jurisdiction(s) in which EPD is applicable. An empty array, or absent properties, "
101
+ "implies global applicability. Accepts "
102
+ "[2-letter country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2), "
103
+ "[M49 region codes](https://unstats.un.org/unsd/methodology/m49/), "
104
+ 'or the alias "EU27" for the 27 members of the Euro bloc, or the alias "NAFTA" '
105
+ "for the members of North American Free Trade Agreement",
106
+ example=["US", "CA", "MX", "EU27", "NAFTA"],
107
+ )
80
108
 
81
109
  @classmethod
82
110
  def get_asset_type(cls) -> str | None:
@@ -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.
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright 2023 by C Change Labs Inc. www.c-change-labs.com
2
+ # Copyright 2024 by C Change Labs Inc. www.c-change-labs.com
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -17,13 +17,29 @@
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
- import pydantic as pyd
21
20
 
21
+ from openepd.compat.pydantic import pyd
22
22
  from openepd.model.base import BaseOpenEpdSchema
23
- from openepd.model.specs.concrete import CmuSpec
23
+ from openepd.model.specs import concrete, steel
24
+ from openepd.model.specs.aluminium import AluminiumV1
25
+ from openepd.model.specs.asphalt import AsphaltV1
26
+ from openepd.model.specs.glass import GlazingV1
27
+ from openepd.model.specs.wood import TimberV1
24
28
 
25
29
 
26
30
  class Specs(BaseOpenEpdSchema):
27
31
  """Material specific specs."""
28
32
 
29
- cmu: CmuSpec | None = pyd.Field(default=None, description="Concrete Masonry Unit-specific (CMU) specs")
33
+ cmu: concrete.CmuSpec | None = pyd.Field(default=None, description="Concrete Masonry Unit-specific (CMU) specs")
34
+ CMU: concrete.CmuSpec | None = pyd.Field(default=None, description="Concrete Masonry Unit-specific (CMU) specs")
35
+ Concrete: concrete.ConcreteV1 | None = pyd.Field(
36
+ default=None, title="ConcreteV1", description="Concrete-specific specs"
37
+ )
38
+ PrecastConcrete: concrete.PrecastConcreteV1 | None = pyd.Field(
39
+ default=None, title="PrecastConcreteV1", description="Precast Concrete-specific specs"
40
+ )
41
+ Steel: steel.SteelV1 | None = pyd.Field(default=None, title="SteelV1", description="Steel-specific specs")
42
+ Asphalt: AsphaltV1 | None = pyd.Field(default=None, title="AsphaltV1")
43
+ Aluminium: AluminiumV1 | None = pyd.Field(default=None, title="AluminiumV1", description="Aluminium-specific specs")
44
+ Timber: TimberV1 | None = pyd.Field(default=None, title="TimberV1", description="Timber-specific specs")
45
+ Glazing: GlazingV1 | None = pyd.Field(default=None, title="GlazingV1", description="Glazing-specific specs")
@@ -0,0 +1,67 @@
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 enum import StrEnum
21
+
22
+ from openepd.compat.pydantic import pyd
23
+ from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
24
+ from openepd.model.validation.numbers import RatioFloat
25
+
26
+
27
+ class AluminiumAlloy(StrEnum):
28
+ """Aluminium alloy enum."""
29
+
30
+ ALLOY_1xxx = ("1xxx",)
31
+ ALLOY_2xxx = ("2xxx",)
32
+ ALLOY_3xxx = ("3xxx",)
33
+ ALLOY_4xxx = ("4xxx",)
34
+ ALLOY_5xxx = ("5xxx",)
35
+ ALLOY_6xxx = ("6xxx",)
36
+ ALLOY_7xxx = ("7xxx",)
37
+ ALLOY_8xxx = ("8xxx",)
38
+ ALLOY_1xx_x = ("1xx.x",)
39
+ ALLOY_2xx_x = ("2xx.x",)
40
+ ALLOY_3xx_x = ("3xx.x",)
41
+ ALLOY_4xx_x = ("4xx.x",)
42
+ ALLOY_5xx_x = ("5xx.x",)
43
+ ALLOY_7xx_x = ("7xx.x",)
44
+ ALLOY_8xx_x = ("8xx.x",)
45
+ ALLOY_9xx_x = ("9xx.x",)
46
+
47
+
48
+ class AluminiumExtrusionsV1(BaseOpenEpdHierarchicalSpec):
49
+ """Aluminium extrusions V1 spec."""
50
+
51
+ _EXT_VERSION = "1.0"
52
+
53
+ """Aluminium extrusions V1 spec."""
54
+ thermally_improved: bool | None = pyd.Field(default=None, description="Thermally improved")
55
+
56
+
57
+ class AluminiumV1(BaseOpenEpdHierarchicalSpec):
58
+ """Aluminium V1 spec."""
59
+
60
+ _EXT_VERSION = "1.0"
61
+ recycled_content: RatioFloat | None = pyd.Field(default=None, description="Recycled content")
62
+
63
+ alloy: AluminiumAlloy | None = pyd.Field(default=None, description="Alloy")
64
+ anodized: bool | None = None
65
+ painted: bool | None = None
66
+
67
+ AluminiumExtrusions: AluminiumExtrusionsV1 | None = pyd.Field(title="AluminiumExtrusionsV1", default=None)
@@ -0,0 +1,87 @@
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 enum import StrEnum
21
+
22
+ from openepd.compat.pydantic import pyd
23
+ from openepd.model.common import OpenEPDUnit
24
+ from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
25
+ from openepd.model.validation.numbers import RatioFloat
26
+ from openepd.model.validation.quantity import LengthMmStr, TemperatureCStr, validate_unit_factory
27
+
28
+
29
+ class AsphaltMixType(StrEnum):
30
+ """Asphalt mix type enum."""
31
+
32
+ HMA = "HMA"
33
+ WMA = "WMA"
34
+
35
+
36
+ class AsphaltGradation(StrEnum):
37
+ """Asphalt gradation enum."""
38
+
39
+ Dense_graded = "Dense-graded"
40
+ Open_graded = "Open-graded"
41
+ Gap_graded = "Gap-graded"
42
+
43
+
44
+ class AsphaltV1(BaseOpenEpdHierarchicalSpec):
45
+ """Asphalt spec."""
46
+
47
+ _EXT_VERSION = "1.0"
48
+
49
+ asphalt_aggregate_size_max: LengthMmStr | None = pyd.Field(
50
+ default=None, example="5mm", description="Max aggregate size"
51
+ )
52
+
53
+ asphalt_rap: RatioFloat | None = pyd.Field(
54
+ default=None, description="Percent of mixture that has been replaced by recycled " "asphalt pavement (RAP)."
55
+ )
56
+ asphalt_ras: RatioFloat | None = pyd.Field(
57
+ default=None, description="Percent of mixture that has been replaced by recycled " "asphalt shingles (RAS)."
58
+ )
59
+ asphalt_ground_tire_rubber: RatioFloat | None = pyd.Field(
60
+ default=None, description="Percent of mixture that has been replaced " "by ground tire rubber (GTR)."
61
+ )
62
+
63
+ asphalt_max_temperature: TemperatureCStr | None = pyd.Field(
64
+ default=None,
65
+ description="The upper threshold temperature to which an asphalt "
66
+ "binder can be heated preventing the asphalt mixture "
67
+ "from rutting",
68
+ )
69
+ asphalt_min_temperature: TemperatureCStr | None = pyd.Field(
70
+ default=None,
71
+ description="The lower threshold temperature for an asphalt "
72
+ "binder to prevent thermal cracking of the asphalt"
73
+ " mixture.",
74
+ )
75
+
76
+ asphalt_mix_type: AsphaltMixType | None = pyd.Field(default=None, description="Asphalt mix type")
77
+ asphalt_gradation: AsphaltGradation | None = pyd.Field(default=None, description="Asphalt gradation")
78
+
79
+ asphalt_sbr: bool | None = pyd.Field(default=None, description="Styrene-butadiene rubber (SBR)")
80
+ asphalt_sbs: bool | None = pyd.Field(default=None, description="Styrene-butadiene-styrene (SBS)")
81
+ asphalt_ppa: bool | None = pyd.Field(default=None, description="Polyphosphoric acid (PPA)")
82
+ asphalt_gtr: bool | None = pyd.Field(default=None, description="Ground tire rubber (GTR)")
83
+ asphalt_pmb: bool | None = pyd.Field(default=None, description="Polymer modified bitumen (PMB)")
84
+
85
+ _aggregate_size_max_validator = pyd.validator("asphalt_aggregate_size_max", allow_reuse=True)(
86
+ validate_unit_factory(OpenEPDUnit.m)
87
+ )
@@ -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
+ from openepd.compat.pydantic import pyd
23
+ from openepd.model.base import BaseOpenEpdSchema, Version
24
+ from openepd.model.validation.common import validate_version_compatibility, validate_version_format
25
+ from openepd.model.validation.quantity import QuantityValidator
26
+ from openepd.model.versioning import WithExtVersionMixin
27
+
28
+
29
+ class BaseOpenEpdSpec(BaseOpenEpdSchema):
30
+ """Base class for all OpenEPD specs."""
31
+
32
+ class Config:
33
+ use_enum_values = False # we need to store enums as strings and not values
34
+
35
+
36
+ class BaseOpenEpdHierarchicalSpec(BaseOpenEpdSpec, WithExtVersionMixin):
37
+ """Base class for new specs (hierarchical, versioned)."""
38
+
39
+ # external validator for quantities (e.g. length, mass, etc.) which should be setup by the user of the library.
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