openepd 7.0.0__tar.gz → 7.1.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.
Files changed (142) hide show
  1. {openepd-7.0.0 → openepd-7.1.0}/PKG-INFO +4 -2
  2. {openepd-7.0.0 → openepd-7.1.0}/pyproject.toml +3 -3
  3. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/__version__.py +1 -1
  4. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/category/sync_api.py +1 -1
  5. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/base.py +1 -1
  6. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/common.py +8 -5
  7. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/epd.py +4 -4
  8. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/generic_estimate.py +2 -2
  9. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/lcia.py +47 -24
  10. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/org.py +38 -26
  11. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/base.py +3 -2
  12. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/furnishings.py +15 -15
  13. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/steel.py +3 -1
  14. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/furnishings.py +10 -10
  15. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/validation/quantity.py +37 -32
  16. openepd-7.0.0/src/openepd/__init__.py +0 -21
  17. {openepd-7.0.0 → openepd-7.1.0}/LICENSE +0 -0
  18. {openepd-7.0.0 → openepd-7.1.0}/README.md +0 -0
  19. {openepd-7.0.0/src/openepd/api → openepd-7.1.0/src/openepd}/__init__.py +0 -0
  20. {openepd-7.0.0/src/openepd/api/average_dataset → openepd-7.1.0/src/openepd/api}/__init__.py +0 -0
  21. {openepd-7.0.0/src/openepd/api/category → openepd-7.1.0/src/openepd/api/average_dataset}/__init__.py +0 -0
  22. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/average_dataset/generic_estimate_sync_api.py +0 -0
  23. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/average_dataset/industry_epd_sync_api.py +0 -0
  24. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/base_sync_client.py +0 -0
  25. {openepd-7.0.0/src/openepd/api/dto → openepd-7.1.0/src/openepd/api/category}/__init__.py +0 -0
  26. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/category/dto.py +0 -0
  27. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/common.py +0 -0
  28. {openepd-7.0.0/src/openepd/api/epd → openepd-7.1.0/src/openepd/api/dto}/__init__.py +0 -0
  29. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/dto/base.py +0 -0
  30. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/dto/common.py +0 -0
  31. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/dto/meta.py +0 -0
  32. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/dto/mf.py +0 -0
  33. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/dto/params.py +0 -0
  34. {openepd-7.0.0/src/openepd/api/pcr → openepd-7.1.0/src/openepd/api/epd}/__init__.py +0 -0
  35. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/epd/dto.py +0 -0
  36. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/epd/sync_api.py +0 -0
  37. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/errors.py +0 -0
  38. {openepd-7.0.0/src/openepd/api/test → openepd-7.1.0/src/openepd/api/pcr}/__init__.py +0 -0
  39. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/pcr/sync_api.py +0 -0
  40. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/sync_client.py +0 -0
  41. {openepd-7.0.0/src/openepd/bundle → openepd-7.1.0/src/openepd/api/test}/__init__.py +0 -0
  42. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/api/utils.py +0 -0
  43. {openepd-7.0.0/src/openepd/model → openepd-7.1.0/src/openepd/bundle}/__init__.py +0 -0
  44. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/bundle/base.py +0 -0
  45. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/bundle/model.py +0 -0
  46. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/bundle/reader.py +0 -0
  47. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/bundle/writer.py +0 -0
  48. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/m49/__init__.py +0 -0
  49. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/m49/const.py +0 -0
  50. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/m49/utils.py +0 -0
  51. {openepd-7.0.0/src/openepd/model/specs/singular/mixins → openepd-7.1.0/src/openepd/model}/__init__.py +0 -0
  52. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/category.py +0 -0
  53. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/declaration.py +0 -0
  54. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/factory.py +0 -0
  55. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/geography.py +0 -0
  56. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/industry_epd.py +0 -0
  57. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/pcr.py +0 -0
  58. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/README.md +0 -0
  59. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/__init__.py +0 -0
  60. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/asphalt.py +0 -0
  61. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/concrete.py +0 -0
  62. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/enums.py +0 -0
  63. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/__init__.py +0 -0
  64. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/accessories.py +0 -0
  65. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/aggregates.py +0 -0
  66. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/aluminium.py +0 -0
  67. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/asphalt.py +0 -0
  68. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/bulk_materials.py +0 -0
  69. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/cast_decks_and_underlayment.py +0 -0
  70. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/cladding.py +0 -0
  71. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/cmu.py +0 -0
  72. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/concrete.py +0 -0
  73. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/conveying_equipment.py +0 -0
  74. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/electrical.py +0 -0
  75. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +0 -0
  76. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/electricity.py +0 -0
  77. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/finishes.py +0 -0
  78. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/fire_and_smoke_protection.py +0 -0
  79. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/grouting.py +0 -0
  80. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/manufacturing_inputs.py +0 -0
  81. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/masonry.py +0 -0
  82. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/material_handling.py +0 -0
  83. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/mechanical.py +0 -0
  84. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/mechanical_insulation.py +0 -0
  85. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/network_infrastructure.py +0 -0
  86. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/openings.py +0 -0
  87. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/other_electrical_equipment.py +0 -0
  88. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/other_materials.py +0 -0
  89. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/plumbing.py +0 -0
  90. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/precast_concrete.py +0 -0
  91. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/sheathing.py +0 -0
  92. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/thermal_moisture_protection.py +0 -0
  93. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/utility_piping.py +0 -0
  94. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/wood.py +0 -0
  95. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/range/wood_joists.py +0 -0
  96. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/__init__.py +0 -0
  97. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/accessories.py +0 -0
  98. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/aggregates.py +0 -0
  99. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/aluminium.py +0 -0
  100. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/asphalt.py +0 -0
  101. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/bulk_materials.py +0 -0
  102. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/cast_decks_and_underlayment.py +0 -0
  103. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/cladding.py +0 -0
  104. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/cmu.py +0 -0
  105. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/common.py +0 -0
  106. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/concrete.py +0 -0
  107. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/conveying_equipment.py +0 -0
  108. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/deprecated/__init__.py +0 -0
  109. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/deprecated/concrete.py +0 -0
  110. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/deprecated/steel.py +0 -0
  111. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/electrical.py +0 -0
  112. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/electrical_transmission_and_distribution_equipment.py +0 -0
  113. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/electricity.py +0 -0
  114. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/finishes.py +0 -0
  115. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/fire_and_smoke_protection.py +0 -0
  116. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/grouting.py +0 -0
  117. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/manufacturing_inputs.py +0 -0
  118. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/masonry.py +0 -0
  119. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/material_handling.py +0 -0
  120. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/mechanical.py +0 -0
  121. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/mechanical_insulation.py +0 -0
  122. {openepd-7.0.0/src/openepd/model/validation → openepd-7.1.0/src/openepd/model/specs/singular/mixins}/__init__.py +0 -0
  123. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/mixins/conduit_mixin.py +0 -0
  124. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/network_infrastructure.py +0 -0
  125. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/openings.py +0 -0
  126. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/other_electrical_equipment.py +0 -0
  127. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/other_materials.py +0 -0
  128. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/plumbing.py +0 -0
  129. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/precast_concrete.py +0 -0
  130. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/sheathing.py +0 -0
  131. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/steel.py +0 -0
  132. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/thermal_moisture_protection.py +0 -0
  133. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/utility_piping.py +0 -0
  134. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/wood.py +0 -0
  135. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/specs/singular/wood_joists.py +0 -0
  136. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/standard.py +0 -0
  137. /openepd-7.0.0/src/openepd/model/validation/numbers.py → /openepd-7.1.0/src/openepd/model/validation/__init__.py +0 -0
  138. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/validation/common.py +0 -0
  139. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/validation/enum.py +0 -0
  140. /openepd-7.0.0/src/openepd/patch_pydantic.py → /openepd-7.1.0/src/openepd/model/validation/numbers.py +0 -0
  141. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/model/versioning.py +0 -0
  142. {openepd-7.0.0 → openepd-7.1.0}/src/openepd/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 7.0.0
