openepd 6.13.1__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 (102) 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/m49/__init__.py +2 -0
  18. openepd/m49/const.py +5 -2
  19. openepd/m49/{geo_converter.py → utils.py} +24 -2
  20. openepd/model/base.py +60 -43
  21. openepd/model/category.py +13 -10
  22. openepd/model/common.py +100 -55
  23. openepd/model/declaration.py +93 -64
  24. openepd/model/epd.py +51 -43
  25. openepd/model/generic_estimate.py +28 -13
  26. openepd/model/industry_epd.py +15 -9
  27. openepd/model/lcia.py +132 -113
  28. openepd/model/org.py +54 -33
  29. openepd/model/pcr.py +38 -32
  30. openepd/model/specs/asphalt.py +31 -22
  31. openepd/model/specs/base.py +11 -9
  32. openepd/model/specs/concrete.py +60 -39
  33. openepd/model/specs/range/aggregates.py +9 -9
  34. openepd/model/specs/range/aluminium.py +7 -7
  35. openepd/model/specs/range/asphalt.py +22 -19
  36. openepd/model/specs/range/cladding.py +16 -16
  37. openepd/model/specs/range/cmu.py +10 -9
  38. openepd/model/specs/range/concrete.py +36 -27
  39. openepd/model/specs/range/conveying_equipment.py +16 -15
  40. openepd/model/specs/range/electrical.py +24 -22
  41. openepd/model/specs/range/finishes.py +109 -104
  42. openepd/model/specs/range/fire_and_smoke_protection.py +7 -7
  43. openepd/model/specs/range/furnishings.py +16 -12
  44. openepd/model/specs/range/manufacturing_inputs.py +16 -16
  45. openepd/model/specs/range/masonry.py +16 -16
  46. openepd/model/specs/range/mechanical.py +47 -47
  47. openepd/model/specs/range/mechanical_insulation.py +7 -7
  48. openepd/model/specs/range/network_infrastructure.py +54 -46
  49. openepd/model/specs/range/openings.py +36 -31
  50. openepd/model/specs/range/plumbing.py +15 -13
  51. openepd/model/specs/range/precast_concrete.py +20 -16
  52. openepd/model/specs/range/sheathing.py +18 -18
  53. openepd/model/specs/range/steel.py +25 -25
  54. openepd/model/specs/range/thermal_moisture_protection.py +20 -20
  55. openepd/model/specs/range/utility_piping.py +9 -9
  56. openepd/model/specs/range/wood.py +19 -19
  57. openepd/model/specs/range/wood_joists.py +8 -8
  58. openepd/model/specs/singular/__init__.py +9 -5
  59. openepd/model/specs/singular/aggregates.py +22 -15
  60. openepd/model/specs/singular/aluminium.py +20 -5
  61. openepd/model/specs/singular/asphalt.py +44 -20
  62. openepd/model/specs/singular/cladding.py +38 -23
  63. openepd/model/specs/singular/cmu.py +26 -11
  64. openepd/model/specs/singular/common.py +3 -2
  65. openepd/model/specs/singular/concrete.py +85 -48
  66. openepd/model/specs/singular/conveying_equipment.py +30 -17
  67. openepd/model/specs/singular/deprecated/__init__.py +3 -2
  68. openepd/model/specs/singular/deprecated/concrete.py +68 -33
  69. openepd/model/specs/singular/deprecated/steel.py +28 -15
  70. openepd/model/specs/singular/electrical.py +69 -41
  71. openepd/model/specs/singular/finishes.py +250 -140
  72. openepd/model/specs/singular/fire_and_smoke_protection.py +9 -6
  73. openepd/model/specs/singular/furnishings.py +16 -14
  74. openepd/model/specs/singular/manufacturing_inputs.py +23 -14
  75. openepd/model/specs/singular/masonry.py +66 -21
  76. openepd/model/specs/singular/mechanical.py +48 -47
  77. openepd/model/specs/singular/mechanical_insulation.py +7 -6
  78. openepd/model/specs/singular/mixins/conduit_mixin.py +13 -10
  79. openepd/model/specs/singular/network_infrastructure.py +111 -52
  80. openepd/model/specs/singular/openings.py +127 -95
  81. openepd/model/specs/singular/plumbing.py +15 -12
  82. openepd/model/specs/singular/precast_concrete.py +68 -54
  83. openepd/model/specs/singular/sheathing.py +47 -27
  84. openepd/model/specs/singular/steel.py +69 -45
  85. openepd/model/specs/singular/thermal_moisture_protection.py +36 -20
  86. openepd/model/specs/singular/utility_piping.py +11 -8
  87. openepd/model/specs/singular/wood.py +48 -24
  88. openepd/model/specs/singular/wood_joists.py +19 -6
  89. openepd/model/standard.py +15 -8
  90. openepd/model/validation/common.py +9 -3
  91. openepd/model/validation/numbers.py +0 -13
  92. openepd/model/validation/quantity.py +53 -25
  93. openepd/model/versioning.py +9 -6
  94. openepd/patch_pydantic.py +0 -93
  95. {openepd-6.13.1.dist-info → openepd-7.0.0.dist-info}/METADATA +1 -1
  96. openepd-7.0.0.dist-info/RECORD +142 -0
  97. openepd/compat/__init__.py +0 -15
  98. openepd/compat/compat_functional_validators.py +0 -25
  99. openepd/compat/pydantic.py +0 -30
  100. openepd-6.13.1.dist-info/RECORD +0 -145
  101. {openepd-6.13.1.dist-info → openepd-7.0.0.dist-info}/LICENSE +0 -0
  102. {openepd-6.13.1.dist-info → openepd-7.0.0.dist-info}/WHEEL +0 -0
