openepd 6.31.1__py3-none-any.whl → 6.32.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 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 = "6.31.1"
16
+ VERSION = "6.32.0"
openepd/model/base.py CHANGED
@@ -70,7 +70,7 @@ class BaseOpenEpdSchema(pyd.BaseModel):
70
70
  allow_mutation = True
71
71
  validate_assignment = False
72
72
  allow_population_by_field_name = True
73
- use_enum_values = True
73
+ use_enum_values = False
74
74
  schema_extra: Callable | dict = modify_pydantic_schema
75
75
 
76
76
  def to_serializable(self, *args, **kwargs) -> dict[str, Any]:
openepd/model/common.py CHANGED
@@ -15,14 +15,15 @@
15
15
  #
16
16
  from collections.abc import Callable, Generator
17
17
  from enum import StrEnum
18
+ import math
18
19
  import re
19
- from typing import Annotated, Any
20
+ from typing import Annotated, Any, Final
20
21
 
21
22
  from openepd.compat.pydantic import pyd
22
23
  from openepd.model.base import BaseOpenEpdSchema
23
24
  from openepd.model.validation.numbers import RatioFloat
24
25
 
25
- DATA_URL_REGEX = r"^data:([-\w]+\/[-+\w.]+)?(;?\w+=[-\w]+)*(;base64)?,.*$"
26
+ DATA_URL_REGEX = r"^data:([-\w]+\/[-+\w.]+)?(;?\w+=[-\w]+)*(;base64)?,(.*)$"
26
27
  """
27
28
  Regular expression pattern for matching Data URLs.
28
29
 
@@ -31,6 +32,14 @@ in web pages as if they were external resources.
31
32
  The pattern matches the following format: data:[<media-type>][;base64],<data>
32
33
  """
33
34
 
35
+ DATA_URL_IMAGE_MAX_LENGTH: Final[int] = math.ceil(32 * 1024 * 4 / 3)
36
+ """
37
+ Maximum allowed length of a data URL image string to ensure the decoded image is less than 32KB.
38
+
39
+ Base64 encoding overhead (approximately 33%) requires
40
+ limiting the encoded string length to 4/3 of the file size limit.
41
+ """
42
+
34
43
 
35
44
  class Amount(BaseOpenEpdSchema):
36
45
  """A value-and-unit pairing for amounts that do not have an uncertainty."""
@@ -336,3 +345,17 @@ class DataUrl(str):
336
345
  raise ValueError(msg)
337
346
 
338
347
  yield validator
348
+
349
+
350
+ def validate_data_url(v: str | None, max_length: int) -> None:
351
+ """
352
+ Validate max length of data URL.
353
+
354
+ :param v: data URL string.
355
+ :param max_length: maximum allowed length.
356
+ :raises ValueError: if the data URL exceeds the maximum length.
357
+ :return: None
358
+ """
359
+ if v and len(v) > max_length:
360
+ msg = f"URL must not exceed {max_length} characters"
361
+ raise ValueError(msg)
@@ -16,12 +16,10 @@
16
16
  import abc
17
17
  import datetime
18
18
  from enum import StrEnum
19
- import math
20
- from typing import Final
21
19
 
22
20
  from openepd.compat.pydantic import pyd
23
21
  from openepd.model.base import BaseOpenEpdSchema, OpenXpdUUID, RootDocument
24
- from openepd.model.common import Amount, DataUrl
22
+ from openepd.model.common import DATA_URL_IMAGE_MAX_LENGTH, Amount, DataUrl, validate_data_url
25
23
  from openepd.model.geography import Geography
26
24
  from openepd.model.org import Org
27
25
  from openepd.model.pcr import Pcr
@@ -34,14 +32,6 @@ DEVELOPER_DESCRIPTION = "The organization responsible for the underlying LCA (an
34
32
  PROGRAM_OPERATOR_DESCRIPTION = "JSON object for program operator Org"
35
33
  THIRD_PARTY_VERIFIER_DESCRIPTION = "JSON object for Org that performed a critical review of the EPD data"
