openepd 6.25.0__py3-none-any.whl → 6.27.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.25.0"
16
+ VERSION = "6.27.0"
@@ -0,0 +1,15 @@
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
+ #
@@ -0,0 +1,168 @@
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
+ __all__ = ["ScopePhases"]
17
+
18
+
19
+ from enum import StrEnum
20
+
21
+ from openepd.utils.functional import classproperty
22
+
23
+
24
+ # noinspection PyMethodParameters
25
+ class ScopePhases(StrEnum):
26
+ """Container of common way to specify known scope phases under OpenEPD standard."""
27
+
28
+ A1 = "A1"
29
+ A2 = "A2"
30
+ A3 = "A3"
31
+ A1A2A3 = "A1A2A3"
32
+ A4 = "A4"
33
+ A5 = "A5"
34
+
35
+ B1 = "B1"
36
+ B2 = "B2"
37
+ B3 = "B3"
38
+ B4 = "B4"
39
+ B5 = "B5"
40
+ B6 = "B6"
41
+ B7 = "B7"
42
+
43
+ C1 = "C1"
44
+ C2 = "C2"
45
+ C3 = "C3"
46
+ C4 = "C4"
47
+
48
+ D = "D"
49
+
50
+ @classproperty
51
+ def a1a2a3(cls) -> str:
52
+ return cls.A1A2A3.lower()
53
+
54
+ @classproperty
55
+ def a1(cls) -> str:
56
+ return cls.A1.lower()
57
+
58
+ @classproperty
59
+ def a2(cls) -> str:
60
+ return cls.A2.lower()
61
+
62
+ @classproperty
63
+ def a3(cls) -> str:
64
+ return cls.A3.lower()
65
+
66
+ @classproperty
67
+ def a4(cls) -> str:
68
+ return cls.A4.lower()
69
+
70
+ @classproperty
71
+ def a5(cls) -> str:
72
+ return cls.A5.lower()
73
+
74
+ @classproperty
75
+ def b1(cls) -> str:
76
+ return cls.B1.lower()
77
+
78
+ @classproperty
79
+ def b2(cls) -> str:
80
+ return cls.B2.lower()
81
+
82
+ @classproperty
83
+ def b3(cls) -> str:
84
+ return cls.B3.lower()
85
+
86
+ @classproperty
87
+ def b4(cls) -> str:
88
+ return cls.B4.lower()
89
+
90
+ @classproperty
91
+ def b5(cls) -> str:
92
+ return cls.B5.lower()
93
+
94
+ @classproperty
95
+ def b6(cls) -> str:
96
+ return cls.B6.lower()
97
+
98
+ @classproperty
99
+ def b7(cls) -> str:
100
+ return cls.B7.lower()
101
+
102
+ @classproperty
103
+ def c1(cls) -> str:
104
+ return cls.C1.lower()
105
+
106
+ @classproperty
107
+ def c2(cls) -> str:
108
+ return cls.C2.lower()
109
+
110
+ @classproperty
111
+ def c3(cls) -> str:
112
+ return cls.C3.lower()
113
+
114
+ @classproperty
115
+ def c4(cls) -> str:
116
+ return cls.C4.lower()
117
+
118
+ @classproperty
119
+ def d(cls) -> str:
120
+ return cls.D.lower()
121
+
122
+ @classmethod
123
+ def a_scopes(cls, *, lowercase: bool = False) -> tuple[str, ...]:
124
+ result = (ScopePhases.A1A2A3, ScopePhases.A1, ScopePhases.A2, ScopePhases.A3, ScopePhases.A4, ScopePhases.A5)
125
+ if lowercase:
126
+ return cls._lowercase(result)
127
+ return result
128
+
129
+ @classmethod
130
+ def b_scopes(cls, *, lowercase: bool = False) -> tuple[str, ...]:
131
+ result = (
132
+ ScopePhases.B1,
133
+ ScopePhases.B2,
134
+ ScopePhases.B3,
135
+ ScopePhases.B4,
136
+ ScopePhases.B5,
137
+ ScopePhases.B6,
138
+ ScopePhases.B7,
139
+ )
140
+ if lowercase:
141
+ return cls._lowercase(result)
142
+
143
+ return result
144
+
145
+ @classmethod
146
+ def c_scopes(cls, *, lowercase: bool = False) -> tuple[str, ...]:
147
+ result = (ScopePhases.C1, ScopePhases.C2, ScopePhases.C3, ScopePhases.C4)
148
+ if lowercase:
149
+ return cls._lowercase(result)
150
+ return result
151
+
152
+ @classmethod
153
+ def d_scopes(cls, *, lowercase: bool = False) -> tuple[str, ...]:
154
+ result = (ScopePhases.D,)
155
+ if lowercase:
156
+ return cls._lowercase(result)
157
+ return result
158
+
159
+ @classmethod
160
+ def all_scopes(cls, *, lowercase: bool = False) -> tuple[str, ...]:
161
+ result = cls.a_scopes() + cls.b_scopes() + cls.c_scopes() + cls.d_scopes()
162
+ if lowercase:
163
+ return cls._lowercase(result)
164
+ return result
165
+
166
+ @classmethod
167
+ def _lowercase(cls, scopes: tuple[str, ...]) -> tuple[str, ...]:
168
+ return tuple(scope.lower() for scope in scopes)
openepd/model/common.py CHANGED
@@ -13,13 +13,24 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ from collections.abc import Callable, Generator
16
17
  from enum import StrEnum