openepd/model/org.py CHANGED
@@ -13,11 +13,12 @@
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, Optional
16
+ from typing import Annotated, List, Optional
17
17
 
18
18
  from openlocationcode import openlocationcode
19
+ import pydantic
20
+ from pydantic import ConfigDict, Field, StringConstraints
19
21
 
20
- from openepd.compat.pydantic import pyd
21
22
  from openepd.model.base import BaseOpenEpdSchema
22
23
  from openepd.model.common import Location, WithAltIdsMixin, WithAttachmentsMixin
23
24
  from openepd.model.validation.common import ReferenceStr
@@ -26,18 +27,19 @@ from openepd.model.validation.common import ReferenceStr
26
27
  class OrgRef(BaseOpenEpdSchema):
27
28
  """Represents Organisation with minimal data."""
28
29
 
29
- web_domain: str | None = pyd.Field(
30
- description="A web domain owned by organization. Typically is the org's home website address", default=None
30
+ web_domain: str | None = pydantic.Field(
31
+ description="A web domain owned by organization. Typically is the org's home website address",
32
+ default=None,
31
33
  )
32
- name: str | None = pyd.Field(
34
+ name: str | None = pydantic.Field(
33
35
  max_length=200,
34
36
  description="Common name for organization",
35
- example="C Change Labs",
37
+ examples=["C Change Labs"],
36
38
  default=None,
37
39
  )
38
- ref: ReferenceStr | None = pyd.Field(
40
+ ref: ReferenceStr | None = pydantic.Field(
39
41
  default=None,
40
- example="https://openepd.buildingtransparency.org/api/orgs/c-change-labs.com",
42
+ examples=["https://openepd.buildingtransparency.org/api/orgs/c-change-labs.com"],
41
43
  description="Reference to this Org's JSON object",
42
44
  )
43
45
 
@@ -45,19 +47,37 @@ class OrgRef(BaseOpenEpdSchema):
45
47
  class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
46
48
  """Represent an organization."""
47
49
 
48
- alt_names: Annotated[list[str], pyd.conlist(pyd.constr(max_length=200), max_items=255)] | None = pyd.Field(
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(
49
60
  description="List of other names for organization",
50
- example=["C-Change Labs", "C-Change Labs inc."],
61
+ examples=[["C-Change Labs", "C-Change Labs inc."]],
51
62
  default=None,
52
63
  )
53
64
  # TODO: NEW field, not in the spec
54
65
 
55
- owner: Optional["OrgRef"] = pyd.Field(description="Organization that controls this organization", default=None)
56
- subsidiaries: Annotated[list["OrgRef"], pyd.conlist(pyd.constr(max_length=200), max_items=255)] | None = pyd.Field(
66
+ 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(
57
77
  description="Organizations controlled by this organization",
58
78
  default=None,
59
79
  )
60
- hq_location: Location | None = pyd.Field(
80
+ hq_location: Location | None = pydantic.Field(
61
81
  default=None,
62
82
  description="Location of a place of business, preferably the corporate headquarters.",
63
83
  )
@@ -66,21 +86,21 @@ class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
66
86
  class PlantRef(BaseOpenEpdSchema):
67
87
  """Represents Plant with minimal data."""
68
88
 
69
- id: str | None = pyd.Field(
89
+ id: str | None = pydantic.Field(
70
90
  description="Plus code (aka Open Location Code) of plant's location and "
71
91
  "owner's web domain joined with `.`(dot).",
72
- example="865P2W3V+3W.interface.com",
92
+ examples=["865P2W3V+3W.interface.com"],
73
93
  default=None,
74
94
  )
75
- name: str | None = pyd.Field(
95
+ name: str | None = pydantic.Field(
76
96
  max_length=200,
77
97
  description="Manufacturer's name for plant. Recommended < 40 chars",
78
- example="Dalton, GA",
98
+ examples=["Dalton, GA"],
79
99
  default=None,
80
100
  )
81
- ref: ReferenceStr | None = pyd.Field(
101
+ ref: ReferenceStr | None = pydantic.Field(
82
102
  default=None,
83
- example="https://openepd.buildingtransparency.org/api/orgs/c-change-labs.com",
103
+ examples=["https://openepd.buildingtransparency.org/api/orgs/c-change-labs.com"],
84
104
  description="Reference to this Plant's JSON object",
85
105
  )
86
106
 
@@ -88,36 +108,38 @@ class PlantRef(BaseOpenEpdSchema):
88
108
  class Plant(PlantRef, WithAttachmentsMixin, WithAltIdsMixin):
89
109
  """Represent a manufacturing plant."""
90
110
 
91
- pluscode: str | None = pyd.Field(
111
+ pluscode: str | None = pydantic.Field(
92
112
  default=None,
93
113
  description="(deprecated) Plus code (aka Open Location Code) of plant's location",
94
114
  deprecated="Pluscode field is deprecated. If users need a pluscode they can obtain it from "
95
115
  "`id` like this: `id.spit('.', maxsplit=1)[0]`",
96
116
  )
97
- latitude: float | None = pyd.Field(
98
- default=None, description="(deprecated) Latitude of the plant location. Use 'location' fields instead."
117
+ latitude: float | None = pydantic.Field(
118
+ default=None,
119
+ description="(deprecated) Latitude of the plant location. Use 'location' fields instead.",
99
120
  )
100
- longitude: float | None = pyd.Field(
101
- default=None, description="(deprecated) Longitude of the plant location. Use 'location' fields instead."
121
+ longitude: float | None = pydantic.Field(
122
+ default=None,
123
+ description="(deprecated) Longitude of the plant location. Use 'location' fields instead.",
102
124
  )
103
- owner: Org | None = pyd.Field(description="Organization that owns the plant", default=None)
104
- address: str | None = pyd.Field(
125
+ owner: Org | None = pydantic.Field(description="Organization that owns the plant", default=None)
126
+ address: str | None = pydantic.Field(
105
127
  max_length=200,
106
128
  default=None,
107
129
  description="(deprecated) Text address, preferably geocoded. Use 'location' fields instead",
108
- example="1503 Orchard Hill Rd, LaGrange, GA 30240, United States",
130
+ examples=["1503 Orchard Hill Rd, LaGrange, GA 30240, United States"],
109
131
  )
110
- contact_email: pyd.EmailStr | None = pyd.Field(
111
- description="Email contact", example="info@interface.com", default=None
132
+ contact_email: pydantic.EmailStr | None = pydantic.Field(
133
+ description="Email contact", examples=["info@interface.com"], default=None
112
134
  )
113
- location: Location | None = pyd.Field(description="Location of the plant", default=None)
135
+ location: Location | None = pydantic.Field(description="Location of the plant", default=None)
114
136
 
115
137
  @classmethod
116
138
  def get_asset_type(cls) -> str | None:
117
139
  """Return the asset type of this class (see BaseOpenEpdSchema.get_asset_type for details)."""
118
140
  return "org"
119
141
 
120
- @pyd.validator("id")
142
+ @pydantic.field_validator("id")
121
143
  def _validate_id(cls, v: str) -> str:
122
144
  if not v:
123
145
  return v
@@ -133,5 +155,4 @@ class Plant(PlantRef, WithAttachmentsMixin, WithAltIdsMixin):
133
155
  raise ValueError("Incorrect web_domain for plant")
134
156
  return v
135
157
 
136
- class Config(BaseOpenEpdSchema.Config):
137
- allow_population_by_field_name = True
158
+ model_config = ConfigDict(populate_by_name=True)
openepd/model/pcr.py CHANGED
@@ -17,7 +17,8 @@ import datetime
17
17
  from enum import StrEnum
18
18
  from typing import Annotated, Optional
19
19
 
20
- from openepd.compat.pydantic import pyd
20
+ import pydantic
21
+
21
22
  from openepd.model.base import BaseOpenEpdSchema, OpenXpdUUID
22
23
  from openepd.model.common import Amount, WithAltIdsMixin, WithAttachmentsMixin
23
24
  from openepd.model.org import Org
@@ -36,87 +37,92 @@ class PcrStatus(StrEnum):
36
37
  class PcrRef(BaseOpenEpdSchema):
37
38
  """Reference to a PCR."""
38
39
 
39
- id: OpenXpdUUID | None = pyd.Field(
40
+ id: OpenXpdUUID | None = pydantic.Field(
40
41
  description="The unique ID for this PCR. To ensure global uniqueness, should be registered "
41
42
  "at open-xpd-uuid.cqd.io/register or a coordinating registry.",
42
- example="ec3xpgq2",
43
+ examples=["ec3xpgq2"],
43
44
  default=None,
44
45
  )
45
- name: str | None = pyd.Field(
46
+ name: str | None = pydantic.Field(
46
47
  max_length=200,
47
48
  description="Full document name as listed in source document",
48
- example="c-PCR-003 Concrete and concrete elements (EN 16757)",
49
+ examples=["c-PCR-003 Concrete and concrete elements (EN 16757)"],
49
50
  )
50
- ref: pyd.AnyUrl | None = pyd.Field(
51
+ ref: pydantic.AnyUrl | None = pydantic.Field(
51
52
  description="Reference to this PCR's JSON object",
52
- example="https://openepd.buildingtransparency.org/api/pcrs/1u7zsed8",
53
+ examples=["https://openepd.buildingtransparency.org/api/pcrs/1u7zsed8"],
53
54
  )
54
55
 
55
56
 
56
57
  class Pcr(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
57
58
  """Represent a PCR (Product Category Rules)."""
58
59
 
59
- id: OpenXpdUUID | None = pyd.Field(
60
+ id: OpenXpdUUID | None = pydantic.Field(
60
61
  description="The unique ID for this PCR. To ensure global uniqueness, should be registered "
61
62
  "at open-xpd-uuid.cqd.io/register or a coordinating registry.",
62
- example="ec3xpgq2",
63
+ examples=["ec3xpgq2"],
63
64
  default=None,
64
65
  )
65
- issuer: Org | None = pyd.Field(description="Organization issuing this PCR", default=None)
66
- issuer_doc_id: str | None = pyd.Field(
66
+ issuer: Org | None = pydantic.Field(description="Organization issuing this PCR", default=None)
67
+ issuer_doc_id: str | None = pydantic.Field(
67
68
  max_length=40,
68
69
  default=None,
69
70
  description="Document ID or code created by issuer",
70
- example="c-PCR-003",
71
+ examples=["c-PCR-003"],
71
72
  )
72
- name: str | None = pyd.Field(
73
+ name: str | None = pydantic.Field(
73
74
  max_length=200,
74
75
  default=None,
75
76
  description="Full document name as listed in source document",
76
- example="c-PCR-003 Concrete and concrete elements (EN 16757)",
77
+ examples=["c-PCR-003 Concrete and concrete elements (EN 16757)"],
77
78
  )
78
- short_name: str | None = pyd.Field(
79
+ short_name: str | None = pydantic.Field(
79
80
  default=None,
80
81
  description="A shortened name without boilerplate text.",
81
- example="Concrete and Concrete Elements",
82
+ examples=["Concrete and Concrete Elements"],
82
83
  )
83
- declared_units: list[Amount] | None = pyd.Field(
84
+ declared_units: list[Amount] | None = pydantic.Field(
84
85
  description="SI declared units for this PCR. If a functional unit is "
85
86
  "utilized, the declared unit shall refer to the amount of "
86
- "product associated with the A1-A3 life cycle stage."
87
+ "product associated with the A1-A3 life cycle stage.",
88
+ default=None,
87
89
  )
88
- version: str | None = pyd.Field(
90
+ version: str | None = pydantic.Field(
89
91
  description="Document version, as expressed in document.",
90
- example="1.0.2",
92
+ examples=["1.0.2"],
91
93
  default=None,
92
94
  )
93
- date_of_issue: datetime.datetime | None = pyd.Field(
94
- example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
95
+ date_of_issue: datetime.datetime | None = pydantic.Field(
96
+ examples=[datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc)],
95
97
  default=None,
96
98
  description="First day on which the document is valid",
97
99
  )
98
- valid_until: datetime.datetime | None = pyd.Field(
99
- example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
100
+ valid_until: datetime.datetime | None = pydantic.Field(
101
+ examples=[datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc)],
100
102
  default=None,
101
103
  description="Last day on which the document is valid",
102
104
  )
103
- doc: str | None = pyd.Field(default=None, description="URL to original document, preferably directly to a PDF.")
104
- parent: Optional["Pcr"] = pyd.Field(
105
+ doc: str | None = pydantic.Field(
106
+ default=None,
107
+ description="URL to original document, preferably directly to a PDF.",
108
+ )
109
+ parent: Optional["Pcr"] = pydantic.Field(
105
110
  description="The parent PCR, base PCR, `Part A` PCR",
106
111
  default=None,
107
112
  )
108
- status: PcrStatus | None = pyd.Field(
113
+ status: PcrStatus | None = pydantic.Field(
109
114
  default=None,
110
115
  description="The current release status of this PCR. "
111
116
  "A PCR with valid_until in the past must have status Expired or Sunset; a PCR with valid_until "
112
117
  "more than 5 years in the past must have status Sunset. Compliant systems should automatically "
113
118
  "update these fields within 24 hours.",
114
119
  )
115
- product_classes: dict[str, str | list[str]] = pyd.Field(
116
- description="List of classifications, including Masterformat and UNSPC", default_factory=dict
120
+ product_classes: dict[str, str | list[str]] = pydantic.Field(
121
+ description="List of classifications, including Masterformat and UNSPC",
122
+ default_factory=dict,
117
123
  )
118
- applicable_in: list[Annotated[str, pyd.Field(min_length=2, max_length=2)]] | None = pyd.Field(
119
- max_items=100,
124
+ applicable_in: list[Annotated[str, pydantic.Field(min_length=2, max_length=2)]] | None = pydantic.Field(
125
+ max_length=100,
120
126
  default=None,
121
127
  description="Jurisdiction(s) in which EPD is applicable. An empty array, or absent properties, "
122
128
  "implies global applicability. Accepts "
@@ -124,7 +130,7 @@ class Pcr(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
124
130
  "[M49 region codes](https://unstats.un.org/unsd/methodology/m49/), "
125
131
  'or the alias "EU27" for the 27 members of the Euro bloc, or the alias "NAFTA" '
126
132
  "for the members of North American Free Trade Agreement",
127
- example=["US", "CA", "MX", "EU27", "NAFTA"],
133
+ examples=[["US", "CA", "MX", "EU27", "NAFTA"]],
128
134
  )
129
135
 
130
136
  @classmethod
@@ -15,10 +15,10 @@
15
15
  #
16
16
  from enum import StrEnum
17
17
 
18
- from openepd.compat.pydantic import pyd
18
+ import pydantic
19
+
19
20
  from openepd.model.common import OpenEPDUnit
20
21
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
21
- from openepd.model.validation.numbers import RatioFloat
22
22
  from openepd.model.validation.quantity import LengthMmStr, TemperatureCStr, validate_quantity_unit_factory
23
23
 
24
24
 
@@ -45,42 +45,51 @@ class AsphaltV1(BaseOpenEpdHierarchicalSpec):
45
45
 
46
46
  _EXT_VERSION = "1.1"
47
47
 
48
- asphalt_aggregate_size_max: LengthMmStr | None = pyd.Field(
49
- default=None, example="5mm", description="Max aggregate size"
48
+ asphalt_aggregate_size_max: LengthMmStr | None = pydantic.Field(
49
+ default=None, examples=["5mm"], description="Max aggregate size"
50
50
  )
51
51
 
52
- asphalt_rap: RatioFloat | None = pyd.Field(
53
- default=None, description="Percent of mixture that has been replaced by recycled " "asphalt pavement (RAP)."
52
+ asphalt_rap: float | None = pydantic.Field(
53
+ default=None,
54
+ description="Percent of mixture that has been replaced by recycled " "asphalt pavement (RAP).",
55
+ ge=0,
56
+ le=1,
54
57
  )
55
- asphalt_ras: RatioFloat | None = pyd.Field(
56
- default=None, description="Percent of mixture that has been replaced by recycled " "asphalt shingles (RAS)."
58
+ asphalt_ras: float | None = pydantic.Field(
59
+ default=None,
60
+ description="Percent of mixture that has been replaced by recycled " "asphalt shingles (RAS).",
61
+ ge=0,
62
+ le=1,
57
63
  )
58
- asphalt_ground_tire_rubber: RatioFloat | None = pyd.Field(
59
- default=None, description="Percent of mixture that has been replaced " "by ground tire rubber (GTR)."
64
+ asphalt_ground_tire_rubber: float | None = pydantic.Field(
65
+ default=None,
66
+ description="Percent of mixture that has been replaced " "by ground tire rubber (GTR).",
67
+ ge=0,
68
+ le=1,
60
69
  )
61
70
 
62
- asphalt_max_temperature: TemperatureCStr | None = pyd.Field(
71
+ asphalt_max_temperature: TemperatureCStr | None = pydantic.Field(
63
72
  default=None,
64
73
  description="The upper threshold temperature to which an asphalt "
65
74
  "binder can be heated preventing the asphalt mixture "
66
75
  "from rutting",
67
76
  )
68
- asphalt_min_temperature: TemperatureCStr | None = pyd.Field(
77
+ asphalt_min_temperature: TemperatureCStr | None = pydantic.Field(
69
78
  default=None,
70
79
  description="The lower threshold temperature for an asphalt "
71
80
  "binder to prevent thermal cracking of the asphalt"
72
81
  " mixture.",
73
82
  )
74
83
 
75
- asphalt_mix_type: AsphaltMixType | None = pyd.Field(default=None, description="Asphalt mix type")
76
- asphalt_gradation: AsphaltGradation | None = pyd.Field(default=None, description="Asphalt gradation")
84
+ asphalt_mix_type: AsphaltMixType | None = pydantic.Field(default=None, description="Asphalt mix type")
85
+ asphalt_gradation: AsphaltGradation | None = pydantic.Field(default=None, description="Asphalt gradation")
77
86
 
78
- asphalt_sbr: bool | None = pyd.Field(default=None, description="Styrene-butadiene rubber (SBR)")
79
- asphalt_sbs: bool | None = pyd.Field(default=None, description="Styrene-butadiene-styrene (SBS)")
80
- asphalt_ppa: bool | None = pyd.Field(default=None, description="Polyphosphoric acid (PPA)")
81
- asphalt_gtr: bool | None = pyd.Field(default=None, description="Ground tire rubber (GTR)")
82
- asphalt_pmb: bool | None = pyd.Field(default=None, description="Polymer modified bitumen (PMB)")
87
+ asphalt_sbr: bool | None = pydantic.Field(default=None, description="Styrene-butadiene rubber (SBR)")
88
+ asphalt_sbs: bool | None = pydantic.Field(default=None, description="Styrene-butadiene-styrene (SBS)")
89
+ asphalt_ppa: bool | None = pydantic.Field(default=None, description="Polyphosphoric acid (PPA)")
90
+ asphalt_gtr: bool | None = pydantic.Field(default=None, description="Ground tire rubber (GTR)")
91
+ asphalt_pmb: bool | None = pydantic.Field(default=None, description="Polymer modified bitumen (PMB)")
83
92
 
84
- _aggregate_size_max_validator = pyd.validator("asphalt_aggregate_size_max", allow_reuse=True)(
85
- validate_quantity_unit_factory(OpenEPDUnit.m)
86
- )
93
+ @pydantic.field_validator("asphalt_aggregate_size_max", mode="before")
94
+ def _aggregate_size_max_validator(cls, value):
95
+ return validate_quantity_unit_factory(OpenEPDUnit.m)(cls, value)
@@ -17,7 +17,9 @@ import dataclasses
17
17
  from types import UnionType
18
18
  from typing import Any
19
19
 
20
- from openepd.compat.pydantic import pyd
20
+ import pydantic
21
+ from pydantic import ConfigDict
22
+
21
23
  from openepd.model.base import BaseOpenEpdSchema, Version
22
24
  from openepd.model.validation.common import validate_version_compatibility, validate_version_format
23
25
  from openepd.model.validation.quantity import ExternalValidationConfig, QuantityValidator
@@ -27,8 +29,7 @@ from openepd.model.versioning import WithExtVersionMixin
27
29
  class BaseOpenEpdSpec(BaseOpenEpdSchema):
28
30
  """Base class for all OpenEPD specs."""
29
31
 
30
- class Config:
31
- use_enum_values = False # we need to store enums as strings and not values
32
+ model_config = ConfigDict(use_enum_values=False)
32
33
 
33
34
 
34
35
  class BaseOpenEpdHierarchicalSpec(BaseOpenEpdSpec, WithExtVersionMixin):
@@ -42,12 +43,13 @@ class BaseOpenEpdHierarchicalSpec(BaseOpenEpdSpec, WithExtVersionMixin):
42
43
  Version.parse_version(self._EXT_VERSION) # validate format correctness
43
44
  super().__init__(**{"ext_version": self._EXT_VERSION, **data})
44
45
 
45
- _version_format_validator = pyd.validator("ext_version", allow_reuse=True, check_fields=False)(
46
- validate_version_format
47
- )
48
- _version_major_match_validator = pyd.validator("ext_version", allow_reuse=True, check_fields=False)(
49
- validate_version_compatibility("_EXT_VERSION")
50
- )
46
+ @pydantic.field_validator("ext_version", mode="before")
47
+ def _version_format_validator(cls, v: str) -> str:
48
+ return validate_version_format(v)
49
+
50
+ @pydantic.field_validator("ext_version", mode="before")
51
+ def _version_major_match_validator(cls, v: str) -> str:
52
+ return validate_version_compatibility("_EXT_VERSION")(cls, v) # type: ignore[arg-type]
51
53
 
52
54
 
53
55
  def setup_external_validators(quantity_validator: QuantityValidator):
@@ -13,98 +13,119 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ import pydantic
16
17
 
17
- from openepd.compat.pydantic import pyd
18
18
  from openepd.model.base import BaseOpenEpdSchema
19
19
  from openepd.model.specs.base import BaseOpenEpdSpec
20
- from openepd.model.validation.numbers import RatioFloat
21
20
 
22
21
 
23
22
  class ConcreteTypicalApplication(BaseOpenEpdSpec):
24
23
  """Typical Application for Concrete."""
25
24
 
26
- fnd: bool | None = pyd.Field(
25
+ fnd: bool | None = pydantic.Field(
27
26
  description="Foundation. Typically used in direct contact with soil, e.g. footings, piles, mass concrete, "
28
27
  "mat foundations, and similar applications.",
29
- example=True,
28
+ examples=[True],
30
29
  default=None,
31
30
  )
32
- sog: bool | None = pyd.Field(
31
+ sog: bool | None = pydantic.Field(
33
32
  description="Slab on Grade. Typically used in continuously supported horizontal "
34
33
  "applications e.g. slab on grade, topping slabs, sidewalks, and roadways.",
35
- example=True,
34
+ examples=[True],
36
35
  default=None,
37
36
  )
38
- hrz: bool | None = pyd.Field(
37
+ hrz: bool | None = pydantic.Field(
39
38
  description="Elevated Horizontal. Typically used in elevated horizontal applications, either on metal deck or "
40
39
  "where soffit formwork must be removed, e.g. post-tension plates, rebar plates, beams and slabs, "
41
40
  "waffle slabs.",
42
- example=True,
41
+ examples=[True],
43
42
  default=None,
44
43
  )
45
- vrt_wall: bool | None = pyd.Field(description="Vertical Wall.", example=True, default=None)
46
- vrt_column: bool | None = pyd.Field(description="Vertical Column.", example=True, default=None)
47
- vrt_other: bool | None = pyd.Field(
44
+ vrt_wall: bool | None = pydantic.Field(description="Vertical Wall.", examples=[True], default=None)
45
+ vrt_column: bool | None = pydantic.Field(description="Vertical Column.", examples=[True], default=None)
46
+ vrt_other: bool | None = pydantic.Field(
48
47
  description="Vertical Other. Typically used in vertical applications other than "
49
48
  "walls or columns, e.g. sloped surfaces where formwork is required "
50
49
  "on multiple faces.",
51
- example=True,
50
+ examples=[True],
52
51
  default=None,
53
52
  )
54
- sht: bool | None = pyd.Field(
55
- description="Shotcrete. Pneumatically applied, without formwork on all sides.", example=True, default=None
53
+ sht: bool | None = pydantic.Field(
54
+ description="Shotcrete. Pneumatically applied, without formwork on all sides.",
55
+ examples=[True],
56
+ default=None,
56
57
  )
57
- cdf: bool | None = pyd.Field(
58
+ cdf: bool | None = pydantic.Field(
58
59
  description="Flowable Fill (CDF). Typically used to fill voids, backfill retaining "
59
60
  "walls, as a sub-base, and similar applications. Also called Controlled "
60
61
  "Density Fill (CDF) or Controlled Low Strength Materials (CLSM).",
61
- example=True,
62
+ examples=[True],
62
63
  default=None,
63
64
  )
64
- sac: bool | None = pyd.Field(
65
- description="Typically used in concrete sidewalks and barrier curbs.", example=True, default=None
65
+ sac: bool | None = pydantic.Field(
66
+ description="Typically used in concrete sidewalks and barrier curbs.",
67
+ examples=[True],
68
+ default=None,
66
69
  )
67
- pav: bool | None = pyd.Field(description="Typically used in pervious concrete", example=True, default=None)
68
- oil: bool | None = pyd.Field(
70
+ pav: bool | None = pydantic.Field(description="Typically used in pervious concrete", examples=[True], default=None)
71
+ oil: bool | None = pydantic.Field(
69
72
  description="Concretes for use in creation, maintenance, and decommissioning of "
70
73
  "petroleum extraction wells and similar applications. Includes foamed "
71
74
  "cement; often called cement in the drilling industry. Differs from "
72
75
  "flowable fill and grout in that it contains no sand or other aggregates.",
73
- example=True,
76
+ examples=[True],
74
77
  default=None,
75
78
  )
76
- grt: bool | None = pyd.Field(
79
+ grt: bool | None = pydantic.Field(
77
80
  description="Cement grouting is a slurry that is placed as a flowable liquid. It is "
78
81
  "an effective material for filling and strengthening granular soils, "
79
82
  "voids in rocks, foundation underpinnings, and other underground voids. "
80
83
  "Also called structural grout, these materials typically impart"
81
84
  " significant strength to the system",
82
- example=True,
85
+ examples=[True],
83
86
  default=None,
84
87
  )
85
- ota: bool | None = pyd.Field(
86
- description="Typical application not covered by other values.", example=True, default=None
88
+ ota: bool | None = pydantic.Field(
89
+ description="Typical application not covered by other values.",
90
+ examples=[True],
91
+ default=None,
87
92
  )
88
93
 
89
94
 
90
95
  class Cementitious(BaseOpenEpdSchema):
91
96
  """List of cementitious materials, and proportion by mass."""
92
97
 
93
- opc: RatioFloat | None = pyd.Field(
94
- default=None, description="Ordinary Gray Portland Cement", example=0.5, ge=0, le=1
98
+ opc: float | None = pydantic.Field(
99
+ default=None,
100
+ description="Ordinary Gray Portland Cement",
101
+ examples=[0.5],
102
+ ge=0,
103
+ le=1,
95
104
  )
96
- wht: RatioFloat | None = pyd.Field(default=None, description="White Portland Cement", example=0.5, ge=0, le=1)
97
- ggbs: RatioFloat | None = pyd.Field(
98
- default=None, description="Ground Granulated Blast Furnace Slag", example=0.5, ge=0, le=1
105
+ wht: float | None = pydantic.Field(default=None, description="White Portland Cement", examples=[0.5], ge=0, le=1)
106
+ ggbs: float | None = pydantic.Field(
107
+ default=None,
108
+ description="Ground Granulated Blast Furnace Slag",
109
+ examples=[0.5],
110
+ ge=0,
111
+ le=1,
99
112
  )
100
- flyAsh: RatioFloat | None = pyd.Field(
101
- default=None, description="Fly Ash, including types F, CL, and CH", example=0.5, ge=0, le=1
113
+ flyAsh: float | None = pydantic.Field(
114
+ default=None,
115
+ description="Fly Ash, including types F, CL, and CH",
116
+ examples=[0.5],
117
+ ge=0,
118
+ le=1,
102
119
  )
103
- siFume: RatioFloat | None = pyd.Field(default=None, description="Silica Fume", example=0.5, ge=0, le=1)
104
- gg45: RatioFloat | None = pyd.Field(
105
- default=None, description="Ground Glass, 45um or smaller", example=0.5, ge=0, le=1
120
+ siFume: float | None = pydantic.Field(default=None, description="Silica Fume", examples=[0.5], ge=0, le=1)
121
+ gg45: float | None = pydantic.Field(
122
+ default=None,
123
+ description="Ground Glass, 45um or smaller",
124
+ examples=[0.5],
125
+ ge=0,
126
+ le=1,
106
127
  )
107
- natPoz: RatioFloat | None = pyd.Field(default=None, description="Natural pozzolan", example=0.5, ge=0, le=1)
108
- mk: RatioFloat | None = pyd.Field(default=None, description="Metakaolin", example=0.5, ge=0, le=1)
109
- CaCO3: RatioFloat | None = pyd.Field(default=None, description="Limestone", example=0.5, ge=0, le=1)
110
- other: RatioFloat | None = pyd.Field(default=None, description="Other SCMs", example=0.5, ge=0, le=1)
128
+ natPoz: float | None = pydantic.Field(default=None, description="Natural pozzolan", examples=[0.5], ge=0, le=1)
129
+ mk: float | None = pydantic.Field(default=None, description="Metakaolin", examples=[0.5], ge=0, le=1)
130
+ CaCO3: float | None = pydantic.Field(default=None, description="Limestone", examples=[0.5], ge=0, le=1)
131
+ other: float | None = pydantic.Field(default=None, description="Other SCMs", examples=[0.5], ge=0, le=1)
@@ -15,16 +15,16 @@
15
15
  #
16
16
  __all__ = ("AggregatesRangeV1",)
17
17
 
18
- # NB! This is a generated code. Do not edit it manually. Please see src/openepd/model/specs/README.md
19
-
18
+ import pydantic
20
19
 
21
- from openepd.compat.pydantic import pyd
22
20
  from openepd.model.common import RangeRatioFloat
23
21
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
24
22
  from openepd.model.specs.enums import AggregateGradation, AggregateWeightClassification
25
23
  from openepd.model.specs.singular.aggregates import AggregateApplication
26
24
  from openepd.model.validation.quantity import AmountRangeLengthMm
27
25
 
26
+ # NB! This is a generated code. Do not edit it manually. Please see src/openepd/model/specs/README.md
27
+
28
28
 
29
29
  class AggregatesRangeV1(BaseOpenEpdHierarchicalSpec):
30
30
  """
@@ -37,20 +37,20 @@ class AggregatesRangeV1(BaseOpenEpdHierarchicalSpec):
37
37
 
38
38
  _EXT_VERSION = "1.0"
39
39
 
40
- recycled_content: RangeRatioFloat | None = pyd.Field(
40
+ recycled_content: RangeRatioFloat | None = pydantic.Field(
41
41
  default=None, description="Percent of total mass that is recycled aggregate"
42
42
  )
43
- nominal_max_size: AmountRangeLengthMm | None = pyd.Field(
43
+ nominal_max_size: AmountRangeLengthMm | None = pydantic.Field(
44
44
  default=None,
45
45
  description="Nominal maximum aggregate size is defined as one sieve size smaller than the maximum aggregate size. The maximum aggregate size is defined as the smallest sieve size that requires 100% passing.",
46
46
  )
47
- weight_classification: list[AggregateWeightClassification] | None = pyd.Field(default=None)
48
- gradation: list[AggregateGradation] | None = pyd.Field(default=None)
49
- manufactured: bool | None = pyd.Field(
47
+ weight_classification: list[AggregateWeightClassification] | None = pydantic.Field(default=None)
48
+ gradation: list[AggregateGradation] | None = pydantic.Field(default=None)
49
+ manufactured: bool | None = pydantic.Field(
50
50
  default=None,
51
51
  description="Aggregate produced via expansion or sintering at high temperatures of the following materials: clay, shale, slate, perlite, vermiculite, or slag. Typically used to produce lightweight aggregate",
52
52
  )
53
- slag: bool | None = pyd.Field(
53
+ slag: bool | None = pydantic.Field(
54
54
  default=None,
55
55
  description="Pelletized, foamed, and granulated blast furnace slag can be used as construction aggregate.",
56
56
  )