36
34
 
37
- PRODUCT_IMAGE_MAX_LENGTH: Final[int] = math.ceil(32 * 1024 * 4 / 3)
38
- """
39
- Maximum length for product_image, product_image_small fields.
40
-
41
- Image file size must be less than 32KB. Base64 encoding overhead (approximately 33%) requires
42
- limiting the encoded string length to 4/3 of the file size limit.
43
- """
44
-
45
35
 
46
36
  class BaseDeclaration(RootDocument, abc.ABC):
47
37
  """Base class for declaration-related documents (EPDs, Industry-wide EPDs, Generic Estimates)."""
@@ -175,9 +165,7 @@ class BaseDeclaration(RootDocument, abc.ABC):
175
165
 
176
166
  @pyd.validator("product_image", "product_image_small")
177
167
  def validate_product_image(cls, v: str | None) -> str | None:
178
- if v and len(v) > PRODUCT_IMAGE_MAX_LENGTH:
179
- msg = f"URL must not exceed {PRODUCT_IMAGE_MAX_LENGTH} characters"
180
- raise ValueError(msg)
168
+ validate_data_url(v, DATA_URL_IMAGE_MAX_LENGTH)
181
169
  return v
182
170
 
183
171
 
openepd/model/epd.py CHANGED
@@ -16,7 +16,14 @@
16
16
 
17
17
  from openepd.compat.pydantic import pyd
18
18
  from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes, OpenEpdExtension
