openepd 7.0.0__py3-none-any.whl → 7.1.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.
openepd/__init__.py CHANGED
@@ -13,9 +13,3 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- import os
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()
openepd/__version__.py CHANGED
@@ -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
  """
openepd/model/base.py CHANGED
@@ -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}"
openepd/model/common.py CHANGED
@@ -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."""
openepd/model/epd.py CHANGED
@@ -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
openepd/model/lcia.py CHANGED
@@ -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):
openepd/model/org.py CHANGED
@@ -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
 
@@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, Any, Callable, ClassVar
19
19
  import pydantic
20
20
  from pydantic import ConfigDict
21
21
  import pydantic_core
22
+ from typing_extensions import Self
22
23
 
23
24
  from openepd.model.base import BaseOpenEpdSchema
24
25
  from openepd.model.common import Amount, OpenEPDUnit, RangeAmount
@@ -377,17 +378,21 @@ class WithDimensionalityMixin(BaseOpenEpdSchema):
377
378
  """Class for dimensionality-validated amounts."""
378
379
 
379
380
  dimensionality_unit: ClassVar[str | None] = None
381
+ unit: str | None
380
382
 
381
383
  # Unit for dimensionality to validate against, for example "kg"
382
384
 
383
- @pydantic.model_validator(mode="before")
384
- def check_dimensionality_matches(cls, values: dict[str, Any]) -> dict[str, Any]:
385
+ @pydantic.model_validator(mode="after")
386
+ def check_dimensionality_matches(self) -> Self:
385
387
  """Check that this amount conforms to the same dimensionality as dimensionality_unit."""
386
- if not cls.dimensionality_unit:
387
- return values
388
+ if not self.dimensionality_unit:
389
+ return self
388
390
 
389
- validate_unit_factory(cls.dimensionality_unit)(BaseOpenEpdSchema, values.get("unit")) # type: ignore[arg-type]
390
- return values
391
+ if self.unit is None:
392
+ raise ValueError("`unit` is required for dimensionality-validated amounts")
393
+
394
+ validate_unit_factory(self.dimensionality_unit)(BaseOpenEpdSchema, self.unit)
395
+ return self
391
396
 
392
397
 
393
398
  class AmountRangeWithDimensionality(RangeAmount, WithDimensionalityMixin):
@@ -409,7 +414,7 @@ class AmountRangeWithDimensionality(RangeAmount, WithDimensionalityMixin):
409
414
  class WithMassKgMixin(WithDimensionalityMixin):
410
415
  """Unit validation mixin."""
411
416
 
412
- dimensionality_unit = MassKgStr.unit
417
+ dimensionality_unit: ClassVar[str | None] = MassKgStr.unit
413
418
 
414
419
 
415
420
  class AmountMass(Amount, WithMassKgMixin):
@@ -427,7 +432,7 @@ class AmountRangeMass(AmountRangeWithDimensionality, WithMassKgMixin):
427
432
  class WithGwpMixin(WithDimensionalityMixin):
428
433
  """Unit validation mixin."""
429
434
 
430
- dimensionality_unit = GwpKgCo2eStr.unit
435
+ dimensionality_unit: ClassVar[str | None] = GwpKgCo2eStr.unit
431
436
 
432
437
 
433
438
  class AmountGWP(Amount, WithGwpMixin):
@@ -463,7 +468,7 @@ class AmountLengthM(Amount, WithLengthMMixin):
463
468
  class WithLengthMmMixin(WithDimensionalityMixin):
464
469
  """Unit validation mixin."""
465
470
 
466
- dimensionality_unit = LengthMmStr.unit
471
+ dimensionality_unit: ClassVar[str | None] = LengthMmStr.unit
467
472
 
468
473
 
469
474
  class AmountLengthMm(Amount, WithLengthMMixin):
@@ -481,7 +486,7 @@ class AmountRangeLengthMm(AmountRangeWithDimensionality, WithLengthMmMixin):
481
486
  class WithPressureMpaMixin(WithDimensionalityMixin):
482
487
  """Unit validation mixin."""
483
488
 
484
- dimensionality_unit = PressureMPaStr.unit
489
+ dimensionality_unit: ClassVar[str | None] = PressureMPaStr.unit
485
490
 
