openepd 6.13.2__py3-none-any.whl → 7.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 (99) hide show
  1. openepd/__init__.py +4 -4
  2. openepd/__version__.py +1 -1
  3. openepd/api/average_dataset/generic_estimate_sync_api.py +11 -10
  4. openepd/api/average_dataset/industry_epd_sync_api.py +9 -8
  5. openepd/api/base_sync_client.py +53 -9
  6. openepd/api/category/sync_api.py +1 -1
  7. openepd/api/dto/base.py +4 -4
  8. openepd/api/dto/common.py +24 -16
  9. openepd/api/dto/meta.py +15 -11
  10. openepd/api/dto/mf.py +9 -8
  11. openepd/api/epd/dto.py +43 -33
  12. openepd/api/epd/sync_api.py +9 -9
  13. openepd/api/pcr/sync_api.py +2 -2
  14. openepd/bundle/model.py +11 -10
  15. openepd/bundle/reader.py +12 -5
  16. openepd/bundle/writer.py +17 -6
  17. openepd/model/base.py +60 -43
  18. openepd/model/category.py +13 -10
  19. openepd/model/common.py +100 -55
  20. openepd/model/declaration.py +93 -64
  21. openepd/model/epd.py +51 -43
  22. openepd/model/generic_estimate.py +28 -13
  23. openepd/model/industry_epd.py +15 -9
  24. openepd/model/lcia.py +132 -113
  25. openepd/model/org.py +54 -33
  26. openepd/model/pcr.py +38 -32
  27. openepd/model/specs/asphalt.py +31 -22
  28. openepd/model/specs/base.py +11 -9
  29. openepd/model/specs/concrete.py +60 -39
  30. openepd/model/specs/range/aggregates.py +9 -9
  31. openepd/model/specs/range/aluminium.py +7 -7
  32. openepd/model/specs/range/asphalt.py +22 -19
  33. openepd/model/specs/range/cladding.py +16 -16
  34. openepd/model/specs/range/cmu.py +10 -9
  35. openepd/model/specs/range/concrete.py +36 -27
  36. openepd/model/specs/range/conveying_equipment.py +16 -15
  37. openepd/model/specs/range/electrical.py +24 -22
  38. openepd/model/specs/range/finishes.py +109 -104
  39. openepd/model/specs/range/fire_and_smoke_protection.py +7 -7
  40. openepd/model/specs/range/furnishings.py +16 -12
  41. openepd/model/specs/range/manufacturing_inputs.py +16 -16
  42. openepd/model/specs/range/masonry.py +16 -16
  43. openepd/model/specs/range/mechanical.py +47 -47
  44. openepd/model/specs/range/mechanical_insulation.py +7 -7
  45. openepd/model/specs/range/network_infrastructure.py +54 -46
  46. openepd/model/specs/range/openings.py +36 -31
  47. openepd/model/specs/range/plumbing.py +15 -13
  48. openepd/model/specs/range/precast_concrete.py +20 -16
  49. openepd/model/specs/range/sheathing.py +18 -18
  50. openepd/model/specs/range/steel.py +25 -25
  51. openepd/model/specs/range/thermal_moisture_protection.py +20 -20
  52. openepd/model/specs/range/utility_piping.py +9 -9
  53. openepd/model/specs/range/wood.py +19 -19
  54. openepd/model/specs/range/wood_joists.py +8 -8
  55. openepd/model/specs/singular/__init__.py +9 -5
  56. openepd/model/specs/singular/aggregates.py +22 -15
  57. openepd/model/specs/singular/aluminium.py +20 -5
  58. openepd/model/specs/singular/asphalt.py +44 -20
  59. openepd/model/specs/singular/cladding.py +38 -23
  60. openepd/model/specs/singular/cmu.py +26 -11
  61. openepd/model/specs/singular/common.py +3 -2
  62. openepd/model/specs/singular/concrete.py +85 -48
  63. openepd/model/specs/singular/conveying_equipment.py +30 -17
  64. openepd/model/specs/singular/deprecated/__init__.py +3 -2
  65. openepd/model/specs/singular/deprecated/concrete.py +68 -33
  66. openepd/model/specs/singular/deprecated/steel.py +28 -15
  67. openepd/model/specs/singular/electrical.py +69 -41
  68. openepd/model/specs/singular/finishes.py +250 -140
  69. openepd/model/specs/singular/fire_and_smoke_protection.py +9 -6
  70. openepd/model/specs/singular/furnishings.py +16 -14
  71. openepd/model/specs/singular/manufacturing_inputs.py +23 -14
  72. openepd/model/specs/singular/masonry.py +66 -21
  73. openepd/model/specs/singular/mechanical.py +48 -47
  74. openepd/model/specs/singular/mechanical_insulation.py +7 -6
  75. openepd/model/specs/singular/mixins/conduit_mixin.py +13 -10
  76. openepd/model/specs/singular/network_infrastructure.py +111 -52
  77. openepd/model/specs/singular/openings.py +127 -95
  78. openepd/model/specs/singular/plumbing.py +15 -12
  79. openepd/model/specs/singular/precast_concrete.py +68 -54
  80. openepd/model/specs/singular/sheathing.py +47 -27
  81. openepd/model/specs/singular/steel.py +69 -45
  82. openepd/model/specs/singular/thermal_moisture_protection.py +36 -20
  83. openepd/model/specs/singular/utility_piping.py +11 -8
  84. openepd/model/specs/singular/wood.py +48 -24
  85. openepd/model/specs/singular/wood_joists.py +19 -6
  86. openepd/model/standard.py +15 -8
  87. openepd/model/validation/common.py +9 -3
  88. openepd/model/validation/numbers.py +0 -13
  89. openepd/model/validation/quantity.py +53 -25
  90. openepd/model/versioning.py +9 -6
  91. openepd/patch_pydantic.py +0 -93
  92. {openepd-6.13.2.dist-info → openepd-7.0.0.dist-info}/METADATA +1 -1
  93. openepd-7.0.0.dist-info/RECORD +142 -0
  94. openepd/compat/__init__.py +0 -15
  95. openepd/compat/compat_functional_validators.py +0 -25
  96. openepd/compat/pydantic.py +0 -30
  97. openepd-6.13.2.dist-info/RECORD +0 -145
  98. {openepd-6.13.2.dist-info → openepd-7.0.0.dist-info}/LICENSE +0 -0
  99. {openepd-6.13.2.dist-info → openepd-7.0.0.dist-info}/WHEEL +0 -0