19
- from openepd.model.common import Ingredient, WithAltIdsMixin, WithAttachmentsMixin
19
+ from openepd.model.common import (
20
+ DATA_URL_IMAGE_MAX_LENGTH,
21
+ DataUrl,
22
+ Ingredient,
23
+ WithAltIdsMixin,
24
+ WithAttachmentsMixin,
25
+ validate_data_url,
26
+ )
20
27
  from openepd.model.declaration import (
21
28
  DEVELOPER_DESCRIPTION,
22
29
  PROGRAM_OPERATOR_DESCRIPTION,
@@ -200,14 +207,14 @@ class EpdPreviewV0(
200
207
  description="Text description of how product is typically used. Can be used to describe accessories "
201
208
  "like fasteners, adhesives, etc. Supports plain text or github flavored markdown.",
202
209
  )
203
- product_usage_image: pyd.AnyUrl | None = pyd.Field(
210
+ product_usage_image: pyd.AnyUrl | None | DataUrl = pyd.Field(
204
211
  description="Pointer (url) to image illustrating how the product is used. No more than 10MB.", default=None
205
212
  )
206
213
  manufacturing_description: str | None = pyd.Field(
207
214
  default=None,
208
215
  description="Text description of manufacturing process. Supports plain text or github flavored markdown.",
209
216
  )
210
- manufacturing_image: pyd.AnyUrl | None = pyd.Field(
217
+ manufacturing_image: pyd.AnyUrl | None | DataUrl = pyd.Field(
211
218
  description="Pointer (url) to an image illustrating the manufacturing process. No more than 10MB.", default=None
212
219
  )
213
220
 
@@ -235,6 +242,11 @@ class EpdPreviewV0(
235
242
  msg = "Invalid doctype"
236
243
  raise ValueError(msg)
237
244
 
245
+ @pyd.validator("product_usage_image", "manufacturing_image")
246
+ def validate_product_image(cls, v: str | None) -> str | None:
247
+ validate_data_url(v, DATA_URL_IMAGE_MAX_LENGTH)
248
+ return v
249
+
238
250
 
239
251
  EpdPreview = EpdPreviewV0
240
252
 
openepd/model/org.py CHANGED
@@ -13,24 +13,22 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- import math
17
- from typing import Annotated, Final, Optional
16
+ from typing import Annotated, Optional
18
17
 
19
18
  from openlocationcode import openlocationcode
20
19
 
21
20
  from openepd.compat.pydantic import pyd
22
21
  from openepd.model.base import BaseOpenEpdSchema
23
- from openepd.model.common import DataUrl, Location, WithAltIdsMixin, WithAttachmentsMixin
22
+ from openepd.model.common import (
23
+ DATA_URL_IMAGE_MAX_LENGTH,
24
+ DataUrl,
25
+ Location,
26
+ WithAltIdsMixin,
27
+ WithAttachmentsMixin,
28
+ validate_data_url,
29
+ )
24
30
  from openepd.model.validation.common import ReferenceStr
25
31
 
26
- ORG_LOGO_MAX_LENGTH: Final[int] = math.ceil(32 * 1024 * 4 / 3)
27
- """
28
- Maximum length of Org.logo field.
29
-
30
- Logo file size must be less than 32KB. Base64 encoding overhead (approximately 33%) requires
31
- limiting the encoded string length to 4/3 of the file size limit.
32
- """
33
-
34
32
 
35
33
  class OrgRef(BaseOpenEpdSchema):
36
34
  """Represents Organisation with minimal data."""
@@ -105,9 +103,7 @@ class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
105
103
 
106
104
  @pyd.validator("logo")
107
105
  def validate_logo(cls, v: str | None) -> str | None:
108
- if v and len(v) > ORG_LOGO_MAX_LENGTH:
109
- msg = f"Logo URL must not exceed {ORG_LOGO_MAX_LENGTH} characters"
110
- raise ValueError(msg)
106
+ validate_data_url(v, DATA_URL_IMAGE_MAX_LENGTH)
111
107
  return v
112
108
 
113
109
 
@@ -179,6 +175,10 @@ class Plant(PlantRef, WithAttachmentsMixin, WithAltIdsMixin):
179
175
  msg = "Incorrect pluscode for plant"
180
176
  raise ValueError(msg)
181
177
 
178
+ if not openlocationcode.isFull(pluscode):
179
+ msg = "Passed Open Location Code is not a valid full code"
180
+ raise ValueError(msg)
181
+
182
182
  if not web_domain:
183
183
  msg = "Incorrect web_domain for plant"
184
184
  raise ValueError(msg)
@@ -18,8 +18,6 @@ from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
18
  from openepd.model.validation.quantity import (
19
19
  PressureMPaStr,
20
20
  ThermalConductivityStr,
21
- validate_quantity_ge_factory,
22
- validate_quantity_unit_factory,
23
21
  )
24
22
 
25
23
 
@@ -41,14 +39,6 @@ class AutoclavedAeratedConcreteV1(BaseOpenEpdHierarchicalSpec):
41
39
  )
42
40
  white: bool | None = pyd.Field(default=None, description="", example=True)
43
41
 
44
- _aac_thermal_conductivity_is_quantity_validator = pyd.validator("thermal_conductivity", allow_reuse=True)(
45
- validate_quantity_unit_factory("W / (m * K)")
46
- )
47
-
48
- _aac_thermal_conductivity_min_validator = pyd.validator("thermal_conductivity", allow_reuse=True)(
49
- validate_quantity_ge_factory("0 W / (m * K)")
50
- )
51
-
52
42
 
53
43
  class BrickV1(BaseOpenEpdHierarchicalSpec):
54
44
  """Solid masonry units made from clay or shale."""
@@ -317,7 +317,7 @@ class AreaPerVolumeStr(QuantityStr):
317
317
  class ThermalConductivityStr(QuantityStr):
318
318
  """Thermal conductivity quantity type."""
319
319
 
320
- unit = "1 W / K / m"
320
+ unit = "W / K / m"
321
321
 
322
322
 
323
323
  class ThermalExpansionStr(QuantityStr):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openepd
3
- Version: 6.31.1
3
+ Version: 6.32.0
4
4
  Summary: Python library to work with OpenEPD format
5
5
  License: Apache-2.0
6
6
  Author: C-Change Labs
@@ -1,5 +1,5 @@
1
1
  openepd/__init__.py,sha256=fhxfEyEurLvSfvQci-vb3njzl_lvhcLXiZrecCOaMU8,794
2
- openepd/__version__.py,sha256=NgJSN9dQLekpi1WW1Gh2L5KcdSamZVPj3Hxrdpg45Fs,639
2
+ openepd/__version__.py,sha256=B29m_wJ1prKL-Kg5P_HiWaVxsRcfdLKlPzjM3pCaZ5w,639
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=_eZt_jGVL1a3p9cr-EF39Ve9Vl5sB8zwzTc_slnRL50,7975
@@ -44,17 +44,17 @@ openepd/m49/__init__.py,sha256=AApOMp9PJPMXZbPB4piedqKtgHE01mlj_MyF3kf519U,718
44
44
  openepd/m49/const.py,sha256=bkYu6J7dQNVb2-nNkjy97uMpt64vICX5o-PHr0lk_90,31833
45
45
  openepd/m49/utils.py,sha256=vQl0wMXtYS2b7NeLIWilDNUopq3MATmLnhEFcMYTeZA,7256
46
46
  openepd/model/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
47
- openepd/model/base.py,sha256=6rP6r-7NwKC6JLFB24v2w4Z-_f_w6ZSNLte5zyj5o70,9928
47
+ openepd/model/base.py,sha256=WAnVnPdsvkRumsDV6XEnYYhFjexIO1ttmzB9znkA8r8,9929
48
48
  openepd/model/category.py,sha256=reeOVRDuZPYU77EMwG0K5VjnK2H9yOGxT0PJXXqrjEk,1639
49
- openepd/model/common.py,sha256=WM6ankkeZazVzKwbn4s5FHwENMTIKWsgdRJkX7DYmpg,13875
50
- openepd/model/declaration.py,sha256=F7LEMeZSJndtvO0AqnvTS43MeRxI3MO1IWLyF1SlwPU,14744
51
- openepd/model/epd.py,sha256=SGAgEEAuvRGsIlgIUI60UqV1alhb5kouOvp8fAC0VPg,12320
49
+ openepd/model/common.py,sha256=xJmu3PjvQwBHH8AUM247woOZeQ-r7cPI8qHBiN7Uorc,14601
50
+ openepd/model/declaration.py,sha256=6QyFsAxbuwRiVc6vGND-tQff1IGctPjaJj3ds-8TA4Y,14343
51
+ openepd/model/epd.py,sha256=WNxFZvcPhMPUfl-HbtDHaAP4QSm7_uG4juuZxOPJwYM,12629
52
52
  openepd/model/factory.py,sha256=UWSGpfCr3GiMTP4rzBkwqxzbXB6GKZ_5Okb1Dqa_4aA,2701
53
53
  openepd/model/generic_estimate.py,sha256=zLGTyf4Uzmp2C0m-J1ePWItSz2RGdZ0OiGPWC5nhKHk,3992
54
54
  openepd/model/geography.py,sha256=Jx7NIDdk_sIvwyh-7YxnIjAwIHW2HCQK7UtFGM2xKtw,42095
55
55
  openepd/model/industry_epd.py,sha256=QZr7OhgGkzqZ8H5p6dCIVk9zSHEYtK3y9Nk-DvkFMyk,4011
56
56
  openepd/model/lcia.py,sha256=PdaSZz02PMBJl_t2B_dmkcbzIFjA7iACrWj1fllHfw0,26230
57
- openepd/model/org.py,sha256=zxBt-c2f1nhJUqygUzhsq6AEEwfNiYGquhgYvcUfvSo,7771
57
+ openepd/model/org.py,sha256=XIBDIiu-s_GZ9op9X8Eo-BAwGMUC_ecajMERj7FtoM0,7612
58
58
  openepd/model/pcr.py,sha256=7nf6ATofdrlPt81vdU6p0E8n_ftFEuCEIKxtYlFwclw,5476
59
59
  openepd/model/specs/README.md,sha256=UGhSiFJ9hOxT1mZl-5ZrhkOrPKf1W_gcu5CI9hzV7LU,2430
60
60
  openepd/model/specs/__init__.py,sha256=YbyiLnQRQIX9mEdfQRVP3Rp1q3iNwujQMBbDK2-CYdM,3836
@@ -125,7 +125,7 @@ openepd/model/specs/singular/fire_and_smoke_protection.py,sha256=1uyEGdMAsboYORH
125
125
  openepd/model/specs/singular/furnishings.py,sha256=4bReWm8FSWHRyq6vmjPm7ZixFIau7J8JF1gtZq6_jVA,5732
126
126
  openepd/model/specs/singular/grouting.py,sha256=pg2tX3W7a2TQ3z_eyFYGlBJY3WEwn6JlZyqM3PQIJcU,934
127
127
  openepd/model/specs/singular/manufacturing_inputs.py,sha256=iIkkBMl13GmuphgvDnh-f4SmilviNrUSDopUfxtJAdc,5699
128
- openepd/model/specs/singular/masonry.py,sha256=f6nph-gscAmVeJ60bG-ebto5kz0fgh0LY27n0VutGFA,3165
128
+ openepd/model/specs/singular/masonry.py,sha256=5UOu6Upiz7wHbbTqASIminlwan2I1X2IhgQRG1hcVlU,2759
129
129
  openepd/model/specs/singular/material_handling.py,sha256=iEeUx1hNKla669y0zl-QAJn6xA7ceq75aRG8qT0p56Q,1274
130
130
  openepd/model/specs/singular/mechanical.py,sha256=VvW9PQ3VI8GCaqu5MKUmxjmcaHkKDAHAIhLyK-DVlUw,9862
131
131
  openepd/model/specs/singular/mechanical_insulation.py,sha256=h-Ir-jiLF-cpiHap4wx5G4fpvsRRKzqcGVXb5U5aqvQ,1607
@@ -150,7 +150,7 @@ openepd/model/validation/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA
150
150
  openepd/model/validation/common.py,sha256=nzZoboF8Hab8WpQA4HGkqe7hds-SLJylh_QhasrEtZs,2518
151
151
  openepd/model/validation/enum.py,sha256=0nRnjwmObw8ERQYWRWbovZjm90CMHi1Sc-UeNxCFnsc,1846
152
152
  openepd/model/validation/numbers.py,sha256=r15pLe8oWY1tshazQvis_suVXpC6PUcPzKTvj3QTL9Q,851
153
- openepd/model/validation/quantity.py,sha256=mP4gIkeOGZuHRhprsf_BX11Cic75NssYxOTkQ1Cyu9c,20230
153
+ openepd/model/validation/quantity.py,sha256=mUvWoSbxEQ04ZYGQHwC6uYM9ETEBJxcwsSq8ngDvgx8,20228
154
154
  openepd/model/versioning.py,sha256=wBZdOVL3ND9FMIRU9PS3vx9M_7MBiO70xYPQvPez6po,4522
155
155
  openepd/patch_pydantic.py,sha256=bO7U5HqthFol0vfycb0a42UAGL3KOQ8-9MW4yCWOFP0,4150
156
156
  openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -158,7 +158,7 @@ openepd/utils/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
158
158
  openepd/utils/functional.py,sha256=sm7od2_UE-cNToezBlwFQ1TCUJub1tz6VykA1X8XH-I,1274
159
159
  openepd/utils/mapping/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
160
160
  openepd/utils/mapping/common.py,sha256=WphCzwQQlzX11tUk88Ubyq3QPBLvH0tBPSIuH0kmiug,7339
161
- openepd-6.31.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
162
- openepd-6.31.1.dist-info/METADATA,sha256=PIE-lDX5YPJSNeLHNt3sKjTCQIl1vidxMMnK0r8k2RQ,9827
163
- openepd-6.31.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
164
- openepd-6.31.1.dist-info/RECORD,,
161
+ openepd-6.32.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
162
+ openepd-6.32.0.dist-info/METADATA,sha256=eagaMXfquwAt_2AXDJyK2NyJsu_8eRgsj1lzulvk_Fg,9827
163
+ openepd-6.32.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
164
+ openepd-6.32.0.dist-info/RECORD,,