3
+ Version: 7.1.0
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
@@ -15,13 +15,15 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
18
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
21
  Provides-Extra: api-client
20
22
  Requires-Dist: email-validator (>=1.3.1)
21
23
  Requires-Dist: idna (>=3.7)
22
24
  Requires-Dist: open-xpd-uuid (>=0.2.1,<0.3.0)
23
25
  Requires-Dist: openlocationcode (>=1.0.1)
24
- Requires-Dist: pydantic (>=1.10,<3.0)
26
+ Requires-Dist: pydantic (>=2.0,<3)
25
27
  Requires-Dist: requests (>=2.0) ; extra == "api-client"
26
28
  Project-URL: Repository, https://github.com/cchangelabs/openepd
27
29
  Description-Content-Type: text/markdown
@@ -1,11 +1,11 @@
1
1
  [tool.ruff]
2
2
  line-length = 120
3
- target-version = "py312"
3
+ target-version = "py311"
4
4
  exclude = [".*pyi"]
5
5
 
6
6
  [tool.poetry]
7
7
  name = "openepd"
8
- version = "7.0.0"
8
+ version = "7.1.0"
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 = ">=1.10,<3.0"
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"
@@ -13,4 +13,4 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- VERSION = "7.0.0"
16
+ VERSION = "7.1.0"
@@ -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.model_validate(response.content)
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(**data)
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
- @pydantic.model_validator(mode="before")
36
- def check_qty_or_unit(cls, values: dict[str, Any]):
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
- if values.get("qty") is None and values.get("unit") is None:
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 values
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
- @pydantic.model_validator(mode="before")
204
- def _unit_validator(cls, values: dict[str, Any]) -> dict[str, Any]:
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, v in values.items():
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 cls.allowed_units:
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 cls.allowed_units is not None and len(all_units) == 1 and ExternalValidationConfig.QUANTITY_VALIDATOR:
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 = cls.allowed_units if isinstance(cls.allowed_units, tuple) else (cls.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 values
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.__fields__:
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,103 @@ 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"
366
+
367
+
368
+ class ScopeSetKgSbe(ScopeSet):
369
+ """ScopeSet measured in kgSbe."""
370
+
371
+ allowed_units: ClassVar[str | tuple[str, ...] | None] = "kgSbe"
362
372
 
363
373
 
364
374
  class ScopeSetDiseaseIncidence(ScopeSet):
365
375
  """ScopeSet measuring disease incidence measured in AnnualPerCapita (cases)."""
366
376
 
367
- allowed_units = "AnnualPerCapita"
377
+ allowed_units: ClassVar[str | tuple[str, ...] | None] = "AnnualPerCapita"
368
378
 
369
379
 
370
380
  class ScopeSetMass(ScopeSet):
371
381
  """ScopeSet measuring mass in kg."""
372
382
 
373
- allowed_units = "kg"
383
+ allowed_units: ClassVar[str | tuple[str, ...] | None] = "kg"
374
384
 
375
385
 
376
386
  class ScopeSetVolume(ScopeSet):
377
387
  """ScopeSet measuring mass in kg."""
378
388
 
379
- allowed_units = "m3"
389
+ allowed_units: ClassVar[str | tuple[str, ...] | None] = "m3"
380
390
 
381
391
 
382
392
  class ScopeSetMassOrVolume(ScopeSet):
383
393
  """ScopeSet measuring mass in kg OR volume in m3, example: radioactive waste."""
384
394
 
385
- allowed_units = ("kg", "m3")
395
+ allowed_units: ClassVar[str | tuple[str, ...] | None] = ("kg", "m3")
386
396
 
387
397
 
388
398
  class ScopeSetEnergy(ScopeSet):
389
399
  """ScopeSet measuring mass in kg."""
390
400
 
391
- allowed_units = "MJ"
401
+ allowed_units: ClassVar[str | tuple[str, ...] | None] = "MJ"
392
402
 
393
403
 
394
404
  class ImpactSet(ScopesetByNameBase):
@@ -483,6 +493,19 @@ class ImpactSet(ScopesetByNameBase):
483
493
  default=None,
484
494
  description="Land use related impacts / Soil quality, in potential soil quality parameters.",
485
495
  )
