openepd 6.13.1__tar.gz → 7.0.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 (151) hide show
  1. {openepd-6.13.1 → openepd-7.0.0}/PKG-INFO +1 -1
  2. {openepd-6.13.1 → openepd-7.0.0}/pyproject.toml +6 -1
  3. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/__init__.py +4 -4
  4. openepd-7.0.0/src/openepd/__version__.py +16 -0
  5. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/average_dataset/generic_estimate_sync_api.py +11 -10
  6. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/average_dataset/industry_epd_sync_api.py +9 -8
  7. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/base_sync_client.py +53 -9
  8. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/category/sync_api.py +1 -1
  9. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/dto/base.py +4 -4
  10. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/dto/common.py +24 -16
  11. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/dto/meta.py +15 -11
  12. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/dto/mf.py +9 -8
  13. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/epd/dto.py +43 -33
  14. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/epd/sync_api.py +9 -9
  15. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/pcr/sync_api.py +2 -2
  16. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/bundle/model.py +11 -10
  17. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/bundle/reader.py +12 -5
  18. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/bundle/writer.py +17 -6
  19. openepd-7.0.0/src/openepd/m49/__init__.py +17 -0
  20. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/m49/const.py +5 -2
  21. openepd-6.13.1/src/openepd/m49/geo_converter.py → openepd-7.0.0/src/openepd/m49/utils.py +24 -2
  22. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/base.py +60 -43
  23. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/category.py +13 -10
  24. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/common.py +100 -55
  25. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/declaration.py +93 -64
  26. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/epd.py +51 -43
  27. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/generic_estimate.py +28 -13
  28. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/industry_epd.py +15 -9
  29. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/lcia.py +132 -113
  30. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/org.py +54 -33
  31. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/pcr.py +38 -32
  32. openepd-7.0.0/src/openepd/model/specs/asphalt.py +95 -0
  33. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/base.py +11 -9
  34. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/concrete.py +60 -39
  35. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/aggregates.py +9 -9
  36. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/aluminium.py +7 -7
  37. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/asphalt.py +22 -19
  38. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/cladding.py +16 -16
  39. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/cmu.py +10 -9
  40. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/concrete.py +36 -27
  41. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/conveying_equipment.py +16 -15
  42. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/electrical.py +24 -22
  43. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/finishes.py +109 -104
  44. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/fire_and_smoke_protection.py +7 -7
  45. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/furnishings.py +16 -12
  46. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/manufacturing_inputs.py +16 -16
  47. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/masonry.py +16 -16
  48. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/mechanical.py +47 -47
  49. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/mechanical_insulation.py +7 -7
  50. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/network_infrastructure.py +54 -46
  51. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/openings.py +36 -31
  52. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/plumbing.py +15 -13
  53. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/precast_concrete.py +20 -16
  54. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/sheathing.py +18 -18
  55. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/steel.py +25 -25
  56. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/thermal_moisture_protection.py +20 -20
  57. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/utility_piping.py +9 -9
  58. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/wood.py +19 -19
  59. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/wood_joists.py +8 -8
  60. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/__init__.py +9 -5
  61. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/aggregates.py +22 -15
  62. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/aluminium.py +20 -5
  63. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/asphalt.py +44 -20
  64. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/cladding.py +38 -23
  65. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/cmu.py +26 -11
  66. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/common.py +3 -2
  67. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/concrete.py +85 -48
  68. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/conveying_equipment.py +30 -17
  69. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/deprecated/__init__.py +3 -2
  70. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/deprecated/concrete.py +68 -33
  71. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/deprecated/steel.py +28 -15
  72. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/electrical.py +69 -41
  73. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/finishes.py +250 -140
  74. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/fire_and_smoke_protection.py +9 -6
  75. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/furnishings.py +16 -14
  76. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/manufacturing_inputs.py +23 -14
  77. openepd-7.0.0/src/openepd/model/specs/singular/masonry.py +126 -0
  78. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/mechanical.py +48 -47
  79. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/mechanical_insulation.py +7 -6
  80. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/mixins/conduit_mixin.py +13 -10
  81. openepd-7.0.0/src/openepd/model/specs/singular/network_infrastructure.py +252 -0
  82. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/openings.py +127 -95
  83. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/plumbing.py +15 -12
  84. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/precast_concrete.py +68 -54
  85. openepd-7.0.0/src/openepd/model/specs/singular/sheathing.py +107 -0
  86. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/steel.py +69 -45
  87. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/thermal_moisture_protection.py +36 -20
  88. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/utility_piping.py +11 -8
  89. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/wood.py +48 -24
  90. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/wood_joists.py +19 -6
  91. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/standard.py +15 -8
  92. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/validation/common.py +9 -3
  93. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/validation/quantity.py +53 -25
  94. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/versioning.py +9 -6
  95. openepd-6.13.1/src/openepd/__version__.py +0 -16
  96. openepd-6.13.1/src/openepd/compat/compat_functional_validators.py +0 -25
  97. openepd-6.13.1/src/openepd/compat/pydantic.py +0 -30
  98. openepd-6.13.1/src/openepd/model/specs/asphalt.py +0 -86
  99. openepd-6.13.1/src/openepd/model/specs/singular/masonry.py +0 -81
  100. openepd-6.13.1/src/openepd/model/specs/singular/network_infrastructure.py +0 -193
  101. openepd-6.13.1/src/openepd/model/specs/singular/sheathing.py +0 -87
  102. openepd-6.13.1/src/openepd/model/validation/numbers.py +0 -28
  103. openepd-6.13.1/src/openepd/patch_pydantic.py +0 -108
  104. {openepd-6.13.1 → openepd-7.0.0}/LICENSE +0 -0
  105. {openepd-6.13.1 → openepd-7.0.0}/README.md +0 -0
  106. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/__init__.py +0 -0
  107. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/average_dataset/__init__.py +0 -0
  108. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/category/__init__.py +0 -0
  109. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/category/dto.py +0 -0
  110. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/common.py +0 -0
  111. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/dto/__init__.py +0 -0
  112. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/dto/params.py +0 -0
  113. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/epd/__init__.py +0 -0
  114. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/errors.py +0 -0
  115. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/pcr/__init__.py +0 -0
  116. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/sync_client.py +0 -0
  117. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/test/__init__.py +0 -0
  118. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/api/utils.py +0 -0
  119. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/bundle/__init__.py +0 -0
  120. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/bundle/base.py +0 -0
  121. {openepd-6.13.1/src/openepd/compat → openepd-7.0.0/src/openepd/model}/__init__.py +0 -0
  122. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/factory.py +0 -0
  123. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/geography.py +0 -0
  124. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/README.md +0 -0
  125. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/__init__.py +0 -0
  126. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/enums.py +0 -0
  127. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/__init__.py +0 -0
  128. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/accessories.py +0 -0
  129. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/bulk_materials.py +0 -0
  130. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/cast_decks_and_underlayment.py +0 -0
  131. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +0 -0
  132. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/electricity.py +0 -0
  133. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/grouting.py +0 -0
  134. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/material_handling.py +0 -0
  135. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/other_electrical_equipment.py +0 -0
  136. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/range/other_materials.py +0 -0
  137. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/accessories.py +0 -0
  138. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/bulk_materials.py +0 -0
  139. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/cast_decks_and_underlayment.py +0 -0
  140. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/electrical_transmission_and_distribution_equipment.py +0 -0
  141. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/electricity.py +0 -0
  142. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/grouting.py +0 -0
  143. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/material_handling.py +0 -0
  144. {openepd-6.13.1/src/openepd/m49 → openepd-7.0.0/src/openepd/model/specs/singular/mixins}/__init__.py +0 -0
  145. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/other_electrical_equipment.py +0 -0
  146. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/specs/singular/other_materials.py +0 -0
  147. {openepd-6.13.1/src/openepd/model → openepd-7.0.0/src/openepd/model/validation}/__init__.py +0 -0
  148. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/model/validation/enum.py +0 -0
  149. /openepd-6.13.1/src/openepd/model/specs/singular/mixins/__init__.py → /openepd-7.0.0/src/openepd/model/validation/numbers.py +0 -0
  150. /openepd-6.13.1/src/openepd/model/validation/__init__.py → /openepd-7.0.0/src/openepd/patch_pydantic.py +0 -0
  151. {openepd-6.13.1 → openepd-7.0.0}/src/openepd/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 6.13.1
