openepd 6.13.2__py3-none-any.whl → 6.15.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 (61) hide show
  1. openepd/__version__.py +1 -1
  2. openepd/api/average_dataset/generic_estimate_sync_api.py +2 -1
  3. openepd/api/average_dataset/industry_epd_sync_api.py +2 -1
  4. openepd/api/base_sync_client.py +10 -8
  5. openepd/api/common.py +17 -11
  6. openepd/api/dto/common.py +1 -1
  7. openepd/api/epd/sync_api.py +2 -1
  8. openepd/bundle/base.py +5 -4
  9. openepd/bundle/reader.py +13 -7
  10. openepd/bundle/writer.py +11 -6
  11. openepd/compat/pydantic.py +1 -1
  12. openepd/m49/__init__.py +2 -0
  13. openepd/m49/const.py +1 -1
  14. openepd/m49/utils.py +16 -10
  15. openepd/model/base.py +19 -15
  16. openepd/model/common.py +22 -19
  17. openepd/model/declaration.py +2 -2
  18. openepd/model/epd.py +2 -1
  19. openepd/model/factory.py +5 -3
  20. openepd/model/lcia.py +30 -6
  21. openepd/model/org.py +6 -3
  22. openepd/model/pcr.py +2 -2
  23. openepd/model/specs/__init__.py +36 -0
  24. openepd/model/specs/asphalt.py +3 -3
  25. openepd/model/specs/base.py +2 -1
  26. openepd/model/specs/enums.py +695 -695
  27. openepd/model/specs/range/accessories.py +1 -1
  28. openepd/model/specs/range/aluminium.py +1 -1
  29. openepd/model/specs/range/cladding.py +10 -10
  30. openepd/model/specs/range/cmu.py +0 -3
  31. openepd/model/specs/range/concrete.py +1 -1
  32. openepd/model/specs/range/conveying_equipment.py +2 -2
  33. openepd/model/specs/range/electrical.py +18 -18
  34. openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +1 -1
  35. openepd/model/specs/range/finishes.py +16 -16
  36. openepd/model/specs/range/fire_and_smoke_protection.py +3 -3
  37. openepd/model/specs/range/furnishings.py +17 -17
  38. openepd/model/specs/range/manufacturing_inputs.py +4 -4
  39. openepd/model/specs/range/masonry.py +1 -1
  40. openepd/model/specs/range/mechanical.py +6 -6
  41. openepd/model/specs/range/network_infrastructure.py +3 -3
  42. openepd/model/specs/range/openings.py +17 -17
  43. openepd/model/specs/range/other_materials.py +4 -4
  44. openepd/model/specs/range/plumbing.py +5 -5
  45. openepd/model/specs/range/precast_concrete.py +2 -2
  46. openepd/model/specs/range/steel.py +16 -11
  47. openepd/model/specs/range/thermal_moisture_protection.py +12 -12
  48. openepd/model/specs/range/wood.py +4 -7
  49. openepd/model/specs/singular/__init__.py +109 -1
  50. openepd/model/specs/singular/aluminium.py +2 -1
  51. openepd/model/specs/singular/furnishings.py +10 -10
  52. openepd/model/specs/singular/steel.py +10 -2
  53. openepd/model/validation/common.py +10 -6
  54. openepd/model/validation/enum.py +4 -2
  55. openepd/model/validation/quantity.py +8 -1
  56. openepd/model/versioning.py +8 -6
  57. openepd/patch_pydantic.py +2 -1
  58. {openepd-6.13.2.dist-info → openepd-6.15.0.dist-info}/METADATA +1 -1
  59. {openepd-6.13.2.dist-info → openepd-6.15.0.dist-info}/RECORD +61 -61
  60. {openepd-6.13.2.dist-info → openepd-6.15.0.dist-info}/LICENSE +0 -0
  61. {openepd-6.13.2.dist-info → openepd-6.15.0.dist-info}/WHEEL +0 -0
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.13.2"
16
+ VERSION = "6.15.0"
@@ -135,7 +135,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
135
135
  """
136
136
  ge_id = ge.id
137
137
  if not ge_id:
138
- raise ValueError("The ID must be set to edit a GenericEstimate.")
138
+ msg = "The ID must be set to edit a GenericEstimate."
139
+ raise ValueError(msg)
139
140
 
140
141
  response = self._client.do_request(
141
142
  "put",
@@ -90,7 +90,8 @@ class IndustryEpdApi(BaseApiMethodGroup):
90
90
  """
91
91
  iepd_id = iepd.id
92
92
  if not iepd_id:
