openepd 7.1.0__py3-none-any.whl → 7.3.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/__version__.py +1 -1
- openepd/api/average_dataset/generic_estimate_sync_api.py +2 -1
- openepd/api/average_dataset/industry_epd_sync_api.py +2 -1
- openepd/api/base_sync_client.py +10 -8
- openepd/api/common.py +17 -11
- openepd/api/dto/common.py +1 -1
- openepd/api/epd/sync_api.py +2 -1
- openepd/api/org/__init__.py +15 -0
- openepd/api/org/sync_api.py +79 -0
- openepd/api/pcr/sync_api.py +35 -0
- openepd/api/plant/__init__.py +15 -0
- openepd/api/plant/sync_api.py +79 -0
- openepd/api/standard/__init__.py +15 -0
- openepd/api/standard/sync_api.py +79 -0
- openepd/api/sync_client.py +27 -0
- openepd/bundle/base.py +47 -6
- openepd/bundle/model.py +1 -0
- openepd/bundle/reader.py +35 -10
- openepd/bundle/writer.py +21 -12
- openepd/m49/__init__.py +2 -0
- openepd/m49/const.py +1 -1
- openepd/m49/utils.py +16 -10
- openepd/model/base.py +20 -15
- openepd/model/common.py +10 -5
- openepd/model/declaration.py +2 -2
- openepd/model/epd.py +2 -1
- openepd/model/factory.py +5 -3
- openepd/model/generic_estimate.py +4 -0
- openepd/model/lcia.py +10 -10
- openepd/model/org.py +14 -7
- openepd/model/pcr.py +2 -2
- openepd/model/specs/__init__.py +37 -0
- openepd/model/specs/asphalt.py +3 -3
- openepd/model/specs/base.py +2 -1
- openepd/model/specs/enums.py +9 -1
- openepd/model/specs/range/__init__.py +5 -3
- openepd/model/specs/range/accessories.py +1 -1
- openepd/model/specs/range/aluminium.py +1 -1
- openepd/model/specs/range/cladding.py +10 -10
- openepd/model/specs/range/cmu.py +0 -2
- openepd/model/specs/range/concrete.py +25 -2
- openepd/model/specs/range/conveying_equipment.py +2 -2
- openepd/model/specs/range/electrical.py +18 -18
- openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +1 -1
- openepd/model/specs/range/exterior_improvements.py +47 -0
- openepd/model/specs/range/finishes.py +19 -40
- openepd/model/specs/range/fire_and_smoke_protection.py +3 -3
- openepd/model/specs/range/furnishings.py +7 -7
- openepd/model/specs/range/manufacturing_inputs.py +17 -5
- openepd/model/specs/range/masonry.py +1 -1
- openepd/model/specs/range/mechanical.py +6 -6
- openepd/model/specs/range/mixins/__init__.py +15 -0
- openepd/model/specs/range/mixins/access_flooring_mixin.py +43 -0
- openepd/model/specs/range/network_infrastructure.py +3 -3
- openepd/model/specs/range/openings.py +17 -17
- openepd/model/specs/range/other_materials.py +4 -4
- openepd/model/specs/range/plumbing.py +5 -5
- openepd/model/specs/range/precast_concrete.py +2 -2
- openepd/model/specs/range/steel.py +18 -9
- openepd/model/specs/range/thermal_moisture_protection.py +12 -12
- openepd/model/specs/range/wood.py +4 -6
- openepd/model/specs/singular/__init__.py +119 -2
- openepd/model/specs/singular/aluminium.py +2 -1
- openepd/model/specs/singular/concrete.py +25 -1
- openepd/model/specs/singular/deprecated/__init__.py +1 -1
- openepd/model/specs/singular/exterior_improvements.py +47 -0
- openepd/model/specs/singular/finishes.py +3 -59
- openepd/model/specs/singular/manufacturing_inputs.py +13 -1
- openepd/model/specs/singular/mixins/access_flooring_mixin.py +55 -0
- openepd/model/specs/singular/steel.py +10 -2
- openepd/model/validation/common.py +10 -6
- openepd/model/validation/enum.py +4 -2
- openepd/model/validation/quantity.py +13 -6
- openepd/model/versioning.py +8 -6
- {openepd-7.1.0.dist-info → openepd-7.3.0.dist-info}/METADATA +23 -13
- {openepd-7.1.0.dist-info → openepd-7.3.0.dist-info}/RECORD +78 -67
- {openepd-7.1.0.dist-info → openepd-7.3.0.dist-info}/WHEEL +1 -1
- {openepd-7.1.0.dist-info → openepd-7.3.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2025 by C Change Labs Inc. www.c-change-labs.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
import pydantic as pyd
|
17
|
+
|
18
|
+
from openepd.model.specs.base import BaseOpenEpdSpec
|
19
|
+
from openepd.model.specs.enums import (
|
20
|
+
AccessFlooringCoreMaterial,
|
21
|
+
AccessFlooringFinishMaterial,
|
22
|
+
AccessFlooringSeismicRating,
|
23
|
+
AccessFlooringStringers,
|
24
|
+
)
|
25
|
+
from openepd.model.validation.quantity import AmountRangeForce, AmountRangeLengthMm, AmountRangePressureMpa
|
26
|
+
|
27
|
+
|
28
|
+
class AccessFlooringRangeMixin(BaseOpenEpdSpec):
|
29
|
+
core_material: list[AccessFlooringCoreMaterial] | None = pyd.Field(default=None, description="")
|
30
|
+
finish_material: list[AccessFlooringFinishMaterial] | None = pyd.Field(default=None, description="")
|
31
|
+
stringers: list[AccessFlooringStringers] | None = pyd.Field(default=None, description="")
|
32
|
+
seismic_rating: list[AccessFlooringSeismicRating] | None = pyd.Field(default=None, description="")
|
33
|
+
magnetically_attached_finish: bool | None = pyd.Field(default=None, description="")
|
34
|
+
permanent_finish: bool | None = pyd.Field(default=None, description="")
|
35
|
+
drylay: bool | None = pyd.Field(default=None, description="")
|
36
|
+
adjustable_height: bool | None = pyd.Field(default=None, description="")
|
37
|
+
fixed_height: bool | None = pyd.Field(default=None, description="")
|
38
|
+
finished_floor_height: AmountRangeLengthMm | None = pyd.Field(default=None, description="")
|
39
|
+
panel_thickness: AmountRangeLengthMm | None = pyd.Field(default=None, description="")
|
40
|
+
concentrated_load: AmountRangePressureMpa | None = pyd.Field(default=None, description="")
|
41
|
+
uniform_load: AmountRangePressureMpa | None = pyd.Field(default=None, description="")
|
42
|
+
rolling_load_10_pass: AmountRangeForce | None = pyd.Field(default=None, description="")
|
43
|
+
rolling_load_10000_pass: AmountRangeForce | None = pyd.Field(default=None, description="")
|
@@ -14,14 +14,14 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
__all__ = (
|
17
|
-
"PDURangeV1",
|
18
17
|
"CabinetsRacksAndEnclosuresRangeV1",
|
18
|
+
"CommunicationsConduitRangeV1",
|
19
19
|
"DataCablingRangeV1",
|
20
20
|
"FloorBoxesAndAccessoriesRangeV1",
|
21
|
+
"NetworkInfrastructureRangeV1",
|
21
22
|
"NetworkingCableTraysRangeV1",
|
22
23
|
"NetworkingRacewaysRangeV1",
|
23
|
-
"
|
24
|
-
"NetworkInfrastructureRangeV1",
|
24
|
+
"PDURangeV1",
|
25
25
|
)
|
26
26
|
|
27
27
|
import pydantic
|
@@ -14,34 +14,34 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
__all__ = (
|
17
|
-
"
|
18
|
-
"
|
19
|
-
"
|
20
|
-
"SlidingGlassDoorsRangeV1",
|
17
|
+
"CurtainWallsRangeV1",
|
18
|
+
"DoorsAndFramesRangeV1",
|
19
|
+
"EntrancesRangeV1",
|
21
20
|
"FenestrationAccessoriesRangeV1",
|
22
21
|
"FenestrationFramingRangeV1",
|
23
22
|
"FenestrationHardwareRangeV1",
|
23
|
+
"FenestrationPartsRangeV1",
|
24
24
|
"FlatGlassPanesRangeV1",
|
25
|
-
"
|
25
|
+
"GlassPanesRangeV1",
|
26
26
|
"GlazedDoorsRangeV1",
|
27
|
-
"
|
28
|
-
"
|
27
|
+
"GlazingRangeV1",
|
28
|
+
"InsulatingGlazingUnitsRangeV1",
|
29
29
|
"IntegratedDoorsOpeningAssembliesRangeV1",
|
30
30
|
"MetalDoorAndFramesRangeV1",
|
31
|
-
"SpecialtyDoorsAndFramesRangeV1",
|
32
|
-
"WoodDoorsRangeV1",
|
33
|
-
"FenestrationPartsRangeV1",
|
34
|
-
"GlassPanesRangeV1",
|
35
31
|
"NAFSFenestrationRangeV1",
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
32
|
+
"OpeningsRangeV1",
|
33
|
+
"PanelDoorsRangeV1",
|
34
|
+
"PressureResistantDoorsRangeV1",
|
35
|
+
"ProcessedNonInsulatingGlassPanesRangeV1",
|
36
|
+
"SlidingGlassDoorsRangeV1",
|
37
|
+
"SpecialFunctionDoorsRangeV1",
|
38
|
+
"SpecialtyDoorsAndFramesRangeV1",
|
41
39
|
"StorefrontsRangeV1",
|
42
40
|
"TranslucentWallAndRoofAssembliesRangeV1",
|
41
|
+
"UnitSkylightsRangeV1",
|
43
42
|
"WindowWallAssembliesRangeV1",
|
44
|
-
"
|
43
|
+
"WindowsRangeV1",
|
44
|
+
"WoodDoorsRangeV1",
|
45
45
|
)
|
46
46
|
|
47
47
|
import pydantic
|
@@ -17,17 +17,17 @@ __all__ = (
|
|
17
17
|
"AuxiliariesRangeV1",
|
18
18
|
"CleaningProductsRangeV1",
|
19
19
|
"ClothingRangeV1",
|
20
|
-
"FoodBeverageRangeV1",
|
21
|
-
"TransportationInfrastructureRangeV1",
|
22
|
-
"UnsupportedRangeV1",
|
23
20
|
"CopperRangeV1",
|
24
21
|
"EarthworkRangeV1",
|
25
22
|
"ExteriorSunControlDevicesRangeV1",
|
23
|
+
"FoodBeverageRangeV1",
|
26
24
|
"GypsumFinishingCompoundsRangeV1",
|
25
|
+
"OtherMaterialsRangeV1",
|
27
26
|
"ProfilesRangeV1",
|
27
|
+
"TransportationInfrastructureRangeV1",
|
28
28
|
"UnknownRangeV1",
|
29
|
+
"UnsupportedRangeV1",
|
29
30
|
"ZincRangeV1",
|
30
|
-
"OtherMaterialsRangeV1",
|
31
31
|
)
|
32
32
|
|
33
33
|
# NB! This is a generated code. Do not edit it manually. Please see src/openepd/model/specs/README.md
|
@@ -16,17 +16,17 @@
|
|
16
16
|
__all__ = (
|
17
17
|
"BathtubsRangeV1",
|
18
18
|
"FaucetsRangeV1",
|
19
|
-
"OtherPlumbingFixturesRangeV1",
|
20
|
-
"WaterClosetsRangeV1",
|
21
19
|
"FireProtectionPipingRangeV1",
|
22
20
|
"FireSprinklersRangeV1",
|
23
|
-
"StorageTanksRangeV1",
|
24
|
-
"WaterHeatersRangeV1",
|
25
|
-
"PlumbingFixturesRangeV1",
|
26
21
|
"FireSuppressionRangeV1",
|
22
|
+
"OtherPlumbingFixturesRangeV1",
|
27
23
|
"PipingRangeV1",
|
28
24
|
"PlumbingEquipmentRangeV1",
|
25
|
+
"PlumbingFixturesRangeV1",
|
29
26
|
"PlumbingRangeV1",
|
27
|
+
"StorageTanksRangeV1",
|
28
|
+
"WaterClosetsRangeV1",
|
29
|
+
"WaterHeatersRangeV1",
|
30
30
|
)
|
31
31
|
|
32
32
|
import pydantic
|
@@ -15,10 +15,10 @@
|
|
15
15
|
#
|
16
16
|
__all__ = (
|
17
17
|
"ArchitecturalPrecastRangeV1",
|
18
|
-
"StructuralPrecastRangeV1",
|
19
|
-
"UtilityUndergroundPrecastRangeV1",
|
20
18
|
"CivilPrecastRangeV1",
|
21
19
|
"PrecastConcreteRangeV1",
|
20
|
+
"StructuralPrecastRangeV1",
|
21
|
+
"UtilityUndergroundPrecastRangeV1",
|
22
22
|
)
|
23
23
|
|
24
24
|
import pydantic
|
@@ -14,25 +14,27 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
__all__ = (
|
17
|
+
"CoilSteelRangeV1",
|
17
18
|
"ColdFormedFramingRangeV1",
|
19
|
+
"ColdFormedSteelRangeV1",
|
20
|
+
"CrudeSteelRangeV1",
|
18
21
|
"DeckingSteelRangeV1",
|
19
|
-
"SteelSuspensionAssemblyRangeV1",
|
20
22
|
"HollowSectionsRangeV1",
|
21
23
|
"HotRolledSectionsRangeV1",
|
22
|
-
"
|
24
|
+
"MBQSteelRangeV1",
|
23
25
|
"MetalRailingsRangeV1",
|
24
26
|
"MetalStairsRangeV1",
|
25
27
|
"MiscMetalFabricationRangeV1",
|
26
28
|
"OpenWebMembranesRangeV1",
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"ColdFormedSteelRangeV1",
|
30
|
-
"StructuralSteelRangeV1",
|
31
|
-
"PrefabricatedSteelAssembliesRangeV1",
|
29
|
+
"OtherSteelRangeV1",
|
30
|
+
"PlateSteelRangeV1",
|
32
31
|
"PostTensioningSteelRangeV1",
|
32
|
+
"PrefabricatedSteelAssembliesRangeV1",
|
33
33
|
"RebarSteelRangeV1",
|
34
|
-
"WireMeshSteelRangeV1",
|
35
34
|
"SteelRangeV1",
|
35
|
+
"SteelSuspensionAssemblyRangeV1",
|
36
|
+
"StructuralSteelRangeV1",
|
37
|
+
"WireMeshSteelRangeV1",
|
36
38
|
)
|
37
39
|
|
38
40
|
from typing import ClassVar
|
@@ -308,6 +310,12 @@ class OtherSteelRangeV1(BaseOpenEpdHierarchicalSpec):
|
|
308
310
|
_EXT_VERSION = "1.0"
|
309
311
|
|
310
312
|
|
313
|
+
class CrudeSteelRangeV1(BaseOpenEpdHierarchicalSpec):
|
314
|
+
"""Crude steel products."""
|
315
|
+
|
316
|
+
_EXT_VERSION = "1.0"
|
317
|
+
|
318
|
+
|
311
319
|
class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
|
312
320
|
"""
|
313
321
|
Broad category for construction materials made from steel and its alloys.
|
@@ -315,7 +323,7 @@ class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
|
|
315
323
|
Range version.
|
316
324
|
"""
|
317
325
|
|
318
|
-
_EXT_VERSION: ClassVar[str] = "1.
|
326
|
+
_EXT_VERSION: ClassVar[str] = "1.2"
|
319
327
|
|
320
328
|
yield_tensile_str: AmountRangePressureMpa | None = pydantic.Field(
|
321
329
|
default=None,
|
@@ -349,3 +357,4 @@ class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
|
|
349
357
|
RebarSteel: RebarSteelRangeV1 | None = None
|
350
358
|
WireMeshSteel: WireMeshSteelRangeV1 | None = None
|
351
359
|
OtherSteel: OtherSteelRangeV1 | None = None
|
360
|
+
CrudeSteel: CrudeSteelRangeV1 | None = None
|
@@ -14,28 +14,28 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
__all__ = (
|
17
|
+
"AirBarriersRangeV1",
|
17
18
|
"BituminousRoofingRangeV1",
|
18
|
-
"SinglePlyEPDMRangeV1",
|
19
|
-
"SinglePlyKEERangeV1",
|
20
|
-
"SinglePlyOtherRangeV1",
|
21
|
-
"SinglePlyPolyurethaneRangeV1",
|
22
|
-
"SinglePlyPVCRangeV1",
|
23
|
-
"SinglePlyTPORangeV1",
|
24
19
|
"BlanketInsulationRangeV1",
|
25
20
|
"BlownInsulationRangeV1",
|
26
21
|
"BoardInsulationRangeV1",
|
27
|
-
"FoamedInPlaceRangeV1",
|
28
|
-
"SprayedInsulationRangeV1",
|
29
|
-
"AirBarriersRangeV1",
|
30
|
-
"MembraneRoofingRangeV1",
|
31
|
-
"InsulationRangeV1",
|
32
22
|
"DampproofingAndWaterproofingRangeV1",
|
33
23
|
"FlashingAndSheetMetalRangeV1",
|
24
|
+
"FoamedInPlaceRangeV1",
|
25
|
+
"InsulationRangeV1",
|
34
26
|
"JointProtectionRangeV1",
|
27
|
+
"MembraneRoofingRangeV1",
|
35
28
|
"RoofCoverBoardsRangeV1",
|
29
|
+
"SinglePlyEPDMRangeV1",
|
30
|
+
"SinglePlyKEERangeV1",
|
31
|
+
"SinglePlyOtherRangeV1",
|
32
|
+
"SinglePlyPVCRangeV1",
|
33
|
+
"SinglePlyPolyurethaneRangeV1",
|
34
|
+
"SinglePlyTPORangeV1",
|
35
|
+
"SprayedInsulationRangeV1",
|
36
36
|
"SteepSlopeRoofingRangeV1",
|
37
|
-
"WeatherBarriersRangeV1",
|
38
37
|
"ThermalMoistureProtectionRangeV1",
|
38
|
+
"WeatherBarriersRangeV1",
|
39
39
|
)
|
40
40
|
|
41
41
|
import pydantic
|
@@ -14,26 +14,24 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
__all__ = (
|
17
|
-
"WoodDeckingRangeV1",
|
18
|
-
"WoodFramingRangeV1",
|
19
|
-
"PrefabricatedWoodInsulatedPanelsRangeV1",
|
20
|
-
"PrefabricatedWoodTrussRangeV1",
|
21
17
|
"CompositeLumberRangeV1",
|
22
18
|
"DimensionLumberRangeV1",
|
23
19
|
"HeavyTimberRangeV1",
|
24
20
|
"MassTimberRangeV1",
|
25
21
|
"NonStructuralWoodRangeV1",
|
22
|
+
"PrefabricatedWoodInsulatedPanelsRangeV1",
|
26
23
|
"PrefabricatedWoodRangeV1",
|
24
|
+
"PrefabricatedWoodTrussRangeV1",
|
27
25
|
"SheathingPanelsRangeV1",
|
28
26
|
"UnfinishedWoodRangeV1",
|
27
|
+
"WoodDeckingRangeV1",
|
28
|
+
"WoodFramingRangeV1",
|
29
29
|
"WoodRangeV1",
|
30
30
|
)
|
31
31
|
|
32
32
|
# NB! This is a generated code. Do not edit it manually. Please see src/openepd/model/specs/README.md
|
33
33
|
|
34
34
|
|
35
|
-
from builtins import float
|
36
|
-
|
37
35
|
import pydantic
|
38
36
|
|
39
37
|
from openepd.model.org import OrgRef
|
@@ -13,9 +13,18 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
16
|
+
__all__ = [
|
17
|
+
"BaseCompatibilitySpec",
|
18
|
+
"Specs",
|
19
|
+
]
|
20
|
+
|
21
|
+
|
22
|
+
from collections.abc import Sequence
|
23
|
+
import types
|
24
|
+
from typing import Any, ClassVar, TypeVar
|
17
25
|
|
18
26
|
import pydantic
|
27
|
+
from pydantic.fields import FieldInfo
|
19
28
|
|
20
29
|
from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
|
21
30
|
from openepd.model.specs.singular.accessories import AccessoriesV1
|
@@ -36,6 +45,7 @@ from openepd.model.specs.singular.electrical_transmission_and_distribution_equip
|
|
36
45
|
ElectricalTransmissionAndDistributionEquipmentV1,
|
37
46
|
)
|
38
47
|
from openepd.model.specs.singular.electricity import ElectricityV1
|
48
|
+
from openepd.model.specs.singular.exterior_improvements import ExteriorImprovementsV1
|
39
49
|
from openepd.model.specs.singular.finishes import FinishesV1
|
40
50
|
from openepd.model.specs.singular.fire_and_smoke_protection import FireAndSmokeProtectionV1
|
41
51
|
from openepd.model.specs.singular.furnishings import FurnishingsV1
|
@@ -58,7 +68,7 @@ from openepd.model.specs.singular.utility_piping import UtilityPipingV1
|
|
58
68
|
from openepd.model.specs.singular.wood import WoodV1
|
59
69
|
from openepd.model.specs.singular.wood_joists import WoodJoistsV1
|
60
70
|
|
61
|
-
|
71
|
+
TValue = TypeVar("TValue")
|
62
72
|
|
63
73
|
|
64
74
|
class Specs(BaseOpenEpdHierarchicalSpec):
|
@@ -106,6 +116,7 @@ class Specs(BaseOpenEpdHierarchicalSpec):
|
|
106
116
|
MechanicalInsulation: MechanicalInsulationV1 | None = None
|
107
117
|
OtherElectricalEquipment: OtherElectricalEquipmentV1 | None = None
|
108
118
|
WoodJoists: WoodJoistsV1 | None = None
|
119
|
+
ExteriorImprovements: ExteriorImprovementsV1 | None = None
|
109
120
|
|
110
121
|
# historical backward-compatible specs
|
111
122
|
concrete: ConcreteOldSpec | None = None
|
@@ -141,3 +152,109 @@ class Specs(BaseOpenEpdHierarchicalSpec):
|
|
141
152
|
continue
|
142
153
|
set_safely(values, new_key_spec, old_value)
|
143
154
|
return values
|
155
|
+
|
156
|
+
def get_by_spec_path(
|
157
|
+
self,
|
158
|
+
path: str | Sequence[str],
|
159
|
+
*,
|
160
|
+
delimiter: str = ".",
|
161
|
+
asserted_type: type[TValue] | None = None,
|
162
|
+
ensure_path: bool = True,
|
163
|
+
) -> TValue | None:
|
164
|
+
"""
|
165
|
+
Access specific property of the spec by a path, which can be string-separated or sequence of keys.
|
166
|
+
|
167
|
+
:param path: The path to access, either as a string with delimiters (e.g. "Concrete.strength_28d")
|
168
|
+
or sequence of keys (e.g. ["Concrete", "strength_28d"])
|
169
|
+
:param delimiter: String separator used when path is provided as a string (default ".")
|
170
|
+
:param asserted_type: Type to validate the returned value against (optional)
|
171
|
+
:param ensure_path: If True, validates that path exists in the spec schema before accessing (default True)
|
172
|
+
:return: Value at the specified path if found, None if path resolves to None
|
173
|
+
:raises KeyError: If ensure_path=True and path does not exist in schema
|
174
|
+
:raises TypeError: If asserted_type is provided and value type doesn't match
|
175
|
+
:raises ValueError: If path is not string or sequence
|
176
|
+
|
177
|
+
Example usage:
|
178
|
+
>>> specs.get_by_spec_path("Concrete.strength_28d")
|
179
|
+
>>> specs.get_by_spec_path(["Concrete", "strength_28d"])
|
180
|
+
>>> specs.get_by_spec_path("Concrete.strength_28d", asserted_type=str)
|
181
|
+
"""
|
182
|
+
keys = self._path_to_keys(path, delimiter)
|
183
|
+
|
184
|
+
if ensure_path:
|
185
|
+
self._ensure_path(keys)
|
186
|
+
|
187
|
+
result: Any = self
|
188
|
+
for key in self._path_to_keys(path, delimiter):
|
189
|
+
result = getattr(result, key, None)
|
190
|
+
if result is None:
|
191
|
+
return None
|
192
|
+
|
193
|
+
if not asserted_type:
|
194
|
+
return result
|
195
|
+
|
196
|
+
if isinstance(result, asserted_type):
|
197
|
+
return result
|
198
|
+
|
199
|
+
msg = f"Expected {asserted_type} but got {type(result)}"
|
200
|
+
raise TypeError(msg)
|
201
|
+
|
202
|
+
def _ensure_path(self, keys: Sequence[str]) -> None:
|
203
|
+
"""
|
204
|
+
Validate if a sequence of keys exists in the spec schema structure.
|
205
|
+
|
206
|
+
:param keys: Sequence of string keys representing path in spec schema
|
207
|
+
:raises KeyError: If path specified by keys does not exist in schema
|
208
|
+
|
209
|
+
This internal method walks through the schema structure following the provided keys
|
210
|
+
sequence, checking if each key exists at the corresponding level. For UnionType fields,
|
211
|
+
it extracts the non-None type and continues validation if it's a BaseModel.
|
212
|
+
"""
|
213
|
+
klass = self.__class__
|
214
|
+
|
215
|
+
fields: dict[str, FieldInfo] = klass.model_fields
|
216
|
+
|
217
|
+
field: FieldInfo | None
|
218
|
+
for i, key in enumerate(keys, start=1):
|
219
|
+
field = fields.get(key)
|
220
|
+
if field is None:
|
221
|
+
msg = f"Path {'.'.join(keys)} does not exist in {klass.__name__} spec"
|
222
|
+
raise KeyError(msg)
|
223
|
+
|
224
|
+
assert hasattr(field, "annotation"), "Annotation field is required"
|
225
|
+
if isinstance(field.annotation, types.UnionType):
|
226
|
+
target_type = next(item for item in field.annotation.__args__ if item is not None)
|
227
|
+
else:
|
228
|
+
target_type = field.annotation
|
229
|
+
|
230
|
+
if target_type is None:
|
231
|
+
msg = f"Path {'.'.join(keys)} does not exist in {klass.__name__} spec"
|
232
|
+
raise KeyError(msg)
|
233
|
+
|
234
|
+
if issubclass(target_type, pydantic.BaseModel):
|
235
|
+
fields = target_type.model_fields
|
236
|
+
elif i == len(keys):
|
237
|
+
return None
|
238
|
+
else:
|
239
|
+
msg = f"Path {'.'.join(keys)} does not exist in {klass.__name__} spec"
|
240
|
+
raise KeyError(msg)
|
241
|
+
|
242
|
+
return None
|
243
|
+
|
244
|
+
def _path_to_keys(self, path: str | Sequence[str], delimiter: str) -> Sequence[str]:
|
245
|
+
"""
|
246
|
+
Convert path parameter to sequence of keys.
|
247
|
+
|
248
|
+
:param path: Path to convert, either string to split by delimiter or sequence of keys
|
249
|
+
:param delimiter: String separator used to split path if string is provided
|
250
|
+
:return: Sequence of string keys
|
251
|
+
:raises ValueError: If a path is not string or sequence
|
252
|
+
"""
|
253
|
+
match path:
|
254
|
+
case str():
|
255
|
+
return path.split(delimiter)
|
256
|
+
case Sequence():
|
257
|
+
return path
|
258
|
+
case _:
|
259
|
+
msg = f"Unsupported path type: {type(path)}"
|
260
|
+
raise ValueError(msg)
|
@@ -26,7 +26,8 @@ class AluminiumBilletsV1(BaseOpenEpdHierarchicalSpec):
|
|
26
26
|
|
27
27
|
|
28
28
|
class AluminiumExtrusionsV1(BaseOpenEpdHierarchicalSpec):
|
29
|
-
"""
|
29
|
+
"""
|
30
|
+
Extruded aluminum products used in construction.
|
30
31
|
|
31
32
|
Includes range of finish options including mill finish, painted, and anodized.
|
32
33
|
"""
|
@@ -99,6 +99,28 @@ class ShotcreteV1(BaseOpenEpdHierarchicalSpec):
|
|
99
99
|
_EXT_VERSION = "1.0"
|
100
100
|
|
101
101
|
|
102
|
+
class CellularConcreteV1(BaseOpenEpdHierarchicalSpec):
|
103
|
+
"""
|
104
|
+
Cellular concrete is typically composed of cementitious material, water, and pre-formed foam with air entrainment.
|
105
|
+
|
106
|
+
Such a product is a homogeneous void or cell structure.
|
107
|
+
It is self-compacting and can be pumped over extensive heights and distances.
|
108
|
+
"""
|
109
|
+
|
110
|
+
_EXT_VERSION = "1.0"
|
111
|
+
|
112
|
+
|
113
|
+
class OtherConcreteV1(BaseOpenEpdHierarchicalSpec):
|
114
|
+
"""
|
115
|
+
Other Concrete Products.
|
116
|
+
|
117
|
+
Other types of concrete products that are not captured by existing concrete categories.
|
118
|
+
Could include products such as patching concrete or additives
|
119
|
+
"""
|
120
|
+
|
121
|
+
_EXT_VERSION = "1.0"
|
122
|
+
|
123
|
+
|
102
124
|
class ConcreteV1(BaseOpenEpdHierarchicalSpec):
|
103
125
|
"""
|
104
126
|
Concrete.
|
@@ -107,7 +129,7 @@ class ConcreteV1(BaseOpenEpdHierarchicalSpec):
|
|
107
129
|
hardens over time.
|
108
130
|
"""
|
109
131
|
|
110
|
-
_EXT_VERSION = "1.
|
132
|
+
_EXT_VERSION = "1.1"
|
111
133
|
|
112
134
|
# Own fields:
|
113
135
|
lightweight: bool | None = pydantic.Field(
|
@@ -208,6 +230,8 @@ class ConcreteV1(BaseOpenEpdHierarchicalSpec):
|
|
208
230
|
OilPatch: OilPatchV1 | None = None
|
209
231
|
ReadyMix: ReadyMixV1 | None = None
|
210
232
|
Shotcrete: ShotcreteV1 | None = None
|
233
|
+
OtherConcrete: OtherConcreteV1 | None = None
|
234
|
+
CellularConcrete: CellularConcreteV1 | None = None
|
211
235
|
|
212
236
|
@pydantic.field_validator("aci_exposure_classes", mode="before", check_fields=False)
|
213
237
|
def _validate_aci_exposure_classes(cls, value):
|
@@ -50,7 +50,7 @@ def get_safely(d: dict, path: str) -> tuple[bool, Any]:
|
|
50
50
|
return False, None
|
51
51
|
current = current.get(p)
|
52
52
|
case pydantic.BaseModel() as model:
|
53
|
-
if not hasattr(current, p) or p not in model.
|
53
|
+
if not hasattr(current, p) or p not in model.model_fields_set:
|
54
54
|
return False, None
|
55
55
|
current = getattr(current, p)
|
56
56
|
case _:
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2025 by C Change Labs Inc. www.c-change-labs.com
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
import pydantic as pyd
|
17
|
+
|
18
|
+
from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
|
19
|
+
from openepd.model.validation.quantity import AmountPressureMpa
|
20
|
+
|
21
|
+
|
22
|
+
class ConcretePaversV1(BaseOpenEpdHierarchicalSpec):
|
23
|
+
"""
|
24
|
+
Segmental units of concrete of standardized sizes and shapes for use in paving applications.
|
25
|
+
|
26
|
+
Includes pavers and paving slabs.
|
27
|
+
"""
|
28
|
+
|
29
|
+
_EXT_VERSION = "1.0"
|
30
|
+
|
31
|
+
compressive_strength: AmountPressureMpa | None = pyd.Field(
|
32
|
+
default=None,
|
33
|
+
description=(
|
34
|
+
"Unit strength in MPa, ksi, or psi. "
|
35
|
+
"This is the net compressive strength, also known as unit strength, as opposed to f′ₘ (f prime m) which is "
|
36
|
+
"the strength of the completed wall module."
|
37
|
+
),
|
38
|
+
)
|
39
|
+
white_cement: bool | None = pyd.Field(default=None, description="CMU using some portion of white cement")
|
40
|
+
|
41
|
+
|
42
|
+
class ExteriorImprovementsV1(BaseOpenEpdHierarchicalSpec):
|
43
|
+
"""Products that alter the exterior appearance of a lot or its structures."""
|
44
|
+
|
45
|
+
_EXT_VERSION = "1.0"
|
46
|
+
|
47
|
+
ConcretePavers: ConcretePaversV1 | None = None
|