@@ -13,7 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from openepd.compat.pydantic import pyd
16
+ import pydantic
17
+
17
18
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
19
  from openepd.model.specs.enums import (
19
20
  FoamType,
@@ -23,7 +24,6 @@ from openepd.model.specs.enums import (
23
24
  RoofCoverBoardsFacing,
24
25
  RoofCoverBoardsMaterial,
25
26
  )
26
- from openepd.model.validation.numbers import RatioFloat
27
27
  from openepd.model.validation.quantity import LengthMmStr, PressureMPaStr
28
28
 
29
29
 
@@ -95,7 +95,7 @@ class BoardInsulationV1(BaseOpenEpdHierarchicalSpec):
95
95
  _EXT_VERSION = "1.0"
96
96
 
97
97
  # Own fields:
98
- compressive_strength: PressureMPaStr | None = pyd.Field(default=None, description="", example="1 MPa")
98
+ compressive_strength: PressureMPaStr | None = pydantic.Field(default=None, description="", examples=["1 MPa"])
99
99
 
100
100
 
101
101
  class FoamedInPlaceV1(BaseOpenEpdHierarchicalSpec):
@@ -104,7 +104,7 @@ class FoamedInPlaceV1(BaseOpenEpdHierarchicalSpec):
104
104
  _EXT_VERSION = "1.0"
105
105
 
106
106
  # Own fields:
107
- foam_type: FoamType | None = pyd.Field(default=None, description="", example="Open-Cell")
107
+ foam_type: FoamType | None = pydantic.Field(default=None, description="", examples=["Open-Cell"])
108
108
 
109
109
 
110
110
  class SprayedInsulationV1(BaseOpenEpdHierarchicalSpec):
@@ -134,14 +134,30 @@ class MembraneRoofingV1(BaseOpenEpdHierarchicalSpec):
134
134
  _EXT_VERSION = "1.0"
135
135
 
136
136
  # Own fields:
137
- thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="10 mm")
138
- sri: float | None = pyd.Field(default=None, description="", example=2.3)
139
- total_recycled_content: RatioFloat | None = pyd.Field(default=None, description="", example=0.5, ge=0, le=1)
140
- post_consumer_recycled_content: RatioFloat | None = pyd.Field(default=None, description="", example=0.5, ge=0, le=1)
141
- reinforcement: MembraneRoofingReinforcement | None = pyd.Field(default=None, description="", example="Polyester")
142
- felt_backing: bool | None = pyd.Field(default=None, description="", example=True)
143
- nsf347: bool | None = pyd.Field(default=None, description="", example=True)
144
- vantage_vinyl: bool | None = pyd.Field(default=None, description="", example=True)
137
+ thickness: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["10 mm"])
138
+ sri: float | None = pydantic.Field(default=None, description="", examples=[2.3])
139
+ total_recycled_content: float | None = pydantic.Field(default=None, description="", examples=[0.5], ge=0, le=1)
140
+ post_consumer_recycled_content: float | None = pydantic.Field(
141
+ default=None, description="", examples=[0.5], ge=0, le=1
142
+ )
143
+ reinforcement: MembraneRoofingReinforcement | None = pydantic.Field(
144
+ default=None, description="", examples=["Polyester"]
145
+ )
146
+ felt_backing: bool | None = pydantic.Field(
147
+ default=None,
148
+ description="",
149
+ examples=[True],
150
+ )
151
+ nsf347: bool | None = pydantic.Field(
152
+ default=None,
153
+ description="",
154
+ examples=[True],
155
+ )
156
+ vantage_vinyl: bool | None = pydantic.Field(
157
+ default=None,
158
+ description="",
159
+ examples=[True],
160
+ )
145
161
 