18
+ import re
17
19
  from typing import Annotated, Any
18
20
 
19
21
  from openepd.compat.pydantic import pyd
20
22
  from openepd.model.base import BaseOpenEpdSchema
21
23
  from openepd.model.validation.numbers import RatioFloat
22
24
 
25
+ DATA_URL_REGEX = r"^data:([-\w]+\/[-+\w.]+)?(;?\w+=[-\w]+)*(;base64)?,.*$"
26
+ """
27
+ Regular expression pattern for matching Data URLs.
28
+
29
+ A Data URL is a URI scheme that allows you to embed small data items inline
30
+ in web pages as if they were external resources.
31
+ The pattern matches the following format: data:[<media-type>][;base64],<data>
32
+ """
33
+
23
34
 
24
35
  class Amount(BaseOpenEpdSchema):
25
36
  """A value-and-unit pairing for amounts that do not have an uncertainty."""
@@ -313,3 +324,15 @@ class EnumGroupingAware:
313
324
  def get_groupings(cls) -> list[list]:
314
325
  """Return logical groupings of the values."""
315
326
  return []
327
+
328
+
329
+ class DataUrl(str):
330
+ @classmethod
331
+ def __get_validators__(cls) -> Generator[Callable[[str], str], None, None]:
332
+ def validator(v: str) -> str:
333
+ if re.compile(DATA_URL_REGEX).match(v):
334
+ return v
335
+ msg = "Value must be a valid dataUrl"
336
+ raise ValueError(msg)
337
+
338
+ yield validator
@@ -203,6 +203,9 @@ class WithVerifierMixin(pyd.BaseModel):
203
203
  third_party_verifier_email: pyd.EmailStr | None = pyd.Field(
204
204
  description="Email address of the third party verifier", example="john.doe@example.com", default=None
205
205
  )
206
+ third_party_verifier_name: str | None = pyd.Field(
207
+ description="The publishable name of the third party verifier", example="John Doe", default=None
208
+ )
206
209
 
207
210
 
208
211
  class WithEpdDeveloperMixin(pyd.BaseModel):
openepd/model/lcia.py CHANGED
@@ -522,6 +522,7 @@ class LCIAMethod(StrEnum):
522
522
  CML_1992 = "CML 1992"
523
523
  RECIPE_2016 = "ReCiPe 2016"
524
524
  RECIPE_2008 = "ReCiPe 2008"
525
+ GWP_GHG = "GWP-GHG"
525
526
  LIME2 = "LIME2"
526
527
 
527
528
  @classmethod
openepd/model/org.py CHANGED
@@ -13,15 +13,24 @@
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
+ import math
17
+ from typing import Annotated, Final, Optional
17
18
 
