openepd 7.13.4__py3-none-any.whl → 7.14.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 = "7.13.4"
16
+ VERSION = "7.14.0"
openepd/model/common.py CHANGED
@@ -14,14 +14,16 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  from enum import StrEnum
17
- from typing import Annotated, Any, Self
17
+ import math
18
+ import re
19
+ from typing import Annotated, Any, Final, Self
18
20
 
19
21
  import pydantic
20
22
  import pydantic_core
21
23
 
22
24
  from openepd.model.base import BaseOpenEpdSchema
23
25
 
24
- DATA_URL_REGEX = r"^data:([-\w]+\/[-+\w.]+)?(;?\w+=[-\w]+)*(;base64)?,.*$"
26
+ DATA_URL_REGEX = r"^data:([-\w]+\/[-+\w.]+)?(;?\w+=[-\w]+)*(;base64)?,(.*)$"
25
27
  """
26
28
  Regular expression pattern for matching Data URLs.
27
29
 
@@ -30,6 +32,14 @@ in web pages as if they were external resources.
30
32
  The pattern matches the following format: data:[<media-type>][;base64],<data>
31
33
  """
32
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
+
33
43
 
34
44
  class Amount(BaseOpenEpdSchema):
35
45
  """A value-and-unit pairing for amounts that do not have an uncertainty."""
@@ -372,3 +382,20 @@ class EnumGroupingAware:
372
382
  def get_groupings(cls) -> list[list]:
373
383
  """Return logical groupings of the values."""
374
384
  return []
385
+
386
+
387
+ def validate_data_url(v: pydantic.AnyUrl | None, max_length: int) -> None:
388
+ """
389
+ Validate data URL.
390
+
391
+ :param v: data URL string.
392
+ :param max_length: maximum allowed length.
393
+ :raises ValueError: if the data URL exceeds the maximum length or has invalid format.
394
+ :return: None
395
+ """
396
+ if v and len(v) > max_length:
397
+ msg = f"URL must not exceed {max_length} characters"
398
+ raise ValueError(msg)
399
+ if v and v.scheme == "data" and not re.compile(DATA_URL_REGEX).match(str(v)):
400
+ msg = "Invalid data URL format"
401
+ raise ValueError(msg)
@@ -16,14 +16,11 @@
16
16
  import abc
17
17
  import datetime
18
18
  from enum import StrEnum
19
- import math
20
- import re
21
- from typing import Final
22
19
 
23
20
  import pydantic
24
21
 
25
22
  from openepd.model.base import BaseOpenEpdSchema, OpenXpdUUID, RootDocument
26
- from openepd.model.common import DATA_URL_REGEX, Amount
23
+ from openepd.model.common import DATA_URL_IMAGE_MAX_LENGTH, Amount, validate_data_url
27
24
  from openepd.model.geography import Geography
28
25
  from openepd.model.org import Org
29
26
  from openepd.model.pcr import Pcr
@@ -36,14 +33,6 @@ DEVELOPER_DESCRIPTION = "The organization responsible for the underlying LCA (an
36
33
  PROGRAM_OPERATOR_DESCRIPTION = "JSON object for program operator Org"
37
34
  THIRD_PARTY_VERIFIER_DESCRIPTION = "JSON object for Org that performed a critical review of the EPD data"
38
35
 
39
- PRODUCT_IMAGE_MAX_LENGTH: Final[int] = math.ceil(32 * 1024 * 4 / 3)
40
- """
41
- Maximum length for product_image, product_image_small fields.
42
-
43
- Image file size must be less than 32KB. Base64 encoding overhead (approximately 33%) requires
44
- limiting the encoded string length to 4/3 of the file size limit.
45
- """
46
-
47
36
 
48
37
  class BaseDeclaration(RootDocument, abc.ABC):
49
38
  """Base class for declaration-related documents (EPDs, Industry-wide EPDs, Generic Estimates)."""
@@ -194,12 +183,7 @@ class BaseDeclaration(RootDocument, abc.ABC):
194
183
 
195
184
  @pydantic.field_validator("product_image", "product_image_small")
196
185
  def validate_product_image(cls, v: pydantic.AnyUrl | None) -> pydantic.AnyUrl | None:
197
- if v and len(v) > PRODUCT_IMAGE_MAX_LENGTH:
198
- msg = f"URL must not exceed {PRODUCT_IMAGE_MAX_LENGTH} characters"
199
- raise ValueError(msg)
200
- if v and v.scheme == "data" and not re.compile(DATA_URL_REGEX).match(str(v)):
201
- msg = "Invalid data URL format"
202
- raise ValueError(msg)
186
+ validate_data_url(v, DATA_URL_IMAGE_MAX_LENGTH)
203
187
  return v