3
+ Version: 7.0.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
@@ -1,6 +1,11 @@
1
+ [tool.ruff]
2
+ line-length = 120
3
+ target-version = "py312"
4
+ exclude = [".*pyi"]
5
+
1
6
  [tool.poetry]
2
7
  name = "openepd"
3
- version = "6.13.1"
8
+ version = "7.0.0"
4
9
  license = "Apache-2.0"
5
10
  description = "Python library to work with OpenEPD format"
6
11
  authors = ["C-Change Labs <support@c-change-labs.com>"]
@@ -15,7 +15,7 @@
15
15
  #
16
16
  import os
17
17
 
18
- from .patch_pydantic import patch_pydantic
19
-
20
- if os.environ.get("OPENEPD_DISABLE_PYDANTIC_PATCH", "false").lower() not in ("true", "1", "yes"):
21
- patch_pydantic()
18
+ # from .patch_pydantic import patch_pydantic
19
+ #
20
+ # if os.environ.get("OPENEPD_DISABLE_PYDANTIC_PATCH", "false").lower() not in ("true", "1", "yes"):
21
+ # patch_pydantic()
@@ -0,0 +1,16 @@
1
+ #
2
+ # Copyright 2025 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
+ VERSION = "7.0.0"
@@ -52,8 +52,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
52
52
  """
53
53
  response = self._client.do_request("get", f"/generic_estimates/{uuid}")
54
54
  if with_response:
55
- return GenericEstimate.parse_obj(response.json()), response
56
- return GenericEstimate.parse_obj(response.json())
55
+ return GenericEstimate.model_validate(response.json()), response
56
+ return GenericEstimate.model_validate(response.json())
57
57
 
58
58
  @overload
59
59
  def post_with_refs(
@@ -88,8 +88,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
88
88
  )
89
89
  content = response.json()
90
90
  if with_response:
91
- return GenericEstimate.parse_obj(content), response
92
- return GenericEstimate.parse_obj(content)
91
+ return GenericEstimate.model_validate(content), response
92
+ return GenericEstimate.model_validate(content)
93
93
 
94
94
  @overload
95
95
  def create(self, ge: GenericEstimate, with_response: Literal[True]) -> tuple[GenericEstimateRef, Response]: ...
@@ -114,8 +114,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
114
114
  )
115
115
  content = response.json()
116
116
  if with_response:
117
- return GenericEstimateRef.parse_obj(content), response
118
- return GenericEstimateRef.parse_obj(content)
117
+ return GenericEstimateRef.model_validate(content), response
118
+ return GenericEstimateRef.model_validate(content)
119
119
 
120
120
  @overload
121
121
  def edit(self, ge: GenericEstimate, with_response: Literal[True]) -> tuple[GenericEstimateRef, Response]: ...
@@ -144,8 +144,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
144
144
  )
145
145
  content = response.json()
146
146
  if with_response:
147
- return GenericEstimateRef.parse_obj(content), response
148
- return GenericEstimateRef.parse_obj(content)
147
+ return GenericEstimateRef.model_validate(content), response
148
+ return GenericEstimateRef.model_validate(content)
149
149
 
150
150
  @overload
151
151
  def list_raw(
@@ -177,7 +177,7 @@ class GenericEstimateApi(BaseApiMethodGroup):
177
177
  page_size=page_size,
178
178
  ),
179
179
  )
180
- data = [GenericEstimatePreview.parse_obj(o) for o in response.json()]
180
+ data = [GenericEstimatePreview.model_validate(o) for o in response.json()]
181
181
  if with_response:
182
182
  return data, response
183
183
  return data
@@ -193,7 +193,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
193
193
  def _get_page(p_num: int, p_size: int) -> GenericEstimateListResponse:
194
194
  data_list, response = self.list_raw(page_num=p_num, page_size=p_size, with_response=True)
195
195
  return GenericEstimateListResponse(
196
- payload=data_list, meta=GenericEstimateSearchMeta(paging=paging_meta_from_v1_api(response))
196
+ payload=data_list,
197
+ meta=GenericEstimateSearchMeta(paging=paging_meta_from_v1_api(response)),
197
198
  )
198
199
 
199
200
  return StreamingListResponse[GenericEstimatePreview](_get_page, page_size=page_size)
@@ -45,8 +45,8 @@ class IndustryEpdApi(BaseApiMethodGroup):
45
45
  """