18
19
  from openlocationcode import openlocationcode
19
20
 
20
21
  from openepd.compat.pydantic import pyd
21
22
  from openepd.model.base import BaseOpenEpdSchema
22
- from openepd.model.common import Location, WithAltIdsMixin, WithAttachmentsMixin
23
+ from openepd.model.common import DataUrl, Location, WithAltIdsMixin, WithAttachmentsMixin
23
24
  from openepd.model.validation.common import ReferenceStr
24
25
 
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
+
25
34
 
26
35
  class OrgRef(BaseOpenEpdSchema):
27
36
  """Represents Organisation with minimal data."""
@@ -85,6 +94,21 @@ class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
85
94
  default=None,
86
95
  description="Location of a place of business, preferably the corporate headquarters.",
87
96
  )
97
+ logo: pyd.AnyUrl | DataUrl | None = pyd.Field(
98
+ default=None,
99
+ description=(
100
+ "URL pointer to, or dataURL, for a square logo for the company, preferably 300x300 pixels."
101
+ "A logo of the type used on social media platforms such as LinkedIn is recommended."
102
+ ),
103
+ example="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
104
+ )
105
+
106
+ @pyd.validator("logo")
107
+ 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)
111
+ return v
88
112
 
89
113
 
90
114
  class PlantRef(BaseOpenEpdSchema):
@@ -0,0 +1,36 @@
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
+
17
+ # Portions of this file are sourced from https://github.com/django/django/blob/main/django/utils/functional.py
18
+ # Copyright (c) Django Software Foundation and individual contributors.
19
+ # Licensed under the BSD 3-Clause License.
20
+
21
+ # noinspection PyPep8Naming
22
+ class classproperty:
23
+ """
24
+ Decorator that converts a method with a single cls argument into a property
25
+ that can be accessed directly from the class.
26
+ """ # noqa: D205
27
+
28
+ def __init__(self, method=None):
29
+ self.fget = method
30
+
31
+ def __get__(self, instance, cls=None):
32
+ return self.fget(cls)
33
+
34
+ def getter(self, method):
35
+ self.fget = method
36
+ return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openepd
3
- Version: 6.25.0
3
+ Version: 6.27.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=hDgSAJf2ChxmeWlgYCHYlntPXqIh3A42Hhy4RbDRnLc,639
2
+ openepd/__version__.py,sha256=HmKYYpYPt8TvWu0Hh2PXVwOFCv8fcayILaKwQX8TeWk,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
@@ -38,21 +38,23 @@ openepd/bundle/writer.py,sha256=kX1vJ3z96W1TxYwy152hHbmHul_7jcxUXV8psOhtr18,8564
38
38
  openepd/compat/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
39
39
  openepd/compat/compat_functional_validators.py,sha256=aWg3a80fqT8zjN0S260N-Ad2WFKAaB8ByN7ArBW3NMA,834
40
40
  openepd/compat/pydantic.py,sha256=HZJmAiYO7s-LLcFHuj3iXS4MGOITExZYn2rPmteoqNI,1146
41
+ openepd/constants/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
42
+ openepd/constants/scope_phases.py,sha256=zcK7ytSbCNE76tX0vZMWbEUAFgFSifXU9YG_s3vvMqw,4035
41
43
  openepd/m49/__init__.py,sha256=AApOMp9PJPMXZbPB4piedqKtgHE01mlj_MyF3kf519U,718
42
44
  openepd/m49/const.py,sha256=bkYu6J7dQNVb2-nNkjy97uMpt64vICX5o-PHr0lk_90,31833
43
45
  openepd/m49/utils.py,sha256=vQl0wMXtYS2b7NeLIWilDNUopq3MATmLnhEFcMYTeZA,7256
44
46
  openepd/model/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
45
47
  openepd/model/base.py,sha256=6rP6r-7NwKC6JLFB24v2w4Z-_f_w6ZSNLte5zyj5o70,9928
46
48
  openepd/model/category.py,sha256=reeOVRDuZPYU77EMwG0K5VjnK2H9yOGxT0PJXXqrjEk,1639