486
491
 
487
492
  class AmountPressureMpa(Amount, WithPressureMpaMixin):
@@ -499,7 +504,7 @@ class AmountRangePressureMpa(AmountRangeWithDimensionality, WithPressureMpaMixin
499
504
  class WithAreaM2Mixin(WithDimensionalityMixin):
500
505
  """Unit validation mixin."""
501
506
 
502
- dimensionality_unit = AreaM2Str.unit
507
+ dimensionality_unit: ClassVar[str | None] = AreaM2Str.unit
503
508
 
504
509
 
505
510
  class AmountAreaM2(Amount, WithAreaM2Mixin):
@@ -517,7 +522,7 @@ class AmountRangeAreaM2(AmountRangeWithDimensionality, WithAreaM2Mixin):
517
522
  class WithLengthInchStr(WithDimensionalityMixin):
518
523
  """Unit validation mixin."""
519
524
 
520
- dimensionality_unit = LengthInchStr.unit
525
+ dimensionality_unit: ClassVar[str | None] = LengthInchStr.unit
521
526
 
522
527
 
523
528
  class AmountLengthInch(Amount, WithLengthInchStr):
@@ -535,7 +540,7 @@ class AmountRangeLengthInch(AmountRangeWithDimensionality, WithLengthInchStr):
535
540
  class WithTemperatureCMixin(WithDimensionalityMixin):
536
541
  """Unit validation mixin."""
537
542
 
538
- dimensionality_unit = TemperatureCStr.unit
543
+ dimensionality_unit: ClassVar[str | None] = TemperatureCStr.unit
539
544
 
540
545
 
541
546
  class AmountTemperatureC(Amount, WithTemperatureCMixin):
@@ -553,7 +558,7 @@ class AmountRangeTemperatureC(AmountRangeWithDimensionality, WithTemperatureCMix
553
558
  class WithCapacityPerHourMixin(WithDimensionalityMixin):
554
559
  """Unit validation mixin."""
555
560
 
556
- dimensionality_unit = CapacityPerHourStr.unit
561
+ dimensionality_unit: ClassVar[str | None] = CapacityPerHourStr.unit
557
562
 
558
563
 
559
564
  class AmountCapacityPerHour(Amount, WithCapacityPerHourMixin):
@@ -571,7 +576,7 @@ class AmountRangeCapacityPerHour(AmountRangeWithDimensionality, WithCapacityPerH
571
576
  class WithRValueMixin(WithDimensionalityMixin):
572
577
  """Unit validation mixin."""
573
578
 
574
- dimensionality_unit = RValueStr.unit
579
+ dimensionality_unit: ClassVar[str | None] = RValueStr.unit
575
580
 
576
581
 
577
582
  class AmountRValue(Amount, WithRValueMixin):
@@ -589,7 +594,7 @@ class AmountRangeRValue(AmountRangeWithDimensionality, WithRValueMixin):
589
594
  class WithSpeedMixin(WithDimensionalityMixin):
590
595
  """Unit validation mixin."""
591
596
 
592
- dimensionality_unit = SpeedStr.unit
597
+ dimensionality_unit: ClassVar[str | None] = SpeedStr.unit
593
598
 
594
599
 
595
600
  class AmountSpeed(Amount, WithSpeedMixin):
@@ -607,7 +612,7 @@ class AmountRangeSpeed(AmountRangeWithDimensionality, WithSpeedMixin):
607
612
  class WithColorTemperatureMixin(WithDimensionalityMixin):
608
613
  """Unit validation mixin."""
609
614
 
610
- dimensionality_unit = ColorTemperatureStr.unit
615
+ dimensionality_unit: ClassVar[str | None] = ColorTemperatureStr.unit
611
616
 
612
617
 
613
618
  class AmountColorTemperature(Amount, WithColorTemperatureMixin):
@@ -625,7 +630,7 @@ class AmountRangeColorTemperature(AmountRangeWithDimensionality, WithColorTemper
625
630
  class WithLuminosityMixin(WithDimensionalityMixin):
626
631
  """Unit validation mixin."""
627
632
 
628
- dimensionality_unit = LuminosityStr.unit
633
+ dimensionality_unit: ClassVar[str | None] = LuminosityStr.unit
629
634
 
630
635
 
631
636
  class AmountLuminosity(Amount, WithLuminosityMixin):
@@ -643,7 +648,7 @@ class AmountRangeLuminosity(AmountRangeWithDimensionality, WithLuminosityMixin):
643
648
  class WithPowerMixin(WithDimensionalityMixin):
644
649
  """Unit validation mixin."""
645
650
 
646
- dimensionality_unit = PowerStr.unit
651
+ dimensionality_unit: ClassVar[str | None] = PowerStr.unit
647
652
 
648
653
 
649
654
  class AmountPower(Amount, WithPowerMixin):
@@ -661,7 +666,7 @@ class AmountRangePower(AmountRangeWithDimensionality, WithPowerMixin):
661
666
  class WithElectricalCurrentMixin(WithDimensionalityMixin):
662
667
  """Unit validation mixin."""
663
668
 
664
- dimensionality_unit = ElectricalCurrentStr.unit
669
+ dimensionality_unit: ClassVar[str | None] = ElectricalCurrentStr.unit
665
670
 
666
671
 
667
672
  class AmountElectricalCurrent(Amount, WithElectricalCurrentMixin):
@@ -679,7 +684,7 @@ class AmountRangeElectricalCurrent(AmountRangeWithDimensionality, WithElectrical
679
684
  class WithVolumeMixin(WithDimensionalityMixin):
680
685
  """Unit validation mixin."""
681
686
 
682
- dimensionality_unit = VolumeStr.unit
687
+ dimensionality_unit: ClassVar[str | None] = VolumeStr.unit
683
688
 
684
689
 
685
690
  class AmountVolume(Amount, WithVolumeMixin):
@@ -697,7 +702,7 @@ class AmountRangeVolume(AmountRangeWithDimensionality, WithVolumeMixin):
697
702
  class WithAirflowMixin(WithDimensionalityMixin):
698
703
  """Unit validation mixin."""
699
704
 
700
- dimensionality_unit = AirflowStr.unit
705
+ dimensionality_unit: ClassVar[str | None] = AirflowStr.unit
701
706
 
702
707
 
703
708
  class AmountAirflow(Amount, WithAirflowMixin):
@@ -715,7 +720,7 @@ class AmountRangeAirflow(AmountRangeWithDimensionality, WithAirflowMixin):
715
720
  class WithFlowRateMixin(WithDimensionalityMixin):
716
721
  """Unit validation mixin."""
717
722
 
718
- dimensionality_unit = FlowRateStr.unit
723
+ dimensionality_unit: ClassVar[str | None] = FlowRateStr.unit
719
724
 
720
725
 
721
726
  class AmountFlowRate(Amount, WithFlowRateMixin):
@@ -733,7 +738,7 @@ class AmountRangeFlowRate(AmountRangeWithDimensionality, WithFlowRateMixin):
733
738
  class WithMassPerLengthMixin(WithDimensionalityMixin):
734
739
  """Unit validation mixin."""
735
740
 
736
- dimensionality_unit = MassPerLengthStr.unit
741
+ dimensionality_unit: ClassVar[str | None] = MassPerLengthStr.unit
737
742
 
738
743
 
739
744
  class AmountMassPerLength(Amount, WithFlowRateMixin):
@@ -751,7 +756,7 @@ class AmountRangeMassPerLength(AmountRangeWithDimensionality, WithFlowRateMixin)
751
756
  class WithAreaPerVolumeMixin(WithDimensionalityMixin):
752
757
  """Unit validation mixin."""
753
758
 
754
- dimensionality_unit = AreaPerVolumeStr.unit
759
+ dimensionality_unit: ClassVar[str | None] = AreaPerVolumeStr.unit
755
760
 
756
761
 
757
762
  class AmountAreaPerVolume(Amount, WithFlowRateMixin):
@@ -769,7 +774,7 @@ class AmountRangeAreaPerVolume(AmountRangeWithDimensionality, WithFlowRateMixin)
769
774
  class WithThermalConductivity(WithDimensionalityMixin):
770
775
  """Unit validation mixin."""
771
776
 
772
- dimensionality_unit = ThermalConductivityStr.unit
777
+ dimensionality_unit: ClassVar[str | None] = ThermalConductivityStr.unit
773
778
 
774
779
 
775
780
  class AmountThermalConductivityMixin(Amount, WithThermalConductivity):
@@ -791,7 +796,7 @@ class WithForce(WithDimensionalityMixin):
791
796
  May the Force be with you.
792
797
  """
793
798
 
794
- dimensionality_unit = ForceNStr.unit
799
+ dimensionality_unit: ClassVar[str | None] = ForceNStr.unit
795
800
 
796
801
 
797
802
  class AmountForce(Amount, WithForce):
@@ -809,7 +814,7 @@ class AmountRangeForce(AmountRangeWithDimensionality, WithForce):
809
814
  class WithYarnWeight(WithDimensionalityMixin):
810
815
  """Unit validation mixin."""
811
816
 
812
- dimensionality_unit = YarnWeightStr.unit
817
+ dimensionality_unit: ClassVar[str | None] = YarnWeightStr.unit
813
818
 
814
819
 
815
820
  class AmountYarnWeight(Amount, WithYarnWeight):
@@ -827,7 +832,7 @@ class AmountRangeYarnWeight(AmountRangeWithDimensionality, WithYarnWeight):
827
832
  class WithThermalExpansion(WithDimensionalityMixin):
828
833
  """Unit validation mixin."""
829
834
 
830
- dimensionality_unit = ThermalExpansionStr.unit
835
+ dimensionality_unit: ClassVar[str | None] = ThermalExpansionStr.unit
831
836
 
832
837
 
833
838
  class AmountThermalExpansion(Amount, WithThermalExpansion):
@@ -845,7 +850,7 @@ class AmountRangeThermalExpansion(AmountRangeWithDimensionality, WithThermalExpa
845
850
  class WithUtilization(WithDimensionalityMixin):
846
851
  """Unit validation mixin."""
847
852
 
848
- dimensionality_unit = UtilizationStr.unit
853
+ dimensionality_unit: ClassVar[str | None] = UtilizationStr.unit
849
854
 
850
855
 
851
856
  class AmountUtilization(Amount, WithUtilization):
@@ -863,7 +868,7 @@ class AmountRangeUtilization(AmountRangeWithDimensionality, WithUtilization):
863
868
  class WithUFactor(WithDimensionalityMixin):
864
869
  """Unit validation mixin."""
865
870
 
866
- dimensionality_unit = UFactorStr.unit
871
+ dimensionality_unit: ClassVar[str | None] = UFactorStr.unit
867
872
 
868
873
 
869
874
  class AmountUFactor(Amount, WithUFactor):
@@ -881,7 +886,7 @@ class AmountRangeUFactor(AmountRangeWithDimensionality, WithUFactor):
881
886
  class WithRFactor(WithDimensionalityMixin):
882
887
  """Unit validation mixin."""
883
888
 
884
- dimensionality_unit = RFactorStr.unit
889
+ dimensionality_unit: ClassVar[str | None] = RFactorStr.unit
885
890
 
886
891
 
887
892
  class AmountRFactor(Amount, WithRFactor):
@@ -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,5 +1,5 @@
1
- openepd/__init__.py,sha256=o8u8-hR9V6fC9zReuofWQmP29Q8fkc1bRVDKcL0R9MI,801
2
- openepd/__version__.py,sha256=PCi2D9R69zErtn17M6az0izAFOXYLv1Egqf6d3NPSzc,638
1
+ openepd/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
2
+ openepd/__version__.py,sha256=YE_JkVAHWAazVwAceVI_tNykuQM3UyXJiDIfU2dySlI,638
3
3
  openepd/api/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
4
4
  openepd/api/average_dataset/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
5
5
  openepd/api/average_dataset/generic_estimate_sync_api.py,sha256=SRIs-qmb_wO7ELumeP1DLXJbVUtdBJ4CToecoCIsPAQ,8015
@@ -7,7 +7,7 @@ openepd/api/average_dataset/industry_epd_sync_api.py,sha256=b3oBjt-HIZuuHd6qDTJg
7
7
  openepd/api/base_sync_client.py,sha256=lVOn8d_EM6-Vwn99poRt1IEyLmDwatXDOH1D68F0LQ8,21739
8
8
  openepd/api/category/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
9
9
  openepd/api/category/dto.py,sha256=-nsSNH1zWIluFuF50qmnIcvloX08Jk_KV5mebJqzLe4,850
10
- openepd/api/category/sync_api.py,sha256=DVBosgq5iVkG8ttpuu8kUK8oNkWpGv322Tff9TMSi88,1376
10
+ openepd/api/category/sync_api.py,sha256=XU68fMQXSsfwfKkZvAlvBS78Z5hNwpXbO_Ffsh7LP0U,1381
11
11
  openepd/api/common.py,sha256=bLSER3RQQFR4lyEuxQPKNjTaa3S0K8fFi8x4QRAPM_g,8812
12
12
  openepd/api/dto/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
13
13
  openepd/api/dto/base.py,sha256=hZbzzBDPEN5-5Hhfcy7FgZj_Ya3g9oS7CgaQYhQiRwU,1041
@@ -33,22 +33,22 @@ openepd/m49/__init__.py,sha256=vkXC6i8OeCl5K88JUIRN_NKfsAIpNIaN-fyPmmd-Lfk,689
33
33
  openepd/m49/const.py,sha256=buspTHOOWhkBXWUIS0oqvQ0y1L7y70Y_PXilkiBIKD4,31819
34
34
  openepd/m49/utils.py,sha256=Qm8HvxAZlQncsyUYDO0lG0feMFFnT4HQPNSMp78W4zU,7111
35
35
  openepd/model/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
36
- openepd/model/base.py,sha256=7REXTR3MOBuxu7dg5b89rYCxU-pi6fABmOV43JZ2ZZA,10798
36
+ openepd/model/base.py,sha256=zSV_tgZZsd2x0jgmwfrrncGDTr0QINTDkPJDubk-W7Q,10811
37
37
  openepd/model/category.py,sha256=iyzzAsiVwW4zJ61oYsm9Sy-sEBA71-aMFXcJP1Y-dPI,1734
38
- openepd/model/common.py,sha256=YfPIJ_aVdTbAZPHC6uUgwjrlUO4kXiSQMul9fhqQEN0,14511
38
+ openepd/model/common.py,sha256=TxcLcYDgAgHtS-go8xYzPYnWTHLuE8bUebiVrXMXf5M,14544
39
39
  openepd/model/declaration.py,sha256=1NT3NhaYI_5u1K9ErfphOSFMY6r1-97qpBYNxuCmbs0,14687
40
- openepd/model/epd.py,sha256=fb674IUQtleUIbqK0C4qo7ZVP0G98WSdulP3_N2Ql9o,12576
40
+ openepd/model/epd.py,sha256=KACR_2tbbk0eltaL37MvDxqdJUDWXtiqYGW0_bWl7v4,12730
41
41
  openepd/model/factory.py,sha256=doexyqZXJVGd45T1JsbjuyGUjEykW-MerKUq4SQHhRY,2656
42
- openepd/model/generic_estimate.py,sha256=mdksGzDM92aANIgc1jhwdYKOvCju8aE1iJD3k3lQBwE,4140
42
+ openepd/model/generic_estimate.py,sha256=RGNrGNwaqY42m6nRrwmDZYO_Gy2EhOBZn6g0EAWiDHs,4224
43
43
  openepd/model/geography.py,sha256=Jx7NIDdk_sIvwyh-7YxnIjAwIHW2HCQK7UtFGM2xKtw,42095
44
44
  openepd/model/industry_epd.py,sha256=Cqn01IUNSZqRkyU05TwtOLXDKlg0YnGzqvKL8A__zbI,4061
45
- openepd/model/lcia.py,sha256=aa3IlbVCiC6dYVT3WqLMWBt3JzGOdeda8URC5L130tg,26377
46
- openepd/model/org.py,sha256=RrfjyEDOaC_Dyp4XNxQgq_FRtxN2ZQmjLpjuZOELrqs,5779
45
+ openepd/model/lcia.py,sha256=swKO8vAISpesYzF-w7HHLjckVhCb3jj3dv4FbIwp3Ak,27749
46
+ openepd/model/org.py,sha256=1ALm91OyJrvyFjkJ1hWQWGKO5fDdYQE8lMWEVZRUPrs,6348
47
47
  openepd/model/pcr.py,sha256=W_GjmMwFxZGLnbbU7khoYziSvOxxd6Id4xBdhitq_SE,5655
48
48
  openepd/model/specs/README.md,sha256=UGhSiFJ9hOxT1mZl-5ZrhkOrPKf1W_gcu5CI9hzV7LU,2430
49
49
  openepd/model/specs/__init__.py,sha256=YcAvn0h_Bhz_Cb4-uEl8DiBI82nXogQsocB-c2zvr_A,3094
50
50
  openepd/model/specs/asphalt.py,sha256=6jAZEBkB4hpwcSDvwVUGiNXgg3W3NElgdhS9caVpocI,3545
51
- openepd/model/specs/base.py,sha256=qHmQux01A1XGzjAtInti_4MS5HAKp1VQpKhC1EhUdc4,2682
51
+ openepd/model/specs/base.py,sha256=X_1TiMmTSSoOtZlbRl0jqzo1agX_QivQL4gToGNQeRY,2757
52
52
  openepd/model/specs/concrete.py,sha256=gjly5f7QEuaKnYZStMm0CisU0Us1PUa2HmxPyhhmuO0,5573
53
53
  openepd/model/specs/enums.py,sha256=_WMe0l8ZKPIGQG53A1rB_Sv_p2izr8Zemg9AEEYOU2g,63638
54
54
  openepd/model/specs/range/__init__.py,sha256=ZXgsyzn_c6H2SPCo5hTVZOjRqlCf0HvVVMDQ4RGaPSU,4502
@@ -67,7 +67,7 @@ openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py,
67
67
  openepd/model/specs/range/electricity.py,sha256=yn_S25C_tZqseb8hiJ1yiHszu2mQ49FOFWWCqNpOBY0,997
68
68
  openepd/model/specs/range/finishes.py,sha256=gJnMvjhivQC0jnKizKcrwn1Y7UJjrK_emixigQhbbfE,21792
69
69
  openepd/model/specs/range/fire_and_smoke_protection.py,sha256=KLH5t3d-a2cppF5i-uW4233z1vAPbJIY1zTn89_gV4k,3333
70
- openepd/model/specs/range/furnishings.py,sha256=SPZDMacVvXHA_zcE4l3fL5hroZWaEeSsql0oSn3BnBo,6576
70
+ openepd/model/specs/range/furnishings.py,sha256=LWu88Y7QEuq7IdNCKQG5v0u0PAWpG919gxkMdmm5Sig,6748
71
71
  openepd/model/specs/range/grouting.py,sha256=BQPxH6BvlXpdhLEZBui9zTuY93K_9syjoa6rdnF8GGY,1095
72
72
  openepd/model/specs/range/manufacturing_inputs.py,sha256=iqK4QS-T5vyjMQtl5v63Feb99qSNwKS0kM-1izoJTzA,5615
73
73
  openepd/model/specs/range/masonry.py,sha256=egeqDr0K3eTFDHeIzFHrg9_u9d6XIHOSmBPnBERkvxY,2908
@@ -81,7 +81,7 @@ openepd/model/specs/range/other_materials.py,sha256=k7Gv6YvWy43B-QFBabBt-TkvkC45
81
81
  openepd/model/specs/range/plumbing.py,sha256=GS-q4nMOaE1aoPwHmDXEjlG7xuZOl0veovZraSSQcc4,5382
82
82
  openepd/model/specs/range/precast_concrete.py,sha256=9bNluxpGuQ_7HqA5rpvg9FJHeMyrGSIH-c1M9Mzz2qQ,3820
83
83
  openepd/model/specs/range/sheathing.py,sha256=FOosFJ8fsyqQnlLTgEqxM4xzDtiYP2jQ7dCf2T4K1O4,3446
84
- openepd/model/specs/range/steel.py,sha256=XEf5jjfiN8uZlY3QoIccGgI1RRxNPaT7eHZE_YFt95o,10503
84
+ openepd/model/specs/range/steel.py,sha256=T4vk8g9X2PUbkm8pfjAvcZ7Y0iN7ErpNZapp89l0lIg,10547
85
85
  openepd/model/specs/range/thermal_moisture_protection.py,sha256=BOZZTNA9mHTaImN4J484LYnYTvv3joBX5OkH6LGBklc,9470
86
86
  openepd/model/specs/range/utility_piping.py,sha256=doRcQRfBWvHX7FDDYk-bEU5rVCS_vjZ_5gKslI0xR7Y,2671
87
87
  openepd/model/specs/range/wood.py,sha256=0Prq5n3n_-DEFe4-fBKDj9zCu2NdBD7ysCun7DRhT_o,6942
@@ -106,7 +106,7 @@ openepd/model/specs/singular/electrical_transmission_and_distribution_equipment.
106
106
  openepd/model/specs/singular/electricity.py,sha256=f1cJ9eklNUf97khCIZiT5-z8V_TbOcvH2me_1UTQVrE,823
107
107
  openepd/model/specs/singular/finishes.py,sha256=8BtFK2_olB8OJQFv8rxf7afoLhqlJI1o2tEKopidOGA,24398
108
108
  openepd/model/specs/singular/fire_and_smoke_protection.py,sha256=h8Dy4ps_xfa5g8EQEIN8OHxjH6aDvqmZUFRdEzYy7aY,3081
109
- openepd/model/specs/singular/furnishings.py,sha256=6MZT42b-nlZdQvvZw1_tVg5GFlMQ2gw_r6zBjxMtxHc,5608
109
+ openepd/model/specs/singular/furnishings.py,sha256=-8_96GZala4_xuI6u29OO0KfGA0OctsD3M5Iid2NHvE,5737
110
110
  openepd/model/specs/singular/grouting.py,sha256=pg2tX3W7a2TQ3z_eyFYGlBJY3WEwn6JlZyqM3PQIJcU,934
111
111
  openepd/model/specs/singular/manufacturing_inputs.py,sha256=OBy7Lzm1TbC8kGxE3YXIXeyrqIPqf2_rXcGEN7MEMBw,5233
112
112
  openepd/model/specs/singular/masonry.py,sha256=e9siblrPFFSvIjucYkwZzSlpSSZPZ9rA1bdVIDQz-RM,3627
@@ -132,11 +132,10 @@ openepd/model/validation/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA
132
132
  openepd/model/validation/common.py,sha256=ZlpByfGzCHZuGfj9p0S_uG8NoGy9ezJVZmwAITp7gR8,2450
133
133
  openepd/model/validation/enum.py,sha256=ttODVtMEQaBemMFywGUNlXU4bAM25FiBlVDKZbOv2iY,1793
134
134
  openepd/model/validation/numbers.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
135
- openepd/model/validation/quantity.py,sha256=ots09u4406szY1k1_LVEOxK30fyEWnxml8Z0LlgAbnE,21044
135
+ openepd/model/validation/quantity.py,sha256=zsyNGygYW4GGeWAtyM9708lFdEuebtSctr3qIfNdEXQ,21720
136
136
  openepd/model/versioning.py,sha256=de53OlduidxAkDlSbAu6xrIy2IUuCnwI69vUJPCXX_8,4512
137
- openepd/patch_pydantic.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
138
137
  openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
139
- openepd-7.0.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
140
- openepd-7.0.0.dist-info/METADATA,sha256=f7Da4D9bA9aa394xf_QcKXu0yFnX_oZyjIuiEwFKbrA,9038
141
- openepd-7.0.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
142
- openepd-7.0.0.dist-info/RECORD,,
138
+ openepd-7.1.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
139
+ openepd-7.1.0.dist-info/METADATA,sha256=xmx2tvBuvOMpEzJ_ViXm82pnWkZQ1_XqqnaBBMo11So,9137
140
+ openepd-7.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
141
+ openepd-7.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.1
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
openepd/patch_pydantic.py DELETED
@@ -1,15 +0,0 @@
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
- #