46
46
  response = self._client.do_request("get", f"/industry_epds/{uuid}")
47
47
  if with_response:
48
- return IndustryEpd.parse_obj(response.json()), response
49
- return IndustryEpd.parse_obj(response.json())
48
+ return IndustryEpd.model_validate(response.json()), response
49
+ return IndustryEpd.model_validate(response.json())
50
50
 
51
51
  @overload
52
52
  def create(self, iepd: IndustryEpd, with_response: Literal[True]) -> tuple[IndustryEpdRef, Response]: ...
@@ -71,8 +71,8 @@ class IndustryEpdApi(BaseApiMethodGroup):
71
71
  )
72
72
  content = response.json()
73
73
  if with_response:
74
- return IndustryEpdRef.parse_obj(content), response
75
- return IndustryEpdRef.parse_obj(content)
74
+ return IndustryEpdRef.model_validate(content), response
75
+ return IndustryEpdRef.model_validate(content)
76
76
 
77
77
  @overload
78
78
  def edit(self, iepd: IndustryEpd, with_response: Literal[True]) -> tuple[IndustryEpdRef, Response]: ...
@@ -99,8 +99,8 @@ class IndustryEpdApi(BaseApiMethodGroup):
99
99
  )
100
100
  content = response.json()
101
101
  if with_response:
102
- return IndustryEpdRef.parse_obj(content), response
103
- return IndustryEpdRef.parse_obj(content)
102
+ return IndustryEpdRef.model_validate(content), response
103
+ return IndustryEpdRef.model_validate(content)
104
104
 
105
105
  @overload
106
106
  def list_raw(
@@ -131,7 +131,7 @@ class IndustryEpdApi(BaseApiMethodGroup):
131
131
  page_size=page_size,
132
132
  ),
133
133
  )
134
- data = [IndustryEpdPreview.parse_obj(o) for o in response.json()]
134
+ data = [IndustryEpdPreview.model_validate(o) for o in response.json()]
135
135
  if with_response:
136
136
  return data, response
137
137
  return data
@@ -147,7 +147,8 @@ class IndustryEpdApi(BaseApiMethodGroup):
147
147
  def _get_page(p_num: int, p_size: int) -> IndustryEpdListResponse:
148
148
  data_list, response = self.list_raw(page_num=p_num, page_size=p_size, with_response=True)
149
149
  return IndustryEpdListResponse(
150
- payload=data_list, meta=IndustryEpdSearchMeta(paging=paging_meta_from_v1_api(response))
150
+ payload=data_list,
151
+ meta=IndustryEpdSearchMeta(paging=paging_meta_from_v1_api(response)),
151
152
  )
152
153
 
153
154
  return StreamingListResponse[IndustryEpdPreview](_get_page, page_size=page_size)
@@ -238,7 +238,12 @@ class SyncHttpClient:
238
238
  return content
239
239
 
240
240
  def read_url_write_to_stream(
241
- self, url: str, target_stream: IO[bytes], method: str = "get", chunk_size: int = 1024, **kwargs
241
+ self,
242
+ url: str,
243
+ target_stream: IO[bytes],
244
+ method: str = "get",
245
+ chunk_size: int = 1024,
246
+ **kwargs,
242
247
  ) -> int:
243
248
  """
244
249
  Perform query to the given endpoint and writes response body to the given stream.
@@ -327,7 +332,12 @@ class SyncHttpClient:
327
332
  )
328
333
  if timeout > left_time:
329
334
  return resp
330
- logger.info("`%s %s` has been throttled for %s second(s)", method, url, timeout)
335
+ logger.info(
336
+ "`%s %s` has been throttled for %s second(s)",
337
+ method,
338
+ url,
339
+ timeout,
340
+ )
331
341
  time.sleep(timeout)
332
342
  left_time -= timeout
333
343
  if left_time > 0:
@@ -374,7 +384,13 @@ class SyncHttpClient:
374
384
  method,
375
385
  url,
376
386
  self._retry_count,
377
- partial(self._run_throttled_request, method, url, request_kwargs, session=session),
387
+ partial(
388
+ self._run_throttled_request,
389
+ method,
390
+ url,
391
+ request_kwargs,
392
+ session=session,
393
+ ),
378
394
  )
379
395
 
380
396
  response = do_request()
@@ -447,13 +463,21 @@ class SyncHttpClient:
447
463
  exception = None
448
464
  try:
449
465
  response = func(*args, **kwargs)
450
- except (requests.exceptions.ConnectionError, ConnectionError, Timeout) as e:
466
+ except (
467
+ requests.exceptions.ConnectionError,
468
+ ConnectionError,
469
+ Timeout,
470
+ ) as e:
451
471
  exception = e
452
472
 
453
473
  if exception or response.status_code == requests_codes.service_unavailable:
454
474
  secs = random.randint(60, 60 * 5)
455
475
  logger.warning(
456
- "%s %s is unavailable. Attempts left: %s. Waiting %s seconds...", method, url, attempts, secs
476
+ "%s %s is unavailable. Attempts left: %s. Waiting %s seconds...",
477
+ method,
478
+ url,
479
+ attempts,
480
+ secs,
457
481
  )
458
482
 
459
483
  # wait random number of seconds and request again
@@ -509,28 +533,48 @@ class DefaultOpenApiErrorHandlers:
509
533
  def handle_not_found(response: Response, raise_for_status: bool) -> Response | None:
510
534
  if raise_for_status:
511
535
  error = DefaultOpenApiErrorHandlers._parse_error_response(response)
512
- raise errors.ObjectNotFound(response.status_code, error.summary, response, error_code=error.error_code)
536
+ raise errors.ObjectNotFound(
537
+ response.status_code,
538
+ error.summary,
539
+ response,
540
+ error_code=error.error_code,
541
+ )
513
542
  return response
514
543
 
515
544
  @staticmethod
516
545
  def handle_unauthorized(response: Response, raise_for_status: bool) -> Response | None:
517
546
  if raise_for_status:
518
547
  error = DefaultOpenApiErrorHandlers._parse_error_response(response)
519
- raise errors.NotAuthorizedError(response.status_code, error.summary, response, error_code=error.error_code)
548
+ raise errors.NotAuthorizedError(
549
+ response.status_code,
550
+ error.summary,
551
+ response,
552
+ error_code=error.error_code,
553
+ )
520
554
  return response
521
555
 
522
556
  @staticmethod
523
557
  def handle_access_denied(response: Response, raise_for_status: bool) -> Response | None:
524
558
  if raise_for_status:
525
559
  error = DefaultOpenApiErrorHandlers._parse_error_response(response)
526
- raise errors.AccessDeniedError(response.status_code, error.summary, response, error_code=error.error_code)
560
+ raise errors.AccessDeniedError(
561
+ response.status_code,
562
+ error.summary,
563
+ response,
564
+ error_code=error.error_code,
565
+ )
527
566
  return response
528
567
 
529
568
  @staticmethod
530
569
  def handle_server_error(response: Response, raise_for_status: bool) -> Response | None:
531
570
  if raise_for_status:
532
571
  error = DefaultOpenApiErrorHandlers._parse_error_response(response)
533
- raise errors.ServerError(response.status_code, error.summary, response, error_code=error.error_code)
572
+ raise errors.ServerError(
573
+ response.status_code,
574
+ error.summary,
575
+ response,
576
+ error_code=error.error_code,
577
+ )
534
578
  return response
535
579
 
536
580
 
@@ -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.parse_raw(response.content)
31
+ return CategoryTreeResponse.model_validate(response.content)
32
32
 
33
33
  def get_tree(self) -> Category:
34
34
  """