47
- openepd/model/common.py,sha256=jblgVf_ASMelxe_-mk6fnOIyM4n0TY4PJ8MeLX8O92c,13134
48
- openepd/model/declaration.py,sha256=72oR19n0CfJx4DhMXGYrLYY_sUB4awZzpr4Tr3Jcnks,13814
49
+ openepd/model/common.py,sha256=WM6ankkeZazVzKwbn4s5FHwENMTIKWsgdRJkX7DYmpg,13875
50
+ openepd/model/declaration.py,sha256=1WXTRlSRIUXTJJhL_e7BkoFyZ9gJKZH7c8pFVxihGFU,13980
49
51
  openepd/model/epd.py,sha256=_1P9-etXfLAVmg44pglnBUvoEyQ50odNBsUrv_OeS9M,12308
50
52
  openepd/model/factory.py,sha256=UWSGpfCr3GiMTP4rzBkwqxzbXB6GKZ_5Okb1Dqa_4aA,2701
51
53
  openepd/model/generic_estimate.py,sha256=zLGTyf4Uzmp2C0m-J1ePWItSz2RGdZ0OiGPWC5nhKHk,3992
52
54
  openepd/model/geography.py,sha256=Jx7NIDdk_sIvwyh-7YxnIjAwIHW2HCQK7UtFGM2xKtw,42095
53
55
  openepd/model/industry_epd.py,sha256=QZr7OhgGkzqZ8H5p6dCIVk9zSHEYtK3y9Nk-DvkFMyk,4011
54
- openepd/model/lcia.py,sha256=5SkiFdJKtza2fc2BBuepbl0TAi-UYsjSDR0ERj9ffmk,26206
55
- openepd/model/org.py,sha256=AAyXx42phVn3fZN0_Ga8CJb2iKh_dfVXsvO1lEtDovM,6832
56
+ openepd/model/lcia.py,sha256=PdaSZz02PMBJl_t2B_dmkcbzIFjA7iACrWj1fllHfw0,26230
57
+ openepd/model/org.py,sha256=zxBt-c2f1nhJUqygUzhsq6AEEwfNiYGquhgYvcUfvSo,7771
56
58
  openepd/model/pcr.py,sha256=7nf6ATofdrlPt81vdU6p0E8n_ftFEuCEIKxtYlFwclw,5476
57
59
  openepd/model/specs/README.md,sha256=UGhSiFJ9hOxT1mZl-5ZrhkOrPKf1W_gcu5CI9hzV7LU,2430
58
60
  openepd/model/specs/__init__.py,sha256=toVWd8_jxmAf7gRwpoXQpLfZW6Cl-NwveoduMXhECms,3903
@@ -151,9 +153,10 @@ openepd/model/versioning.py,sha256=wBZdOVL3ND9FMIRU9PS3vx9M_7MBiO70xYPQvPez6po,4
151
153
  openepd/patch_pydantic.py,sha256=bO7U5HqthFol0vfycb0a42UAGL3KOQ8-9MW4yCWOFP0,4150
152
154
  openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
153
155
  openepd/utils/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
156
+ openepd/utils/functional.py,sha256=sm7od2_UE-cNToezBlwFQ1TCUJub1tz6VykA1X8XH-I,1274
154
157
  openepd/utils/mapping/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
155
158
  openepd/utils/mapping/common.py,sha256=WphCzwQQlzX11tUk88Ubyq3QPBLvH0tBPSIuH0kmiug,7339
156
- openepd-6.25.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
157
- openepd-6.25.0.dist-info/METADATA,sha256=sXZ1WJU-4BzBy9ZSCjbmL_cvd_44recegC2mmALLGlE,9827
158
- openepd-6.25.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
159
- openepd-6.25.0.dist-info/RECORD,,
159
+ openepd-6.27.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
160
+ openepd-6.27.0.dist-info/METADATA,sha256=1qocBkldct5oXEPyBq3C6KN9M9OErubIul8X68L_M1U,9827
161
+ openepd-6.27.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
162
+ openepd-6.27.0.dist-info/RECORD,,