146
162
  # Nested specs:
147
163
  BituminousRoofing: BituminousRoofingV1 | None = None
@@ -159,12 +175,12 @@ class InsulationV1(BaseOpenEpdHierarchicalSpec):
159
175
  _EXT_VERSION = "1.0"
160
176
 
161
177
  # Own fields:
162
- r_value: float | None = pyd.Field(default=None, description="", example=2.3)
163
- material: InsulatingMaterial | None = pyd.Field(default=None, description="", example="Mineral Wool")
164
- intended_application: list[InsulationIntendedApplication] | None = pyd.Field(
165
- default=None, description="", example=["Wall & General"]
178
+ r_value: float | None = pydantic.Field(default=None, description="", examples=[2.3])
179
+ material: InsulatingMaterial | None = pydantic.Field(default=None, description="", examples=["Mineral Wool"])
180
+ intended_application: list[InsulationIntendedApplication] | None = pydantic.Field(
181
+ default=None, description="", examples=[["Wall & General"]]
166
182
  )
167
- thickness_per_declared_unit: LengthMmStr | None = pyd.Field(default=None, description="", example="10 mm")
183
+ thickness_per_declared_unit: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["10 mm"])
168
184
 
169
185
  # Nested specs:
170
186
  BlanketInsulation: BlanketInsulationV1 | None = None
@@ -207,9 +223,9 @@ class RoofCoverBoardsV1(BaseOpenEpdHierarchicalSpec):
207
223
  _EXT_VERSION = "1.0"
208
224
 
209
225
  # Own fields:
210
- material: RoofCoverBoardsMaterial | None = pyd.Field(default=None, description="", example="Gypsum Fiber")
211
- facing: list[RoofCoverBoardsFacing] | None = pyd.Field(default=None, description="", example=["Paper"])
212
- thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="1 m")
226
+ material: RoofCoverBoardsMaterial | None = pydantic.Field(default=None, description="", examples=["Gypsum Fiber"])
227
+ facing: list[RoofCoverBoardsFacing] | None = pydantic.Field(default=None, description="", examples=[["Paper"]])
228
+ thickness: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["1 m"])
213
229
 
214
230
 
215
231
  class SteepSlopeRoofingV1(BaseOpenEpdHierarchicalSpec):
@@ -13,7 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from openepd.compat.pydantic import pyd
16
+ import pydantic
17
+
17
18
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
19
  from openepd.model.specs.enums import BuriedPipingType, PipingAnsiSchedule, UtilityPipingMaterial
19
20
  from openepd.model.validation.quantity import LengthMmStr, MassPerLengthStr
@@ -36,8 +37,8 @@ class BuriedPipingV1(BaseOpenEpdHierarchicalSpec):
36
37
  _EXT_VERSION = "1.0"
37
38
 
38
39
  # Own fields:
39
- buried_piping_type: list[BuriedPipingType] | None = pyd.Field(
40
- default=None, description="", example=["Water Utilities"]
40
+ buried_piping_type: list[BuriedPipingType] | None = pydantic.Field(
41
+ default=None, description="", examples=[["Water Utilities"]]
41
42
  )
42
43
 
43
44
 
@@ -52,11 +53,13 @@ class UtilityPipingV1(BaseOpenEpdHierarchicalSpec):
52
53
  _EXT_VERSION = "1.0"
53
54
 
54
55
  # Own fields:
55
- thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="6 m")
56
- piping_diameter: LengthMmStr | None = pyd.Field(default=None, description="", example="200 mm")
57
- mass_per_unit_length: MassPerLengthStr | None = pyd.Field(default=None, description="", example="1 kg / m")
58
- piping_ansi_schedule: PipingAnsiSchedule | None = pyd.Field(default=None, description="", example="5")
59
- utility_piping_material: UtilityPipingMaterial | None = pyd.Field(default=None, description="", example="PVC")
56
+ thickness: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["6 m"])
57
+ piping_diameter: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["200 mm"])
58
+ mass_per_unit_length: MassPerLengthStr | None = pydantic.Field(default=None, description="", examples=["1 kg / m"])
59
+ piping_ansi_schedule: PipingAnsiSchedule | None = pydantic.Field(default=None, description="", examples=["5"])
60
+ utility_piping_material: UtilityPipingMaterial | None = pydantic.Field(
61
+ default=None, description="", examples=["PVC"]
62
+ )
60
63
 
61
64
  # Nested specs:
62
65
  BuildingHeatingPiping: BuildingHeatingPipingV1 | None = None
@@ -15,7 +15,8 @@
15
15
  #
16
16
  from typing import Annotated
17
17
 
18
- from openepd.compat.pydantic import pyd
18
+ import pydantic
19
+
19
20
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec, CodegenSpec
20
21
  from openepd.model.specs.enums import (
21
22
  AllFabrication,
@@ -27,7 +28,6 @@ from openepd.model.specs.enums import (
27
28
  SheathingPanelsFabrication,
28
29
  )
29
30
  from openepd.model.specs.singular.common import HasForestPracticesCertifiers
30
- from openepd.model.validation.numbers import RatioFloat
31
31
  from openepd.model.validation.quantity import LengthMmStr
32
32
 
33
33
 
@@ -72,8 +72,10 @@ class CompositeLumberV1(BaseOpenEpdHierarchicalSpec):
72
72
 
73
73
  _EXT_VERSION = "1.0"
74
74
 
75
- fabrication: CompositeLumberFabrication | None = pyd.Field(default=None, description="", example="LVL")
76
- timber_species: EngineeredTimberSpecies | None = pyd.Field(default=None, description="", example="Alaska Cedar")
75
+ fabrication: CompositeLumberFabrication | None = pydantic.Field(default=None, description="", examples=["LVL"])
76
+ timber_species: EngineeredTimberSpecies | None = pydantic.Field(
77
+ default=None, description="", examples=["Alaska Cedar"]
78
+ )
77
79
 
78
80
 
79
81
  class DimensionLumberV1(BaseOpenEpdHierarchicalSpec):
@@ -82,7 +84,7 @@ class DimensionLumberV1(BaseOpenEpdHierarchicalSpec):
82
84
  _EXT_VERSION = "1.0"
83
85
 
84
86
  # Nested specs:
85
- timber_species: SawnTimberSpecies | None = pyd.Field(default=None, description="", example="Alaska Cedar")
87
+ timber_species: SawnTimberSpecies | None = pydantic.Field(default=None, description="", examples=["Alaska Cedar"])
86
88
  WoodDecking: WoodDeckingV1 | None = None
87
89
  WoodFraming: WoodFramingV1 | None = None
88
90
 
@@ -103,8 +105,10 @@ class MassTimberV1(BaseOpenEpdHierarchicalSpec):
103
105
 
104
106
  _EXT_VERSION = "1.0"
105
107
 
106
- fabrication: MassTimberFabrication | None = pyd.Field(default=None, description="", example="CLT")
107
- timber_species: EngineeredTimberSpecies | None = pyd.Field(default=None, description="", example="Alaska Cedar")
108
+ fabrication: MassTimberFabrication | None = pydantic.Field(default=None, description="", examples=["CLT"])
109
+ timber_species: EngineeredTimberSpecies | None = pydantic.Field(
110
+ default=None, description="", examples=["Alaska Cedar"]
111
+ )
108
112
 
109
113
 
110
114
  class NonStructuralWoodV1(BaseOpenEpdHierarchicalSpec):
@@ -134,9 +138,11 @@ class SheathingPanelsV1(BaseOpenEpdHierarchicalSpec):
134
138
  _EXT_VERSION = "1.0"
135
139
 
136
140
  # Own fields:
137
- fabrication: SheathingPanelsFabrication | None = pyd.Field(default=None, description="", example="Plywood")
138
- wood_board_thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="10 mm")
139
- timber_species: EngineeredTimberSpecies | None = pyd.Field(default=None, description="", example="Alaska Cedar")
141
+ fabrication: SheathingPanelsFabrication | None = pydantic.Field(default=None, description="", examples=["Plywood"])
142
+ wood_board_thickness: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["10 mm"])
143
+ timber_species: EngineeredTimberSpecies | None = pydantic.Field(
144
+ default=None, description="", examples=["Alaska Cedar"]
145
+ )
140
146
 