204
188
 
205
189
 
openepd/model/epd.py CHANGED
@@ -16,7 +16,13 @@
16
16
  import pydantic
17
17
 
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
+ Ingredient,
22
+ WithAltIdsMixin,
23
+ WithAttachmentsMixin,
24
+ validate_data_url,
25
+ )
20
26
  from openepd.model.declaration import (
21
27
  DEVELOPER_DESCRIPTION,
22
28
  PROGRAM_OPERATOR_DESCRIPTION,
@@ -243,6 +249,11 @@ class EpdPreviewV0(
243
249
  msg = "Invalid doctype"
244
250
  raise ValueError(msg)
245
251
 
252
+ @pydantic.field_validator("product_usage_image", "manufacturing_image")
253
+ def validate_product_usage_image(cls, v: pydantic.AnyUrl | None) -> pydantic.AnyUrl | None:
254
+ validate_data_url(v, DATA_URL_IMAGE_MAX_LENGTH)
255
+ return v
256
+
246
257
 
247
258
  EpdPreview = EpdPreviewV0
248
259
 
openepd/model/org.py CHANGED
@@ -13,26 +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
- import re
18
- from typing import Any, Final, Optional
16
+ from typing import Any, Optional
19
17
 
20
18
  from openlocationcode import openlocationcode
21
19
  import pydantic
22
20
  from pydantic import ConfigDict
23
21
 
24
22
  from openepd.model.base import BaseOpenEpdSchema
25
- from openepd.model.common import DATA_URL_REGEX, Location, WithAltIdsMixin, WithAttachmentsMixin
23
+ from openepd.model.common import (
24
+ DATA_URL_IMAGE_MAX_LENGTH,
25
+ Location,
26
+ WithAltIdsMixin,
27
+ WithAttachmentsMixin,
28
+ validate_data_url,
29
+ )
26
30
  from openepd.model.validation.common import ReferenceStr
27
31
 
28
- ORG_LOGO_MAX_LENGTH: Final[int] = math.ceil(32 * 1024 * 4 / 3)
29
- """
30
- Maximum length of Org.logo field.
31
-
32
- Logo file size must be less than 32KB. Base64 encoding overhead (approximately 33%) requires
33
- limiting the encoded string length to 4/3 of the file size limit.
34
- """
35
-
36
32
 
37
33
  class OrgRef(BaseOpenEpdSchema):
38
34
  """Represents Organisation with minimal data."""
@@ -142,12 +138,7 @@ class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
142
138
 
143
139
  @pydantic.field_validator("logo")
144
140
  def _validate_logo(cls, v: pydantic.AnyUrl | None) -> pydantic.AnyUrl | None:
145
- if v and len(v) > ORG_LOGO_MAX_LENGTH:
146
- msg = f"Logo URL must not exceed {ORG_LOGO_MAX_LENGTH} characters"
147
- raise ValueError(msg)
148
- if v and v.scheme == "data" and not re.compile(DATA_URL_REGEX).match(str(v)):
149
- msg = "Invalid data URL format"
150
- raise ValueError(msg)
141
+ validate_data_url(v, DATA_URL_IMAGE_MAX_LENGTH)
151
142
  return v
152
143
 
153
144
 
@@ -232,6 +223,10 @@ class Plant(PlantRef, WithAttachmentsMixin, WithAltIdsMixin):
232
223
  msg = "Incorrect pluscode for plant"
233
224
  raise ValueError(msg)
234
225
 
226
+ if not openlocationcode.isFull(pluscode):
227
+ msg = "Passed Open Location Code is not a valid full code"
228
+ raise ValueError(msg)
229
+
235
230
  if not web_domain:
236
231
  msg = "Incorrect web_domain for plant"
237
232
  raise ValueError(msg)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openepd
3
- Version: 7.13.4
3
+ Version: 7.14.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=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
2
- openepd/__version__.py,sha256=Vwc6354wyk-FYpA6SgKgC-HMbPM5MyrY1DsnKvXk_Pc,639
2
+ openepd/__version__.py,sha256=4_OOwkPWzDgb1S9aW43U_I7XLkBn0l8_JXtmi6r1ghA,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=mjTT8eGtfj6Fgp-wcs0cCWA7DJo1KL_iQ75rgKkaY3c,8037
@@ -43,15 +43,15 @@ openepd/m49/utils.py,sha256=0UvdtC9gtvRA5WT_hJDIuQR0RSrnx-S34wwwBRM_tsM,7807
43
43
  openepd/model/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
44
44
  openepd/model/base.py,sha256=EVmSzFqIiHQuKODJw_xncYGLy8T9p895MRZdkaeK4Fo,13648
45
45
  openepd/model/category.py,sha256=iyzzAsiVwW4zJ61oYsm9Sy-sEBA71-aMFXcJP1Y-dPI,1734
46
- openepd/model/common.py,sha256=r6VbqOaMAAqEEbXJ0l-_kLDFQJZW_qv5XdZFyOFEFW8,15016
47
- openepd/model/declaration.py,sha256=ryB0QmbdtufUEwUDZqzudlEAnJ_-4wPR9dwfC8i4zUc,15785
48
- openepd/model/epd.py,sha256=cS3ZT_72v0gFcEhuDQrF_Zs8-Cob3FjyipUbBpc-xFM,12760
46
+ openepd/model/common.py,sha256=L-nOsl4MmfSMFOehDyJaltwww--W-MR278-F4iVoVic,15924
47
+ openepd/model/declaration.py,sha256=5uNk2y8bJo0DfsCWdDf4F2YkN0RGeiApAA3oZ67uO-0,15194
48
+ openepd/model/epd.py,sha256=N7dzxfgEOvuNOHatr3_KvwRatVJXQ4mHKTbh7FCUrC0,13077
49
49
  openepd/model/factory.py,sha256=UWSGpfCr3GiMTP4rzBkwqxzbXB6GKZ_5Okb1Dqa_4aA,2701
50
50
  openepd/model/generic_estimate.py,sha256=_R18Uz-hvxtSBl53D0_OkwVCWvoa2nIDjBdec6vEPDE,4304
51
51
  openepd/model/geography.py,sha256=Jx7NIDdk_sIvwyh-7YxnIjAwIHW2HCQK7UtFGM2xKtw,42095
52
52
  openepd/model/industry_epd.py,sha256=Cqn01IUNSZqRkyU05TwtOLXDKlg0YnGzqvKL8A__zbI,4061
53
53
  openepd/model/lcia.py,sha256=6o6GNwLaQaWyYAXCnHl2aAgDa12_v7DwpME5BfVMezk,32780
54
- openepd/model/org.py,sha256=ij_GEFEShRh-k4o29gSowiaJbCuyNqWu61YL3BNq0FM,9369
54
+ openepd/model/org.py,sha256=1THgP8vLuXEWsHkYDT4k-mdSQ1vKiF9Y4AjNKq0Og_4,9016
55
55
  openepd/model/pcr.py,sha256=cu3EakCAjBCkcb_AaLXB-xEjY0mlG-wJe74zGc5tdS0,5637
56
56
  openepd/model/specs/README.md,sha256=UGhSiFJ9hOxT1mZl-5ZrhkOrPKf1W_gcu5CI9hzV7LU,2430
57
57
  openepd/model/specs/__init__.py,sha256=-3uaImbGfOkRdpHQkhHe0eimAg0UBntR3WiT21Pmt1I,3837
@@ -156,7 +156,7 @@ openepd/utils/mapping/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dv
156
156
  openepd/utils/mapping/common.py,sha256=hxfN-WW2WLwE_agQzf_mhvz6OHq5WWlr24uZ1S81k4Y,8426
157
157
  openepd/utils/mapping/geography.py,sha256=1_-dvLk11Hqn-K58yUI5pQ5X5gsnJPFlFT7JK2Rdoeg,2396
158
158
  openepd/utils/markdown.py,sha256=RQmudPhb4QU1I4-S-VV2WFbzzq2Po09kbpjjKbwkA9E,1830
159
- openepd-7.13.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
160
- openepd-7.13.4.dist-info/METADATA,sha256=2m7uh56T5qhea2R7E6XOO_KohwCfb1IsEuXKjGeQXYY,9811
161
- openepd-7.13.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
162
- openepd-7.13.4.dist-info/RECORD,,
159
+ openepd-7.14.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
160
+ openepd-7.14.0.dist-info/METADATA,sha256=gKmM-w5poyAM8GYRAytiMrIIFQ42fVmXeyrXj32qCJw,9811
161
+ openepd-7.14.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
162
+ openepd-7.14.0.dist-info/RECORD,,