@@ -15,14 +15,14 @@
15
15
  #
16
16
  import abc
17
17
 
18
- from openepd.compat.pydantic import pyd
18
+ import pydantic
19
+ from pydantic import ConfigDict
19
20
 
20
21
 
21
- class BaseOpenEpdApiModel(pyd.BaseModel):
22
+ class BaseOpenEpdApiModel(pydantic.BaseModel):
22
23
  """Base class for OpenEPD API DTOs."""
23
24
 
24
- class Config:
25
- extra = pyd.Extra.ignore
25
+ model_config = ConfigDict(extra="ignore")
26
26
 
27
27
 
28
28
  class BaseMetaDto(BaseOpenEpdApiModel, metaclass=abc.ABCMeta):
@@ -15,12 +15,14 @@
15
15
  #
16
16
  import abc
17
17
  import datetime
18
- from typing import Final, Generic, TypeAlias, TypeVar
18
+ from typing import ClassVar, Final, Generic, TypeAlias, TypeVar
19
+
20
+ import pydantic
21
+ from pydantic import ConfigDict
19
22
 
20
23
  from openepd.api.dto.base import BaseMetaDto, BaseOpenEpdApiModel, MetaExtensionBase
21
24
  from openepd.api.dto.meta import PerformanceMetaMixin
22
- from openepd.compat.pydantic import pyd, pyd_generics
23
- from openepd.model.base import AnySerializable, BaseOpenEpdSchema
25
+ from openepd.model.base import AnySerializable
24
26
 
25
27
  DEFAULT_PAGE_SIZE: Final[int] = 100
26
28
  MAX_PAGE_SIZE: Final[int] = 250
@@ -29,25 +31,25 @@ MAX_PAGE_SIZE: Final[int] = 250
29
31
  class AuditableDto(BaseOpenEpdApiModel, metaclass=abc.ABCMeta):
30
32
  """Base class for all DTOs that hold audit information."""
31
33
 