496
+ ADP_mineral: ScopeSetKgSbe | None = pydantic.Field(
497
+ alias="ADP-mineral",
498
+ default=None,
499
+ description='Abiotic depletion potential for non-fossil resources. EN15804 calls this "ADP - minerals and metals".',
500
+ )
501
+
502
+ ADP_fossil: ScopeSetEnergy | None = pydantic.Field(
503
+ alias="ADP-fossil",
504
+ default=None,
505
+ description="Abiotic depletion potential for fossil resources",
506
+ )
507
+
508
+ model_config = pydantic.ConfigDict(from_attributes=True)
486
509
 
487
510
 
488
511
  class LCIAMethod(StrEnum):
@@ -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 Annotated, List, Optional
16
+ from typing import Any, Optional
17
17
 
18
18
  from openlocationcode import openlocationcode
19
19
  import pydantic
20
- from pydantic import ConfigDict, Field, StringConstraints
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
- Annotated[
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
- deprecated="Pluscode field is deprecated. If users need a pluscode they can obtain it from "
115
- "`id` like this: `id.spit('.', maxsplit=1)[0]`",
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, Version
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
@@ -19,11 +19,11 @@ __all__ = (
19
19
  "CountertopsRangeV1",
20
20
  "DemountablePartitionsRangeV1",
21
21
  "OtherFurnishingsRangeV1",
22
- "OpenStorageRangeV1",
23
- "ClosedStorageRangeV1",
24
- "RetractableStorageRangeV1",
25
- "MobileStorageRangeV1",
26
- "WallMountedShelvingRangeV1",
22
+ "OpenStorageFurnitureRangeV1",
23
+ "ClosedStorageFurnitureRangeV1",
24
+ "RetractableStorageFurnitureRangeV1",
25
+ "MobileStorageFurnitureRangeV1",
26
+ "WallMountedStorageShelvingRangeV1",
27
27
  "OtherStorageFurnitureRangeV1",
28
28
  "StorageFurnitureRangeV1",
29
29
  "TablesRangeV1",
@@ -97,7 +97,7 @@ class OtherFurnishingsRangeV1(BaseOpenEpdHierarchicalSpec):
97
97
  _EXT_VERSION = "1.0"
98
98
 
99
99
 
100
- class OpenStorageRangeV1(BaseOpenEpdHierarchicalSpec):
100
+ class OpenStorageFurnitureRangeV1(BaseOpenEpdHierarchicalSpec):
101
101
  """
102
102
  Open Storage.
103
103
 
@@ -109,7 +109,7 @@ class OpenStorageRangeV1(BaseOpenEpdHierarchicalSpec):
109
109
  _EXT_VERSION = "1.0"
110
110
 
111
111
 
112
- class ClosedStorageRangeV1(BaseOpenEpdHierarchicalSpec):
112
+ class ClosedStorageFurnitureRangeV1(BaseOpenEpdHierarchicalSpec):
113
113
  """
114
114
  Closed Storage.
115
115
 
@@ -121,7 +121,7 @@ class ClosedStorageRangeV1(BaseOpenEpdHierarchicalSpec):
121
121
  _EXT_VERSION = "1.0"
122
122
 
123
123
 
124
- class RetractableStorageRangeV1(BaseOpenEpdHierarchicalSpec):
124
+ class RetractableStorageFurnitureRangeV1(BaseOpenEpdHierarchicalSpec):
125
125
  """
126
126
  Retractable Storage.
127
127
 
@@ -133,7 +133,7 @@ class RetractableStorageRangeV1(BaseOpenEpdHierarchicalSpec):
133
133
  _EXT_VERSION = "1.0"
134
134
 
135
135
 
136
- class MobileStorageRangeV1(BaseOpenEpdHierarchicalSpec):
136
+ class MobileStorageFurnitureRangeV1(BaseOpenEpdHierarchicalSpec):
137
137
  """
138
138
  Mobile Storage.
139
139
 
@@ -145,7 +145,7 @@ class MobileStorageRangeV1(BaseOpenEpdHierarchicalSpec):
145
145
  _EXT_VERSION = "1.0"
146
146
 
147
147
 
148
- class WallMountedShelvingRangeV1(BaseOpenEpdHierarchicalSpec):
148
+ class WallMountedStorageShelvingRangeV1(BaseOpenEpdHierarchicalSpec):
149
149
  """
150
150
  Wall Mounted Shelving.
151
151
 
@@ -176,11 +176,11 @@ class StorageFurnitureRangeV1(BaseOpenEpdHierarchicalSpec):
176
176
 
177
177
  _EXT_VERSION = "1.1"
178
178
 
179
- OpenStorage: OpenStorageRangeV1 | None = None
180
- ClosedStorage: ClosedStorageRangeV1 | None = None
181
- RetractableStorage: RetractableStorageRangeV1 | None = None
182
- MobileStorage: MobileStorageRangeV1 | None = None
183
- WallMountedShelving: WallMountedShelvingRangeV1 | None = None
179
+ OpenStorageFurniture: OpenStorageFurnitureRangeV1 | None = None
180
+ ClosedStorageFurniture: ClosedStorageFurnitureRangeV1 | None = None
181
+ RetractableStorageFurniture: RetractableStorageFurnitureRangeV1 | None = None
182
+ MobileStorageFurniture: MobileStorageFurnitureRangeV1 | None = None
183
+ WallMountedStorageShelving: WallMountedStorageShelvingRangeV1 | None = None
184
184
  OtherStorageFurniture: OtherStorageFurnitureRangeV1 | None = None
185
185
 
186
186
 
@@ -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,
@@ -57,7 +57,7 @@ class OtherFurnishingsV1(BaseOpenEpdHierarchicalSpec):
57
57
  _EXT_VERSION = "1.0"
58
58
 
59
59
 
60
- class OpenStorageV1(BaseOpenEpdHierarchicalSpec):
60
+ class OpenStorageFurnitureV1(BaseOpenEpdHierarchicalSpec):
61
61
  """
62
62
  Open Storage.
63
63
 
@@ -67,7 +67,7 @@ class OpenStorageV1(BaseOpenEpdHierarchicalSpec):
67
67
  _EXT_VERSION = "1.0"
68
68
 
69
69
 
70
- class ClosedStorageV1(BaseOpenEpdHierarchicalSpec):
70
+ class ClosedStorageFurnitureV1(BaseOpenEpdHierarchicalSpec):
71
71
  """
72
72
  Closed Storage.
73
73
 
@@ -77,7 +77,7 @@ class ClosedStorageV1(BaseOpenEpdHierarchicalSpec):
77
77
  _EXT_VERSION = "1.0"
78
78
 
79
79
 
80
- class RetractableStorageV1(BaseOpenEpdHierarchicalSpec):
80
+ class RetractableStorageFurnitureV1(BaseOpenEpdHierarchicalSpec):
81
81
  """
82
82
  Retractable Storage.
83
83
 
@@ -87,7 +87,7 @@ class RetractableStorageV1(BaseOpenEpdHierarchicalSpec):
87
87
  _EXT_VERSION = "1.0"
88
88
 
89
89
 
90
- class MobileStorageV1(BaseOpenEpdHierarchicalSpec):
90
+ class MobileStorageFurnitureV1(BaseOpenEpdHierarchicalSpec):
91
91
  """
92
92
  Mobile Storage.
93
93
 
@@ -97,7 +97,7 @@ class MobileStorageV1(BaseOpenEpdHierarchicalSpec):
97
97
  _EXT_VERSION = "1.0"
98
98
 
99
99
 
100
- class WallMountedShelvingV1(BaseOpenEpdHierarchicalSpec):
100
+ class WallMountedStorageShelvingV1(BaseOpenEpdHierarchicalSpec):
101
101
  """
102
102
  Wall Mounted Shelving.
103
103
 
@@ -119,11 +119,11 @@ class StorageFurnitureV1(BaseOpenEpdHierarchicalSpec):
119
119
  _EXT_VERSION = "1.1"
120
120
 
121
121
  # Nested specs:
122
- OpenStorage: OpenStorageV1 | None = None
123
- ClosedStorage: ClosedStorageV1 | None = None
124
- RetractableStorage: RetractableStorageV1 | None = None
125
- MobileStorage: MobileStorageV1 | None = None
126
- WallMountedShelving: WallMountedShelvingV1 | None = None
122
+ OpenStorageFurniture: OpenStorageFurnitureV1 | None = None
123
+ ClosedStorageFurniture: ClosedStorageFurnitureV1 | None = None
124
+ RetractableStorageFurniture: RetractableStorageFurnitureV1 | None = None
125
+ MobileStorageFurniture: MobileStorageFurnitureV1 | None = None
126
+ WallMountedStorageShelving: WallMountedStorageShelvingV1 | None = None
127
127
  OtherStorageFurniture: OtherStorageFurnitureV1 | None = None
128
128
 
129
129