141
147
 
142
148
  class UnfinishedWoodV1(BaseOpenEpdHierarchicalSpec):
@@ -151,25 +157,43 @@ class WoodV1(BaseOpenEpdHierarchicalSpec, HasForestPracticesCertifiers):
151
157
  _EXT_VERSION = "1.0"
152
158
 
153
159
  # Own fields:
154
- timber_species: AllTimberSpecies | None = pyd.Field(
155
- default=None, description="Timber species", example="Alaska Cedar"
160
+ timber_species: AllTimberSpecies | None = pydantic.Field(
161
+ default=None, description="Timber species", examples=["Alaska Cedar"]
162
+ )
163
+ fabrication: AllFabrication | None = pydantic.Field(
164
+ default=None, description="Timber fabrication", examples=["LVL"]
165
+ )
166
+ weather_exposed: bool | None = pydantic.Field(
167
+ default=None,
168
+ description="Weather exposed",
169
+ examples=[True],
170
+ )
171
+ fire_retardant: bool | None = pydantic.Field(
172
+ default=None,
173
+ description="Fire retardant",
174
+ examples=[True],
175
+ )
176
+ decay_resistant: bool | None = pydantic.Field(
177
+ default=None,
178
+ description="Decay resistant",
179
+ examples=[True],
156
180
  )
157
- fabrication: AllFabrication | None = pyd.Field(default=None, description="Timber fabrication", example="LVL")
158
- weather_exposed: bool | None = pyd.Field(default=None, description="Weather exposed", example=True)
159
- fire_retardant: bool | None = pyd.Field(default=None, description="Fire retardant", example=True)
160
- decay_resistant: bool | None = pyd.Field(default=None, description="Decay resistant", example=True)
161
- fsc_certified: Annotated[RatioFloat | None, CodegenSpec(override_type=RatioFloat)] = pyd.Field(
162
- default=None, description="Forest Stewardship Council certified proportion", example=0.3, ge=0, le=1
181
+ fsc_certified: Annotated[float | None, CodegenSpec(override_type=float)] = pydantic.Field(
182
+ default=None,
183
+ description="Forest Stewardship Council certified proportion",
184
+ examples=[0.3],
185
+ ge=0,
186
+ le=1,
163
187
  )
164
- fsc_certified_z: Annotated[float | None, CodegenSpec(override_type=float)] = pyd.Field(
165
- default=None, description="", example=0.7
188
+ fsc_certified_z: Annotated[float | None, CodegenSpec(override_type=float)] = pydantic.Field(
189
+ default=None, description="", examples=[0.7]
166
190
  )
167
191
 
168
- recycled_content: Annotated[RatioFloat | None, CodegenSpec(override_type=RatioFloat)] = pyd.Field(
169
- default=None, description="Recycled content", example=0.3, ge=0, le=1
192
+ recycled_content: Annotated[float | None, CodegenSpec(override_type=float)] = pydantic.Field(
193
+ default=None, description="Recycled content", examples=[0.3], ge=0, le=1
170
194
  )
171
- recycled_content_z: Annotated[float | None, CodegenSpec(override_type=float)] = pyd.Field(
172
- default=None, description="", example=0.7
195
+ recycled_content_z: Annotated[float | None, CodegenSpec(override_type=float)] = pydantic.Field(
196
+ default=None, description="", examples=[0.7]
173
197
  )
174
198
 
175
199
  # Nested specs:
@@ -13,7 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from openepd.compat.pydantic import pyd
16
+ import pydantic
17
+
17
18
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
19
  from openepd.model.specs.enums import AllFabrication, AllTimberSpecies
19
20
  from openepd.model.specs.singular.common import HasForestPracticesCertifiers
@@ -30,8 +31,20 @@ class WoodJoistsV1(BaseOpenEpdHierarchicalSpec, HasForestPracticesCertifiers):
30
31
  _EXT_VERSION = "1.0"
31
32
 
32
33
  # Own fields:
33
- timber_species: AllTimberSpecies | None = pyd.Field(default=None, description="", example="Alaska Cedar")
34
- fabrication: AllFabrication | None = pyd.Field(default=None, description="", example="LVL")
35
- weather_exposed: bool | None = pyd.Field(default=None, description="", example=True)
36
- fire_retardant: bool | None = pyd.Field(default=None, description="", example=True)
37
- decay_resistant: bool | None = pyd.Field(default=None, description="", example=True)
34
+ timber_species: AllTimberSpecies | None = pydantic.Field(default=None, description="", examples=["Alaska Cedar"])
35
+ fabrication: AllFabrication | None = pydantic.Field(default=None, description="", examples=["LVL"])
36
+ weather_exposed: bool | None = pydantic.Field(
37
+ default=None,
38
+ description="",
39
+ examples=[True],
40
+ )
41
+ fire_retardant: bool | None = pydantic.Field(
42
+ default=None,
43
+ description="",
44
+ examples=[True],
45
+ )
46
+ decay_resistant: bool | None = pydantic.Field(
47
+ default=None,
48
+ description="",
49
+ examples=[True],
50
+ )
openepd/model/standard.py CHANGED
@@ -13,7 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from openepd.compat.pydantic import pyd
16
+ import pydantic
17
+
17
18
  from openepd.model.base import BaseOpenEpdSchema
18
19
  from openepd.model.org import OrgRef
19
20
  from openepd.model.validation.common import ReferenceStr
@@ -22,19 +23,25 @@ from openepd.model.validation.common import ReferenceStr
22
23
  class StandardRef(BaseOpenEpdSchema):
23
24
  """Reference version (short) of Standard."""
24
25
 
25
- ref: ReferenceStr | None = pyd.Field(
26
+ ref: ReferenceStr | None = pydantic.Field(
26
27
  default=None,
27
- example="https://openepd.buildingtransparency.org/api/standards/EN15804",
28
+ examples=["https://openepd.buildingtransparency.org/api/standards/EN15804"],
28
29
  description="Reference to this Standard's JSON object",
29
30
  )
30
- short_name: str | None = pyd.Field(description="Short-form of name of standard. Must be unique. Case-insensitive")
31
+ short_name: str | None = pydantic.Field(
32
+ description="Short-form of name of standard. Must be unique. Case-insensitive"
33
+ )
31
34
 
32
35
 
33
36
  class Standard(StandardRef):
34
37
  """A standard, such as EN 15804, ISO 14044, ISO 14024:2018, etc."""
35
38
 
36
- name: str | None = pyd.Field(description="Full document name. Must be unique. Case-insensitive", default=None)
37
- link: pyd.AnyUrl | None = pyd.Field(
38
- description="Link to the exact standard (including version) referred to", default=None
39
+ name: str | None = pydantic.Field(
40
+ description="Full document name. Must be unique. Case-insensitive",
41
+ default=None,
42
+ )
43
+ link: pydantic.AnyUrl | None = pydantic.Field(
44
+ description="Link to the exact standard (including version) referred to",
45
+ default=None,
39
46
  )
40
- issuer: OrgRef | None = pyd.Field(description="Org that issued this standard", default=None)
47
+ issuer: OrgRef | None = pydantic.Field(description="Org that issued this standard", default=None)
@@ -15,7 +15,8 @@
15
15
  #
16
16
  from typing import Annotated, Any, Callable, Type, TypeAlias
17
17
 
18
- from openepd.compat.pydantic import pyd
18
+ import pydantic
19
+
19
20
  from openepd.model.versioning import Version
20
21
 
21
22
 
@@ -33,7 +34,9 @@ def validate_version_format(v: str) -> str:
33
34
  return v
34
35
 
35
36
 
36
- def validate_version_compatibility(class_version_attribute_name: str) -> Callable[[Type, str], str]:
37
+ def validate_version_compatibility(
38
+ class_version_attribute_name: str,
39
+ ) -> Callable[[Type, str], str]:
37
40
  """Ensure that the object which is passed for parsing and validation is compatible with the class."""
38
41
 
39
42
  # we need closure to pass property name, since actual class will only be available in runtime
@@ -51,5 +54,8 @@ def validate_version_compatibility(class_version_attribute_name: str) -> Callabl
51
54
 
52
55
  ReferenceStr: TypeAlias = Annotated[
53
56
  str,
54
- pyd.Field(description="Reference to another object", example="https://buildingtransparency.org/ec3/epds/1u7zsed8"),
57
+ pydantic.Field(
58
+ description="Reference to another object",
59
+ examples=["https://buildingtransparency.org/ec3/epds/1u7zsed8"],
60
+ ),
55
61
  ]
@@ -13,16 +13,3 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
-
17
-
18
- # todo when move to pydantic 2, these should change to Annotated[float, ...]
19
- class RatioFloat(float):
20
- """Float representing a ratio (0-1)."""
21
-
22
- pass
23
-
24
-
25
- class PositiveInt(int):
26
- """Greater than zero integer."""
27
-
28
- pass
@@ -16,7 +16,10 @@
16
16
  from abc import ABC, abstractmethod
17
17
  from typing import TYPE_CHECKING, Any, Callable, ClassVar
18
18
 
19
- from openepd.compat.pydantic import pyd
19
+ import pydantic
20
+ from pydantic import ConfigDict
21
+ import pydantic_core
22
+
20
23
  from openepd.model.base import BaseOpenEpdSchema
21
24
  from openepd.model.common import Amount, OpenEPDUnit, RangeAmount
22
25
 
@@ -113,7 +116,9 @@ def validate_unit_factory(dimensionality: OpenEPDUnit | str) -> "QuantityValidat
113
116
  return validator
114
117
 
115
118
 
116
- def validate_quantity_unit_factory(dimensionality: OpenEPDUnit | str) -> "QuantityValidatorType":
119
+ def validate_quantity_unit_factory(
120
+ dimensionality: OpenEPDUnit | str,
121
+ ) -> "QuantityValidatorType":
117
122
  """Create validator for quantity field to check unit matching."""
118
123
 
119
124
  def validator(cls: type | None, value: str) -> str:
@@ -162,18 +167,33 @@ class QuantityStr(str):
162
167
  unit: ClassVar[str]
163
168
 
164
169
  @classmethod
165
- def __get_validators__(cls):
166
- unit = getattr(cls, "unit", None)
167
- if unit:
168
- yield validate_quantity_unit_factory(cls.unit)
169
- yield validate_quantity_ge_factory(f"0 {cls.unit}")
170
+ def _validate(cls, value: str) -> str:
171
+ value = validate_quantity_unit_factory(cls.unit)(cls, value)
172
+ value = validate_quantity_ge_factory(f"0 {cls.unit}")(cls, value)
173
+ return value
170
174
 
171
175
  @classmethod
172
- def __modify_schema__(cls, field_schema):
173
- field_schema.update(
174
- example=f"1 {cls.unit}",
176
+ def __get_pydantic_core_schema__(
177
+ cls, source: type[Any], handler: pydantic.GetCoreSchemaHandler
178
+ ) -> pydantic_core.core_schema.CoreSchema:
179
+ return pydantic_core.core_schema.no_info_after_validator_function(
180
+ cls._validate,
181
+ pydantic_core.core_schema.str_schema(),
182
+ serialization=pydantic_core.core_schema.plain_serializer_function_ser_schema(
183
+ lambda x: x,
184
+ info_arg=False,
185
+ return_schema=pydantic_core.core_schema.str_schema(),
186
+ ),
175
187
  )
176
188
 
189
+ @classmethod
190
+ def __get_pydantic_json_schema__(cls, core_schema, handler):
191
+ schema = handler(core_schema)
192
+ schema.update(
193
+ examples=[f"1 {cls.unit}"],
194
+ )
195
+ return schema
196
+
177
197
 
178
198
  class PressureMPaStr(QuantityStr):
179
199
  """Pressure quantity type."""
@@ -205,10 +225,12 @@ class LengthMmStr(QuantityStr):
205
225
  unit = OpenEPDUnit.m
206
226
 
207
227
  @classmethod
208
- def __modify_schema__(cls, field_schema):
209
- field_schema.update(
210
- example="6 mm",
228
+ def __get_pydantic_json_schema__(cls, core_schema, handler):
229
+ schema = handler(core_schema)
230
+ schema.update(
231
+ examples=["6 mm"],
211
232
  )
233
+ return schema
212
234
 
213
235
 
214
236
  class LengthInchStr(QuantityStr):
@@ -217,10 +239,12 @@ class LengthInchStr(QuantityStr):
217
239
  unit = "inch"
218
240
 
219
241
  @classmethod
220
- def __modify_schema__(cls, field_schema):
221
- field_schema.update(
222
- example="2.5 inch",
242
+ def __get_pydantic_json_schema__(cls, core_schema, handler):
243
+ schema = handler(core_schema)
244
+ schema.update(
245
+ examples=["2.5 inch"],
223
246
  )
247
+ return schema
224
248
 
225
249
 
226
250
  class TemperatureCStr(QuantityStr):
@@ -356,26 +380,30 @@ class WithDimensionalityMixin(BaseOpenEpdSchema):
356
380
 
357
381
  # Unit for dimensionality to validate against, for example "kg"
358
382
 
359
- @pyd.root_validator
383
+ @pydantic.model_validator(mode="before")
360
384
  def check_dimensionality_matches(cls, values: dict[str, Any]) -> dict[str, Any]:
361
385
  """Check that this amount conforms to the same dimensionality as dimensionality_unit."""
362
386
  if not cls.dimensionality_unit:
363
387
  return values
364
388
 
365
- validate_unit_factory(cls.dimensionality_unit)(BaseOpenEpdSchema, values.get("unit")) # type:ignore [arg-type]
389
+ validate_unit_factory(cls.dimensionality_unit)(BaseOpenEpdSchema, values.get("unit")) # type: ignore[arg-type]
366
390
  return values
367
391
 
368
392
 
369
393
  class AmountRangeWithDimensionality(RangeAmount, WithDimensionalityMixin):
370
394
  """Mass amount, range."""
371
395
 
372
- class Config:
373
- """Pydantic config."""
374
-
375
- @staticmethod
376
- def schema_extra(schema: dict[str, Any], model: type["AmountRangeWithDimensionality"]) -> None:
377
- """Modify json schema."""
378
- schema["example"] = {"min": 1.2, "max": 3.4, "unit": str(model.dimensionality_unit) or None}
396
+ model_config = ConfigDict(
397
+ json_schema_extra=lambda schema, model: schema.update(
398
+ {
399
+ "example": {
400
+ "min": 1.2,
401
+ "max": 3.4,
402
+ "unit": str(model.dimensionality_unit) or None,
403
+ }
404
+ }
405
+ )
406
+ )
379
407
 
380
408
 
381
409
  class WithMassKgMixin(WithDimensionalityMixin):
@@ -17,10 +17,10 @@ from abc import ABC
17
17
  from enum import ReprEnum
18
18
  from typing import ClassVar, NamedTuple
19
19
 
20
- from openepd.compat.pydantic import pyd
20
+ import pydantic
21
21
 
22
22
 
23
- class WithExtVersionMixin(ABC, pyd.BaseModel):
23
+ class WithExtVersionMixin(ABC, pydantic.BaseModel):
24
24
  """Mixin for extensions supporting versions: recommended way."""
25
25
 
26
26
  _EXT_VERSION: ClassVar[str]
@@ -30,10 +30,10 @@ class WithExtVersionMixin(ABC, pyd.BaseModel):
30
30
  """Set the default value for the ext_version field from _EXT_VERSION class var."""
31
31
  super().__init_subclass__()
32
32
  if hasattr(cls, "_EXT_VERSION"):
33
- cls.__fields__["ext_version"].default = cls._EXT_VERSION
33
+ cls.model_fields["ext_version"].default = cls._EXT_VERSION
34
34
 
35
35
  # Note: default is set programmatically in __init_subclass__
36
- ext_version: str | None = pyd.Field(description="Extension version", example="3.22", default=None)
36
+ ext_version: str | None = pydantic.Field(description="Extension version", examples=["3.22"], default=None)
37
37
 
38
38
 
39
39
  class Version(NamedTuple):
@@ -43,7 +43,7 @@ class Version(NamedTuple):
43
43
  minor: int
44
44
 
45
45
  @staticmethod
46
- def parse_version(version: str) -> "Version":
46
+ def parse_version(version: str | None) -> "Version":
47
47
  """Parse the version of extension or the format.
48
48
 
49
49
  Version is expected to be major.minor
@@ -51,7 +51,10 @@ class Version(NamedTuple):
51
51
  :param version: The extension version.
52
52
  :return: A tuple of major and minor version numbers.
53
53
  """
54
- splits = version.split(".", 1) if isinstance(version, str) else None
54
+ if isinstance(version, str):
55
+ splits = version.split(".", 1)
56
+ else:
57
+ splits = []
55
58
  if len(splits) != 2:
56
59
  raise ValueError(f"Invalid version: {version}")
57
60
  if not splits[0].isdigit() or not splits[1].isdigit():