32
- created_by: str = pyd.Field(
34
+ created_by: str = pydantic.Field(
33
35
  title="Created By",
34
- example="johnsmith@cqd.io",
36
+ examples=["johnsmith@cqd.io"],
35
37
  description="User's email or script name that created this list.",
36
38
  )
37
- updated_by: str = pyd.Field(
39
+ updated_by: str = pydantic.Field(
38
40
  title="Updated By",
39
- example="bobbuilder@buildingtransparency.org",
41
+ examples=["bobbuilder@buildingtransparency.org"],
40
42
  description="User's email or script name that updated this list last time.",
41
43
  )
42
- created_on: datetime.datetime = pyd.Field(
44
+ created_on: datetime.datetime = pydantic.Field(
43
45
  title="Created On",
44
- example="2019-06-13T13:17:09+00:00",
46
+ examples=["2019-06-13T13:17:09+00:00"],
45
47
  description="A timestamp when this object has been created "
46
48
  "in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format.",
47
49
  )
48
- updated_on: datetime.datetime = pyd.Field(
50
+ updated_on: datetime.datetime = pydantic.Field(
49
51
  title="Updated On",
50
- example="2020-07-13T13:17:09+00:00",
52
+ examples=["2020-07-13T13:17:09+00:00"],
51
53
  description="A timestamp when this object has been updated last time "
52
54
  "in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format.",
53
55
  )
@@ -59,7 +61,7 @@ TMetaDto = TypeVar("TMetaDto", bound=BaseMetaDto)
59
61
  TMetaExtension = TypeVar("TMetaExtension", bound=MetaExtensionBase)
60
62
 
61
63
 
62
- class MetaCollectionDto(BaseOpenEpdApiModel, pyd_generics.GenericModel, Generic[TMetaExtension]):
64
+ class MetaCollectionDto(BaseOpenEpdApiModel, Generic[TMetaExtension]):
63
65
  """
64
66
  This class is intended to be used as a container for different meta objects.
65
67
 
@@ -86,13 +88,19 @@ class MetaCollectionDto(BaseOpenEpdApiModel, pyd_generics.GenericModel, Generic[
86
88
 
87
89
  ext: TMetaExtension | None = None
88
90
 
89
- class Config(BaseOpenEpdSchema.Config):
90
- schema_extra = {
91
+ model_config: ClassVar[ConfigDict] = ConfigDict(
92
+ json_schema_extra={
91
93
  "description": "Base structure of the response meta section",
92
94
  }
95
+ )
93
96
 
94
97
 
95
- class BaseMeta(PerformanceMetaMixin, MetaCollectionDto[TMetaExtension], Generic[TMetaExtension], metaclass=abc.ABCMeta):
98
+ class BaseMeta(
99
+ PerformanceMetaMixin,
100
+ MetaCollectionDto[TMetaExtension],
101
+ Generic[TMetaExtension],
102
+ metaclass=abc.ABCMeta,
103
+ ):
96
104
  """Base class for creating meta objects specific to a controller."""
97
105
 
98
106
  pass
@@ -101,7 +109,7 @@ class BaseMeta(PerformanceMetaMixin, MetaCollectionDto[TMetaExtension], Generic[
101
109
  TMeta = TypeVar("TMeta", bound=MetaCollectionDto, covariant=True)
102
110
 
103
111
 
104
- class OpenEpdApiResponse(pyd_generics.GenericModel, BaseOpenEpdApiModel, Generic[TPayload, TMeta]):
112
+ class OpenEpdApiResponse(BaseOpenEpdApiModel, Generic[TPayload, TMeta]):
105
113
  """Standard DTO representing response from OpenEPD API server."""
106
114
 
107
115
  payload: TPayload
@@ -13,8 +13,9 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ import pydantic
17
+
16
18
  from openepd.api.dto.base import BaseMetaDto, BaseOpenEpdApiModel
17
- from openepd.compat.pydantic import pyd
18
19
 
19
20
 
20
21
  class PerformanceMeta(BaseMetaDto):
@@ -32,30 +33,33 @@ class PerformanceMetaMixin(BaseMetaDto):
32
33
  class PagingMeta(BaseMetaDto):
33
34
  """Meta for paging information."""
34
35
 
35
- total_count: int = pyd.Field(
36
- title="Total results", example=1233, description="Total number of records for the search"
36
+ total_count: int = pydantic.Field(
37
+ title="Total results",
38
+ examples=[1233],
39
+ description="Total number of records for the search",
37
40
  )
38
- total_pages: int = pyd.Field(title="Total pages", example=20, description="Total pages available")
39
- page_size: int = pyd.Field(title="Page size", example=150, description="Number of records in page")
41
+ total_pages: int = pydantic.Field(title="Total pages", examples=[20], description="Total pages available")
42
+ page_size: int = pydantic.Field(title="Page size", examples=[150], description="Number of records in page")
40
43
 
41
44
 
42
45
  class PagingMetaMixin(BaseOpenEpdApiModel):
43
46
  """Mixin for adding paging meta to MetaCollection."""
44
47
 
45
- paging: PagingMeta | None
48
+ paging: PagingMeta | None = pydantic.Field(default=None, description="Paging information")
46
49
 
47
50
 
48
51
  class WarningMessageDto(BaseOpenEpdApiModel):
49
52
  """DTO for warning messages."""
50
53
 
51
- message: str = pyd.Field(
52
- title="Warning message", example="Categories limited during search, see effective_omf in meta"
54
+ message: str = pydantic.Field(
55
+ title="Warning message",
56
+ examples=["Categories limited during search, see effective_omf in meta"],
53
57
  )
54
- code: str = pyd.Field(title="Warning code", example="CATEGORIES_LIMITED")
55
- field: str | None = pyd.Field(
58
+ code: str = pydantic.Field(title="Warning code", examples=["CATEGORIES_LIMITED"])
59
+ field: str | None = pydantic.Field(
56
60
  title="Field",
57
61
  description="Field to which the warning relates",
58
- example="subcategories",
62
+ examples=["subcategories"],
59
63
  )
60
64
 
61
65
 
@@ -13,8 +13,9 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ import pydantic
17
+
16
18
  from openepd.api.dto.base import BaseOpenEpdApiModel
17
- from openepd.compat.pydantic import pyd
18
19
 
19
20
 
20
21
  class MaterialFilterDefinition(BaseOpenEpdApiModel):
@@ -24,14 +25,14 @@ class MaterialFilterDefinition(BaseOpenEpdApiModel):
24
25
  This object includes material filter itself as well as its hash.
25
26
  """
26
27
 
27
- mf: str = pyd.Field(
28
+ mf: str = pydantic.Field(
28
29
  title="MaterialFilter",
29
- example='!EC3 search("AluminiumBillets") !pragma oMF("1.0/1")',
30
+ examples=['!EC3 search("AluminiumBillets") !pragma oMF("1.0/1")'],
30
31
  description="MaterialFilter in string format",
31
32
  )
32
- mf_hash: str = pyd.Field(
33
+ mf_hash: str = pydantic.Field(
33
34
  title="MaterialFilter hash",
34
- example="22bf5b78cee5b79e1c76e818873d521c3972688b",
35
+ examples=["22bf5b78cee5b79e1c76e818873d521c3972688b"],
35
36
  description="MaterialFilter hash. Can be used to compare filters for equality, put to cache etc.",
36
37
  )
37
38
 
@@ -39,12 +40,12 @@ class MaterialFilterDefinition(BaseOpenEpdApiModel):
39
40
  class MaterialFilterMeta(BaseOpenEpdApiModel):
40
41
  """Meta holding supplementary information about OMF query execution."""
41
42
 
42
- excluded_fields: list[str] | None = pyd.Field(
43
- example=["building_jurisdiction", "jurisdiction"],
43
+ excluded_fields: list[str] | None = pydantic.Field(
44
+ examples=[["building_jurisdiction", "jurisdiction"]],
44
45
  description="list of fields excluded by server process for any reason",
45
46
  default=None,
46
47
  )
47
- effective_omf: MaterialFilterDefinition = pyd.Field(
48
+ effective_omf: MaterialFilterDefinition = pydantic.Field(
48
49
  description="Effective OpenMaterialFilter as applied to search, after transformations if any"
49
50
  )
50
51
 
@@ -15,11 +15,12 @@
15
15
  #
16
16
  from typing import TypeAlias
17
17
 
18
+ import pydantic
19
+
18
20
  from openepd.api.dto.base import BaseOpenEpdApiModel
19
21
  from openepd.api.dto.common import BaseMeta, OpenEpdApiResponse
20
22
  from openepd.api.dto.meta import PagingMetaMixin, WarningMetaMixin
21
23
  from openepd.api.dto.mf import MaterialFilterMetaMixin
22
- from openepd.compat.pydantic import pyd
23
24
  from openepd.model.common import Amount
24
25
  from openepd.model.epd import Epd
25
26
 
@@ -32,70 +33,79 @@ class StatisticsDto(BaseOpenEpdApiModel):
32
33
  """
33
34
 
34
35
  # percentiles
35
- pct10_gwp: float = pyd.Field(
36
+ pct10_gwp: float = pydantic.Field(
36
37
  description="10th percentile GWP for this statistics measured in kgCO2e per declared unit"
37
38
  )
38
- achievable_target: float = pyd.Field(
39
- description="Achievable target. 20th percentile of GWP measured in kgCO2e per declared unit", example=445.65
39
+ achievable_target: float = pydantic.Field(
40
+ description="Achievable target. 20th percentile of GWP measured in kgCO2e per declared unit",
41
+ examples=[445.65],
40
42
  )
41
- pct30_gwp: float = pyd.Field(
43
+ pct30_gwp: float = pydantic.Field(
42
44
  description="30th percentile GWP for this statistics measured in kgCO2e per declared unit"
43
45
  )
44
- pct40_gwp: float = pyd.Field(
46
+ pct40_gwp: float = pydantic.Field(
45
47
  description="40th percentile GWP for this statistics measured in kgCO2e per declared unit"
46
48
  )
47
- pct50_gwp: float = pyd.Field(
49
+ pct50_gwp: float = pydantic.Field(
48
50
  description="50th percentile GWP for this statistics measured in kgCO2e per declared unit"
49
51
  )
50
- pct60_gwp: float = pyd.Field(
52
+ pct60_gwp: float = pydantic.Field(
51
53
  description="60th percentile GWP for this statistics measured in kgCO2e per declared unit"
52
54
  )
53
- pct70_gwp: float = pyd.Field(
55
+ pct70_gwp: float = pydantic.Field(
54
56
  description="70th percentile GWP for this statistics measured in kgCO2e per declared unit"
55
57
  )
56
- conservative_estimate: float = pyd.Field(
58
+ conservative_estimate: float = pydantic.Field(
57
59
  description="Conservative estimate. 80th percentile of GWP per declared unit measured in kgCO2e",
58
- example=640.778,
60
+ examples=[640.778],
59
61
  )
60
- pct90_gwp: float = pyd.Field(
62
+ pct90_gwp: float = pydantic.Field(
61
63
  description="70th percentile GWP for this statistics measured in kgCO2e per declared unit"
62
64
  )
63
65
 
64
66
  # stats
65
- average: float = pyd.Field(description="Average GWP in kgCO2e per declared unit", example=554.2)
66
- min: float | None = pyd.Field(
67
- description="Min GWP of returned results measured in kgCO2e per declared unit", example=998.3
67
+ average: float = pydantic.Field(description="Average GWP in kgCO2e per declared unit", examples=[554.2])
68
+ min: float | None = pydantic.Field(
69
+ description="Min GWP of returned results measured in kgCO2e per declared unit",
70
+ examples=[998.3],
68
71
  )
69
- max: float | None = pyd.Field(
70
- description="Max GWP of returned results measured in kgCO2e per declared unit", example=120.0
72
+ max: float | None = pydantic.Field(
73
+ description="Max GWP of returned results measured in kgCO2e per declared unit",
74
+ examples=[120.0],
71
75
  )
72
76
 
73
77
  # percentiles w/out burden of doubt
74
- pct20_gwp_no_bod: float | None = pyd.Field(
75
- description="20th percentile of GWP (kgCO2e per declared unit), no burden of doubt", example=120
78
+ pct20_gwp_no_bod: float | None = pydantic.Field(
79
+ description="20th percentile of GWP (kgCO2e per declared unit), no burden of doubt",
80
+ examples=[120],
76
81
  )
77
- pct40_gwp_no_bod: float | None = pyd.Field(
78
- description="40th percentile of GWP (kgCO2e per declared unit), no burden of doubt", example=120
82
+ pct40_gwp_no_bod: float | None = pydantic.Field(
83
+ description="40th percentile of GWP (kgCO2e per declared unit), no burden of doubt",
84
+ examples=[120],
79
85
  )
80
- pct60_gwp_no_bod: float | None = pyd.Field(
81
- description="60th percentile of GWP (kgCO2e per declared unit), no burden of doubt", example=120
86
+ pct60_gwp_no_bod: float | None = pydantic.Field(
87
+ description="60th percentile of GWP (kgCO2e per declared unit), no burden of doubt",
88
+ examples=[120],
82
89
  )
83
- pct80_gwp_no_bod: float | None = pyd.Field(
84
- description="80th percentile of GWP (kgCO2e per declared unit), no burden of doubt", example=120
90
+ pct80_gwp_no_bod: float | None = pydantic.Field(
91
+ description="80th percentile of GWP (kgCO2e per declared unit), no burden of doubt",
92
+ examples=[120],
85
93
  )
86
- average_gwp_no_bod: float | None = pyd.Field(description="Average GWP, no burden of doubt", example=120)
94
+ average_gwp_no_bod: float | None = pydantic.Field(description="Average GWP, no burden of doubt", examples=[120])
87
95
 
88
96
  # set parameters
89
- standard_deviation: float = pyd.Field(description="Standard deviation", example=87.62)
90
- epds_count: int = pyd.Field(description="Number of EPDs participated in statistics", example=55)
91
- industry_epds_count: int = pyd.Field(
92
- description="Number of Industry-wide EPDs participated in statistics", example=4
97
+ standard_deviation: float = pydantic.Field(description="Standard deviation", examples=[87.62])
98
+ epds_count: int = pydantic.Field(description="Number of EPDs participated in statistics", examples=[55])
99
+ industry_epds_count: int = pydantic.Field(
100
+ description="Number of Industry-wide EPDs participated in statistics",
101
+ examples=[4],
93
102
  )
94
- generic_estimates_count: int = pyd.Field(
95
- description="Number of Generic Estimates participated in statistics", example=0
103
+ generic_estimates_count: int = pydantic.Field(
104
+ description="Number of Generic Estimates participated in statistics",
105
+ examples=[0],
96
106
  )
97
107
 
98
- declared_unit: Amount = pyd.Field(
108
+ declared_unit: Amount = pydantic.Field(
99
109
  description="Declared unit for the statistics. "
100
110
  "Statistical values - percentiles, averages etc - are based on this unit of product"
101
111
  )