93
- raise ValueError("The ID must be set to edit a IndustryEpd.")
93
+ msg = "The ID must be set to edit a IndustryEpd."
94
+ raise ValueError(msg)
94
95
 
95
96
  response = self._client.do_request(
96
97
  "put",
@@ -14,14 +14,15 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __all__ = (
17
- "HttpStreamReader",
18
- "SyncHttpClient",
17
+ "USER_AGENT_DEFAULT",
18
+ "BaseApiMethodGroup",
19
19
  "DoRequest",
20
+ "HttpStreamReader",
20
21
  "RetryHandler",
21
- "BaseApiMethodGroup",
22
- "USER_AGENT_DEFAULT",
22
+ "SyncHttpClient",
23
23
  )
24
24
 
25
+ from collections.abc import Callable
25
26
  import datetime
26
27
  from functools import partial, wraps
27
28
  from io import IOBase
@@ -29,7 +30,7 @@ import logging
29
30
  import random
30
31
  import shutil
31
32
  import time
32
- from typing import IO, Any, BinaryIO, Callable, Final, NamedTuple
33
+ from typing import IO, Any, BinaryIO, Final, NamedTuple
33
34
 
34
35
  import requests
35
36
  from requests import PreparedRequest, Response, Session, Timeout
@@ -180,7 +181,7 @@ class SyncHttpClient:
180
181
  self._throttler = Throttler(rate_per_sec=requests_per_sec)
181
182
  self._throttle_retry_timeout: float = (
182
183
  float(throttle_retry_timeout)
183
- if isinstance(throttle_retry_timeout, (float, int))
184
+ if isinstance(throttle_retry_timeout, float | int)
184
185
  else throttle_retry_timeout.total_seconds()
185
186
  )
186
187
  self.user_agent = user_agent
@@ -396,7 +397,8 @@ class SyncHttpClient:
396
397
 
397
398
  response.raise_for_status()
398
399
  # This can't be handled by static checker because of the dynamic nature of the raise_for_status method
399
- raise RuntimeError("This line should never be reached")
400
+ msg = "This line should never be reached"
401
+ raise RuntimeError(msg)
400
402
 
401
403
  def _get_url_for_request(self, path_or_url: str) -> str:
402
404
  """
@@ -451,7 +453,7 @@ class SyncHttpClient:
451
453
  exception = e
452
454
 
453
455
  if exception or response.status_code == requests_codes.service_unavailable:
454
- secs = random.randint(60, 60 * 5)
456
+ secs = random.randint(60, 60 * 5) # noqa: S311
455
457
  logger.warning(
456
458
  "%s %s is unavailable. Attempts left: %s. Waiting %s seconds...", method, url, attempts, secs
457
459
  )
openepd/api/common.py CHANGED
@@ -13,12 +13,12 @@
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 Iterable
16
+ from collections.abc import Callable, Iterable, Iterator
17
17
  from contextlib import contextmanager
18
18
  from datetime import datetime, timedelta
19
19
  import threading
20
20
  from time import sleep
21
- from typing import Callable, Generic, Iterator, cast
21
+ from typing import Generic, cast
22
22
 
23
23
  from requests import Response
24
24
 
@@ -126,20 +126,26 @@ class StreamingListResponse(Iterable[TOpenEpdObject], Generic[TOpenEpdObject]):
126
126
  :return: list of items on the page
127
127
  """
128
128
  if page_num <= 0:
129
- raise ValueError("Page number must be positive")
129
+ msg = "Page number must be positive"
130
+ raise ValueError(msg)
130
131
  if self.__current_page != page_num or force_reload:
131
132
  self.__recent_response = self.__fetch_handler(page_num, self.__page_size)
132
133
  self.__current_page = page_num
133
134
  if self.__recent_response is None:
134
- raise RuntimeError("Response is empty, this should not happen, check if fetch_handler is compatible")
135
+ msg = "Response is empty, this should not happen, check if fetch_handler is compatible"
136
+ raise RuntimeError(msg)
135
137
  if self.__recent_response.payload is None:
136
- raise ValueError("Response does not contain payload")
138
+ msg = "Response does not contain payload"
139
+ raise ValueError(msg)
137
140
  if not isinstance(self.__recent_response.payload, list):
138
- raise ValueError("Response does not contain a list")
141
+ msg = "Response does not contain a list"
142
+ raise ValueError(msg)
139
143
  if self.__recent_response.meta is None:
140
- raise ValueError("Response does not contain meta")
144
+ msg = "Response does not contain meta"
145
+ raise ValueError(msg)
141
146
  if not isinstance(self.__recent_response.meta, PagingMetaMixin):
142
- raise ValueError("Response does not contain paging meta")
147
+ msg = "Response does not contain paging meta"
148
+ raise ValueError(msg)
143
149
  return self.__recent_response.payload
144
150
 
145
151
  def get_paging_meta(self) -> PagingMeta:
@@ -152,7 +158,8 @@ class StreamingListResponse(Iterable[TOpenEpdObject], Generic[TOpenEpdObject]):
152
158
  """
153
159
  paging_meta = cast(PagingMetaMixin, self.get_meta()).paging
154
160
  if paging_meta is None:
155
- raise ValueError("Response does not contain paging meta")
161
+ msg = "Response does not contain paging meta"
162
+ raise ValueError(msg)
156
163
  return paging_meta
157
164
 
158
165
  def get_meta(self) -> MetaCollectionDto:
@@ -206,8 +213,7 @@ class StreamingListResponse(Iterable[TOpenEpdObject], Generic[TOpenEpdObject]):
206
213
  self.goto_page(start_from_page)
207
214
  while True:
208
215
  items = self.goto_page(self.current_page)
209
- for x in items:
210
- yield x
216
+ yield from items
211
217
  if not self.has_next_page():
212
218
  return # no more pages
213
219
  else:
openepd/api/dto/common.py CHANGED
@@ -82,7 +82,7 @@ class MetaCollectionDto(BaseOpenEpdApiModel, pyd_generics.GenericModel, Generic[
82
82
 
83
83
  It is generified by the extension type of similar structure (key-dict value), which should not be a part of OpenEPD
84
84
  spec. This allows to add custom meta objects to the response, which would be ignored by OpenEPD implementors.
85
- """
85
+ """ # noqa: D404
86
86
 
87
87
  ext: TMetaExtension | None = None
88
88
 
@@ -173,7 +173,8 @@ class EpdApi(BaseApiMethodGroup):
173
173
  """
174
174
  epd_id = epd.id
175
175
  if not epd_id:
176
- raise ValueError("The EPD ID must be set to edit an EPD.")
176
+ msg = "The EPD ID must be set to edit an EPD."
177
+ raise ValueError(msg)
177
178
  response = self._client.do_request(
178
179
  "put",
179
180
  f"/epds/{encode_path_param(epd_id)}",
openepd/bundle/base.py CHANGED
@@ -14,8 +14,9 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  import abc
17
+ from collections.abc import Callable, Iterator, Sequence
17
18
  import csv
18
- from typing import IO, Callable, Iterator, Self, Sequence, Type
19
+ from typing import IO, Self
19
20
 
20
21
  from openepd.bundle.model import AssetInfo, AssetType, BundleManifest, RelType
21
22
  from openepd.model.base import TOpenEpdObject
@@ -70,7 +71,7 @@ class BaseBundleReader(BundleMixin, metaclass=abc.ABCMeta):
70
71
  def __enter__(self) -> Self:
71
72
  return self
72
73
 
73
- def __exit__(self, type, value, traceback):
74
+ def __exit__(self, type, value, traceback): # noqa: A002
74
75
  self.close()
75
76
 
76
77
  @abc.abstractmethod
@@ -133,7 +134,7 @@ class BaseBundleReader(BundleMixin, metaclass=abc.ABCMeta):
133
134
  pass
134
135
 
135
136
  @abc.abstractmethod
136
- def read_object_asset(self, obj_class: Type[TOpenEpdObject], asset_ref: AssetRef) -> TOpenEpdObject:
137
+ def read_object_asset(self, obj_class: type[TOpenEpdObject], asset_ref: AssetRef) -> TOpenEpdObject:
137
138
  """Read an object asset by given reference."""
138
139
  pass
139
140
 
@@ -171,7 +172,7 @@ class BaseBundleWriter(BundleMixin, metaclass=abc.ABCMeta):
171
172
  def __enter__(self) -> Self:
172
173
  return self
173
174
 
174
- def __exit__(self, type, value, traceback):
175
+ def __exit__(self, type, value, traceback): # noqa: A002
175
176
  self.close()
176
177
 
177
178
  @abc.abstractmethod
openepd/bundle/reader.py CHANGED
@@ -13,10 +13,11 @@
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, Iterator, Sequence
16
17
  import csv
17
18
  import io
18
19
  from os import PathLike
19
- from typing import IO, Callable, Iterator, Sequence, Type, cast
20
+ from typing import IO, cast
20
21
  import zipfile
21
22
 
22
23
  from openepd.bundle.base import AssetFilter, AssetRef, BaseBundleReader
@@ -88,7 +89,8 @@ class DefaultBundleReader(BaseBundleReader):
88
89
  with self._bundle_archive.open("toc", "r") as toc_stream:
89
90
  toc_reader = csv.DictReader(io.TextIOWrapper(toc_stream, encoding="utf-8"), dialect="toc")
90
91
  if not toc_reader.fieldnames or len(toc_reader.fieldnames) < len(self._TOC_FIELDS):
91
- raise ValueError("The bundle file is not valid. TOC reading error: wrong number of fields")
92
+ msg = "The bundle file is not valid. TOC reading error: wrong number of fields"
93
+ raise ValueError(msg)
92
94
 
93
95
  def root_assets_iter(
94
96
  self,
@@ -141,17 +143,21 @@ class DefaultBundleReader(BaseBundleReader):
141
143
  """Read the blob asset."""
142
144
  asset = self.get_asset_by_ref(asset_ref)
143
145
  if asset is None:
144
- raise ValueError("Asset not found")
146
+ msg = "Asset not found"
147
+ raise ValueError(msg)
145
148
  return self._bundle_archive.open(asset.ref, "r")
146
149
 
147
- def read_object_asset(self, obj_class: Type[TOpenEpdObject], asset_ref: AssetRef) -> TOpenEpdObject:
150
+ def read_object_asset(self, obj_class: type[TOpenEpdObject], asset_ref: AssetRef) -> TOpenEpdObject:
148
151
  """Read the object asset."""
149
152
  asset = self.get_asset_by_ref(asset_ref)
150
153
  if asset is None:
151
- raise ValueError("Asset not found")
154
+ msg = "Asset not found"
155
+ raise ValueError(msg)
152
156
  if obj_class.get_asset_type() is None:
153
- raise ValueError(f"Target object {obj_class.__name__} is not supported asset")
157
+ msg = f"Target object {obj_class.__name__} is not supported asset"
158
+ raise ValueError(msg)
154
159
  if asset.type != obj_class.get_asset_type():
155
- raise ValueError(f"Asset type mismatch. Expected {obj_class.get_asset_type()}, got {asset.type}")
160
+ msg = f"Asset type mismatch. Expected {obj_class.get_asset_type()}, got {asset.type}"
161
+ raise ValueError(msg)
156
162
  with self._bundle_archive.open(asset.ref, "r") as asset_stream:
157
163
  return obj_class.parse_raw(asset_stream.read())
openepd/bundle/writer.py CHANGED
@@ -31,8 +31,9 @@ class DefaultBundleWriter(BaseBundleWriter):
31
31
  """Default bundle writer implementation. Writes the bundle to a ZIP file."""
32
32
 
33
33
  def __init__(self, bundle_file: str | PathLike | IO[bytes], comment: str | None = None):
34
- if isinstance(bundle_file, (PathLike, str)) and Path(bundle_file).exists():
35
- raise ValueError("Amending existing files is not supported yet.")
34
+ if isinstance(bundle_file, PathLike | str) and Path(bundle_file).exists():
35
+ msg = "Amending existing files is not supported yet."
36
+ raise ValueError(msg)
36
37
  self._bundle_archive = zipfile.ZipFile(bundle_file, mode="w")
37
38
  self.__manifest = BundleManifest(
38
39
  format="openEPD Bundle/1.0",
@@ -94,7 +95,8 @@ class DefaultBundleWriter(BaseBundleWriter):
94
95
  """Write an object asset to the bundle. Object means subclass of BaseOpenEpdSchem."""
95
96
  asset_type_str = obj.get_asset_type()
96
97
  if asset_type_str is None:
97
- raise ValueError(f"Object {obj} does not have a valid asset type and can't be written to a bundle.")
98
+ msg = f"Object {obj} does not have a valid asset type and can't be written to a bundle."
99
+ raise ValueError(msg)
98
100
  asset_type = AssetType(asset_type_str)
99
101
  rel_ref_str = self._asset_ref_to_str(rel_asset) if rel_asset is not None else None
100
102
  ref_str = self.__generate_entry_name(
@@ -133,14 +135,16 @@ class DefaultBundleWriter(BaseBundleWriter):
133
135
 
134
136
  def __register_entry(self, asset_info: AssetInfo):
135
137
  if asset_info.ref in self.__added_entries:
136
- raise ValueError(f"Asset {asset_info.ref} already exists in the bundle.")
138
+ msg = f"Asset {asset_info.ref} already exists in the bundle."
139
+ raise ValueError(msg)
137
140
  self._toc_writer.writerow(asset_info.dict(exclude_unset=True, exclude_none=True))
138
141
  self.__added_entries.add(asset_info.ref)
139
142
  type_counter = self.__manifest.assets.count_by_type.get(asset_info.type, 0) + 1
140
143
  self.__manifest.assets.count_by_type[asset_info.type] = type_counter
141
144
  self.__manifest.assets.total_count += 1
142
145
  if asset_info.size is None:
143
- raise ValueError("Size of asset is not set.")
146
+ msg = "Size of asset is not set."
147
+ raise ValueError(msg)
144
148
  self.__manifest.assets.total_size += asset_info.size
145
149
 
146
150
  def __generate_entry_name(self, asset_type: str, extension: str | None = None, file_name: str | None = None) -> str:
@@ -157,7 +161,8 @@ class DefaultBundleWriter(BaseBundleWriter):
157
161
  info = self._bundle_archive.getinfo(f"{asset_type}/")
158
162
  if info.is_dir():
159
163
  return
160
- raise ValueError(f"Object with name {asset_type} already exists in the bundle.")
164
+ msg = f"Object with name {asset_type} already exists in the bundle."
165
+ raise ValueError(msg)
161
166
  except KeyError:
162
167
  self._bundle_archive.mkdir(str(asset_type))
163
168
 
@@ -27,4 +27,4 @@ except ImportError:
27
27
 
28
28
  pydantic = pyd
29
29
 
30
- __all__ = ["pyd", "pydantic", "pyd_generics", "functional_validators"]
30
+ __all__ = ["functional_validators", "pyd", "pyd_generics", "pydantic"]
openepd/m49/__init__.py CHANGED
@@ -14,4 +14,6 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
+ __all__ = ["geo_converter"]
18
+
17
19
  from . import utils as geo_converter # for backwards compatibility
openepd/m49/const.py CHANGED
@@ -1169,7 +1169,7 @@ def is_m49_code(to_check: str) -> bool:
1169
1169
  :param to_check: any string
1170
1170
  :return: `True` if passed string is M49 code, `False` otherwise
1171
1171
  """
1172
- warnings.warn("Use m49.utils.is_m49_code instead.", DeprecationWarning)
1172
+ warnings.warn("Use m49.utils.is_m49_code instead.", DeprecationWarning, stacklevel=2)
1173
1173
  from . import utils
1174
1174
 
1175
1175
  return utils.is_m49_code(to_check)
openepd/m49/utils.py CHANGED
@@ -14,15 +14,15 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __all__ = [
17
+ "is_m49_code",
17
18
  "iso_to_m49",
18
19
  "m49_to_iso",
19
- "region_and_country_names_to_m49",
20
+ "m49_to_openepd",
20
21
  "m49_to_region_and_country_names",
21
22
  "openepd_to_m49",
22
- "m49_to_openepd",
23
- "is_m49_code",
23
+ "region_and_country_names_to_m49",
24
24
  ]
25
- from typing import Collection
25
+ from collections.abc import Collection
26
26
 
27
27
  from openepd.m49.const import (
28
28
  COUNTRY_VERBOSE_NAME_TO_M49,
@@ -54,7 +54,8 @@ def iso_to_m49(regions: Collection[str]) -> set[str]:
54
54
  if m49_code:
55
55
  result.add(m49_code)
56
56
  else:
57
- raise ValueError(f"Country code '{code}' not found in M49 region codes.")
57
+ msg = f"Country code '{code}' not found in M49 region codes."
58
+ raise ValueError(msg)
58
59
 
59
60
  return result
60
61
 
@@ -77,7 +78,8 @@ def m49_to_iso(regions: Collection[str]) -> set[str]:
77
78
  if iso_code:
78
79
  result.add(iso_code)
79
80
  else:
80
- raise ValueError(f"Region code '{code}' not found in ISO3166.")
81
+ msg = f"Region code '{code}' not found in ISO3166."
82
+ raise ValueError(msg)
81
83
 
82
84
  return result
83
85
 
@@ -99,7 +101,8 @@ def region_and_country_names_to_m49(regions: Collection[str]) -> set[str]:
99
101
  for name in regions:
100
102
  m49_code = REGION_VERBOSE_NAME_TO_M49.get(name.title()) or COUNTRY_VERBOSE_NAME_TO_M49.get(name.title())
101
103
  if not m49_code:
102
- raise ValueError(f"Region or country name '{name}' not found in M49 region codes.")
104
+ msg = f"Region or country name '{name}' not found in M49 region codes."
105
+ raise ValueError(msg)
103
106
  result.add(m49_code)
104
107
 
105
108
  return result
@@ -120,7 +123,8 @@ def m49_to_region_and_country_names(regions: Collection[str]) -> set[str]:
120
123
  result = set()
121
124
  for code in regions:
122
125
  if code not in M49_TO_REGION_VERBOSE_NAME and code not in M49_TO_COUNTRY_VERBOSE_NAME:
123
- raise ValueError(f"Region code '{code}' not found in M49 region codes.")
126
+ msg = f"Region code '{code}' not found in M49 region codes."
127
+ raise ValueError(msg)
124
128
 
125
129
  name = M49_TO_REGION_VERBOSE_NAME.get(code) or M49_TO_COUNTRY_VERBOSE_NAME.get(code, code)
126
130
  result.add(name)
@@ -153,7 +157,8 @@ def openepd_to_m49(regions: Collection[str]) -> set[str]:
153
157
  elif is_m49_code(region):
154
158
  result.add(region)
155
159
  else:
156
- raise ValueError(f"Region '{region}' not found in ISO3166 or OpenEPD special regions.")
160
+ msg = f"Region '{region}' not found in ISO3166 or OpenEPD special regions."
161
+ raise ValueError(msg)
157
162
  return result
158
163
 
159
164
 
@@ -185,7 +190,8 @@ def m49_to_openepd(regions: list[str]) -> set[str]:
185
190
  if iso_code:
186
191
  result.add(iso_code)
187
192
  else:
188
- raise ValueError(f"Region code '{code}' not found in ISO3166 or OpenEPD special regions.")
193
+ msg = f"Region code '{code}' not found in ISO3166 or OpenEPD special regions."
194
+ raise ValueError(msg)
189
195
 
190
196
  return result
191
197
 
openepd/model/base.py CHANGED
@@ -14,9 +14,10 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  import abc
17
+ from collections.abc import Callable
17
18
  from enum import StrEnum
18
19
  import json
19
- from typing import Any, Callable, ClassVar, Generic, Optional, Type, TypeAlias, TypeVar
20
+ from typing import Any, ClassVar, Generic, Optional, TypeAlias, TypeVar
20
21
 
21
22
  from cqd import open_xpd_uuid # type:ignore[import-untyped,ignore-not-found]
22
23
 
@@ -106,7 +107,7 @@ class BaseOpenEpdSchema(pyd.BaseModel):
106
107
  return self.ext.get(key, default)
107
108
 
108
109
  def get_typed_ext_field(
109
- self, key: str, target_type: Type[TAnySerializable], default: Optional[TAnySerializable] = None
110
+ self, key: str, target_type: type[TAnySerializable], default: TAnySerializable | None = None
110
111
  ) -> TAnySerializable:
111
112
  """
112
113
  Get an extension field from the model and convert it to the target type.
@@ -120,13 +121,14 @@ class BaseOpenEpdSchema(pyd.BaseModel):
120
121
  return target_type.parse_obj(value) # type: ignore[return-value]
121
122
  elif isinstance(value, target_type):
122
123
  return value
123
- raise ValueError(f"Cannot convert {value} to {target_type}")
124
+ msg = f"Cannot convert {value} to {target_type}"
125
+ raise ValueError(msg)
124
126
 
125
- def get_ext(self, ext_type: Type["TOpenEpdExtension"]) -> Optional["TOpenEpdExtension"]:
127
+ def get_ext(self, ext_type: type["TOpenEpdExtension"]) -> Optional["TOpenEpdExtension"]:
126
128
  """Get an extension field from the model or None if it doesn't exist."""
127
129
  return self.get_typed_ext_field(ext_type.get_extension_name(), ext_type, None)
128
130
 
129
- def get_ext_or_empty(self, ext_type: Type["TOpenEpdExtension"]) -> "TOpenEpdExtension":
131
+ def get_ext_or_empty(self, ext_type: type["TOpenEpdExtension"]) -> "TOpenEpdExtension":
130
132
  """Get an extension field from the model or an empty instance if it doesn't exist."""
131
133
  return self.get_typed_ext_field(ext_type.get_extension_name(), ext_type, ext_type.construct(**{}))
132
134
 
@@ -174,7 +176,7 @@ class OpenEpdExtension(BaseOpenEpdSchema, metaclass=abc.ABCMeta):
174
176
 
175
177
  TOpenEpdExtension = TypeVar("TOpenEpdExtension", bound=OpenEpdExtension)
176
178
  TOpenEpdObject = TypeVar("TOpenEpdObject", bound=BaseOpenEpdSchema)
177
- TOpenEpdObjectClass = TypeVar("TOpenEpdObjectClass", bound=Type[BaseOpenEpdSchema])
179
+ TOpenEpdObjectClass = TypeVar("TOpenEpdObjectClass", bound=type[BaseOpenEpdSchema])
178
180
 
179
181
 
180
182
  class RootDocument(abc.ABC, BaseOpenEpdSchema):
@@ -218,23 +220,24 @@ class BaseDocumentFactory(Generic[TRootDocument]):
218
220
  """Create a document from a dictionary."""
219
221
  doctype: str | None = data.get("doctype")
220
222
  if doctype is None:
221
- raise ValueError("Doctype not found in the data.")
223
+ msg = "Doctype not found in the data."
224
+ raise ValueError(msg)
222
225
  if doctype.lower() != cls.DOCTYPE_CONSTRAINT.lower():
223
- raise ValueError(
224
- f"Document type {doctype} not supported. This factory supports {cls.DOCTYPE_CONSTRAINT} only."
225
- )
226
+ msg = f"Document type {doctype} not supported. This factory supports {cls.DOCTYPE_CONSTRAINT} only."
227
+ raise ValueError(msg)
226
228
  version = Version.parse_version(data.get(OPENEPD_VERSION_FIELD, ""))
227
229
  for x, doc_cls in cls.VERSION_MAP.items():
228
230
  if x.major == version.major:
229
231
  if version.minor <= x.minor:
230
232
  return doc_cls(**data)
231
233
  else:
232
- raise ValueError(
233
- f"Unsupported version: {version}. "
234
- f"The highest supported version from branch {x.major}.x is {x}"
234
+ msg = (
235
+ f"Unsupported version: {version}. The highest supported version from branch {x.major}.x is {x}"
235
236
  )
237
+ raise ValueError(msg)
236
238
  supported_versions = ", ".join(f"{v.major}.x" for v in cls.VERSION_MAP.keys())
237
- raise ValueError(f"Version {version} is not supported. Supported versions are: {supported_versions}")
239
+ msg = f"Version {version} is not supported. Supported versions are: {supported_versions}"
240
+ raise ValueError(msg)
238
241
 
239
242
 
240
243
  class OpenXpdUUID(str):
@@ -252,7 +255,8 @@ class OpenXpdUUID(str):
252
255
  open_xpd_uuid.validate(open_xpd_uuid.sanitize(str(v)))
253
256
  return v
254
257
  except open_xpd_uuid.GuidValidationError as e:
255
- raise ValueError("Invalid format") from e
258
+ msg = "Invalid format"
259
+ raise ValueError(msg) from e
256
260
 
257
261
  @classmethod
258
262
  def __get_validators__(cls):
openepd/model/common.py CHANGED
@@ -31,7 +31,8 @@ class Amount(BaseOpenEpdSchema):
31
31
  def check_qty_or_unit(cls, values: dict[str, Any]):
32
32
  """Ensure that qty or unit is provided."""
33
33
  if values.get("qty") is None and values.get("unit") is None:
34
- raise ValueError("Either qty or unit must be provided.")
34
+ msg = "Either qty or unit must be provided."
35
+ raise ValueError(msg)
35
36
  return values
36
37
 
37
38
  def to_quantity_str(self):
@@ -43,19 +44,19 @@ class Distribution(StrEnum):
43
44
  """
44
45
  Distribution of the measured value.
45
46
 
46
- * log-normal: Probability distribution of any random parameter whose natural log is normally distributed (the
47
- PDF is gaussian).
48
- * normal: Probability distribution of any random parameter whose value is normally distributed around the mean
49
- (the PDF is gaussian).
50
- * Continuous uniform probability distribution between minimum value and maximum value and "0" probability beyond
51
- these.
52
- * Probability distribution of any random parameter between minimum value and maximum value with the highest
53
- probability at the average value of minimum plus maximum value. Linear change of probability between minimum,
54
- maximum and average value.
55
- * Means Impact is not known, but with >95% certainty the true value is below the declared value.
56
- So [1e-6,"kgCFC11e",0,"max"] means the ODP was not exactly measured, but it is guaranteed to be below
57
- 1E-6 kg CO2e. It is acceptable to treat a 'max' distribution a normal or lognormal distribution with variation
58
- 0.1%. This is conservative, because the 'max' value is usually much greater than the true impact.
47
+ * log-normal: Probability distribution of any random parameter whose natural log is normally distributed (the
48
+ PDF is gaussian).
49
+ * normal: Probability distribution of any random parameter whose value is normally distributed around the mean
50
+ (the PDF is gaussian).
51
+ * Continuous uniform probability distribution between minimum value and maximum value and "0" probability beyond
52
+ these.
53
+ * Probability distribution of any random parameter between minimum value and maximum value with the highest
54
+ probability at the average value of minimum plus maximum value. Linear change of probability between minimum,
55
+ maximum and average value.
56
+ * Means Impact is not known, but with >95% certainty the true value is below the declared value.
57
+ So [1e-6,"kgCFC11e",0,"max"] means the ODP was not exactly measured, but it is guaranteed to be below
58
+ 1E-6 kg CO2e. It is acceptable to treat a 'max' distribution a normal or lognormal distribution with variation
59
+ 0.1%. This is conservative, because the 'max' value is usually much greater than the true impact.
59
60
  """
60
61
 
61
62
  LOG_NORMAL = "log-normal"
@@ -102,8 +103,7 @@ class Ingredient(BaseOpenEpdSchema):
102
103
  description="Number of declared units of this consumed. Negative values indicate an outflow."
103
104
  )
104
105
  link: pyd.AnyUrl | None = pyd.Field(
105
- description="Link to this object's OpenEPD declaration. "
106
- "An OpenIndustryEPD or OpenLCI link is also acceptable.",
106
+ description="Link to this object's OpenEPD declaration. An OpenIndustryEPD or OpenLCI link is also acceptable.",
107
107
  default=None,
108
108
  )
109
109
 
@@ -123,9 +123,11 @@ class Ingredient(BaseOpenEpdSchema):
123
123
  # for in the calculation of uncertainty
124
124
  if values.get("gwp_fraction"):
125
125
  if not values.get("evidence_type"):
126
- raise ValueError("evidence_type is required if gwp_fraction is provided")
126
+ msg = "evidence_type is required if gwp_fraction is provided"
127
+ raise ValueError(msg)
127
128
  if not (values.get("citation") or values.get("link")):
128
- raise ValueError("link or citation is required if gwp_fraction is provided")
129
+ msg = "link or citation is required if gwp_fraction is provided"
130
+ raise ValueError(msg)
129
131
 
130
132
  return values
131
133
 
@@ -267,7 +269,8 @@ class RangeBase(BaseOpenEpdSchema):
267
269
  min_boundary = values.get("min")
268
270
  max_boundary = values.get("max")
269
271
  if min_boundary is not None and max_boundary is not None and min_boundary > max_boundary:
270
- raise ValueError("Max should be greater than min")
272
+ msg = "Max should be greater than min"
273
+ raise ValueError(msg)
271
274
  return values
272
275
 
273
276
 
@@ -43,11 +43,11 @@ class BaseDeclaration(RootDocument, abc.ABC):
43
43
  default=None,
44
44
  )
45
45
  date_of_issue: datetime.datetime | None = pyd.Field(
46
- example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
46
+ example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.UTC),
47
47
  description="Date the document was issued. This should be the first day on which the document is valid.",
48
48
  )
49
49
  valid_until: datetime.datetime | None = pyd.Field(
50
- example=datetime.datetime(day=11, month=9, year=2028, tzinfo=datetime.timezone.utc),
50
+ example=datetime.datetime(day=11, month=9, year=2028, tzinfo=datetime.UTC),
51
51
  description="Last date the document is valid on, including any extensions.",
52
52
  )
53
53
 
openepd/model/epd.py CHANGED
@@ -231,7 +231,8 @@ class EpdPreviewV0(
231
231
  """
232
232
  if not v or v.lower() == "openepd":
233
233
  return "openEPD"
234
- raise ValueError("Invalid doctype")
234
+ msg = "Invalid doctype"
235
+ raise ValueError(msg)
235
236
 
236
237
 
237
238
  EpdPreview = EpdPreviewV0
openepd/model/factory.py CHANGED
@@ -38,7 +38,8 @@ class DocumentFactory:
38
38
  :raise ValueError if doctype not supported or not found.
39
39
  """
40
40
  if doctype is None:
41
- raise ValueError("The document type is not specified.")
41
+ msg = "The document type is not specified."
42
+ raise ValueError(msg)
42
43
  factory = cls.DOCTYPE_TO_FACTORY.get(doctype)
43
44
  if factory is None:
44
45
  raise ValueError(
@@ -56,11 +57,12 @@ class DocumentFactory:
56
57
  :raise ValueError: if the document type is not specified or not supported.
57
58
  """
58
59
  doctype = data.get("doctype")
59
- if doctype is None or not isinstance(doctype, (str, OpenEpdDoctypes)):
60
- raise ValueError(
60
+ if doctype is None or not isinstance(doctype, str | OpenEpdDoctypes):
61
+ msg = (
61
62
  f"The document type is not specified or not supported. "
62
63
  f"Please specify it in `doctype` field. Supported are: {','.join(cls.DOCTYPE_TO_FACTORY)}"
63
64
  )
65
+ raise ValueError(msg)
64
66
 
65
67
  factory = cls.get_factory(OpenEpdDoctypes(doctype))
66
68
  return factory.from_dict(data)