openepd 6.19.0__py3-none-any.whl → 6.21.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/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 +42 -2
- openepd/bundle/model.py +1 -0
- openepd/bundle/reader.py +22 -3
- openepd/bundle/writer.py +13 -6
- {openepd-6.19.0.dist-info → openepd-6.21.0.dist-info}/METADATA +21 -10
- {openepd-6.19.0.dist-info → openepd-6.21.0.dist-info}/RECORD +17 -11
- {openepd-6.19.0.dist-info → openepd-6.21.0.dist-info}/LICENSE +0 -0
- {openepd-6.19.0.dist-info → openepd-6.21.0.dist-info}/WHEEL +0 -0
openepd/__version__.py
CHANGED
@@ -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,79 @@
|
|
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
|
+
from typing import Literal, overload
|
17
|
+
|
18
|
+
from requests import Response
|
19
|
+
|
20
|
+
from openepd.api.base_sync_client import BaseApiMethodGroup
|
21
|
+
from openepd.api.utils import encode_path_param
|
22
|
+
from openepd.model.org import Org, OrgRef
|
23
|
+
|
24
|
+
|
25
|
+
class OrgApi(BaseApiMethodGroup):
|
26
|
+
"""API methods for Orgs."""
|
27
|
+
|
28
|
+
@overload
|
29
|
+
def create(self, to_create: Org, with_response: Literal[True]) -> tuple[OrgRef, Response]: ...
|
30
|
+
|
31
|
+
@overload
|
32
|
+
def create(self, to_create: Org, with_response: Literal[False] = False) -> OrgRef: ...
|
33
|
+
|
34
|
+
def create(self, to_create: Org, with_response: bool = False) -> OrgRef | tuple[OrgRef, Response]:
|
35
|
+
"""
|
36
|
+
Create a new organization.
|
37
|
+
|
38
|
+
:param to_create: Organization to create
|
39
|
+
:param with_response: if True, return a tuple of (OrgRef, Response), otherwise return only OrgRef
|
40
|
+
:return: Organization reference or Organization reference with HTTP Response object depending on parameter
|
41
|
+
:raise ValidationError: if given object Org is invalid
|
42
|
+
"""
|
43
|
+
response = self._client.do_request("post", "/orgs", json=to_create.to_serializable())
|
44
|
+
content = response.json()
|
45
|
+
ref = OrgRef.parse_obj(content)
|
46
|
+
if with_response:
|
47
|
+
return ref, response
|
48
|
+
return ref
|
49
|
+
|
50
|
+
@overload
|
51
|
+
def edit(self, to_edit: Org, with_response: Literal[True]) -> tuple[OrgRef, Response]: ...
|
52
|
+
|
53
|
+
@overload
|
54
|
+
def edit(self, to_edit: Org, with_response: Literal[False] = False) -> OrgRef: ...
|
55
|
+
|
56
|
+
def edit(self, to_edit: Org, with_response: bool = False) -> OrgRef | tuple[OrgRef, Response]:
|
57
|
+
"""
|
58
|
+
Edit an organization.
|
59
|
+
|
60
|
+
:param to_edit: Organization to edit
|
61
|
+
:param with_response: if True, return a tuple of (OrgRef, Response), otherwise return only Org
|
62
|
+
:return: Organization reference or Organization reference with HTTP Response object depending on parameter
|
63
|
+
:raise ValueError: if the organization web_domain is not set
|
64
|
+
"""
|
65
|
+
entity_id = to_edit.web_domain
|
66
|
+
if not entity_id:
|
67
|
+
msg = "The organization web_domain must be set to edit an organization."
|
68
|
+
raise ValueError(msg)
|
69
|
+
response = self._client.do_request(
|
70
|
+
"put",
|
71
|
+
f"/orgs/{encode_path_param(entity_id)}",
|
72
|
+
json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
|
73
|
+
)
|
74
|
+
response.raise_for_status()
|
75
|
+
content = response.json()
|
76
|
+
ref = OrgRef.parse_obj(content)
|
77
|
+
if with_response:
|
78
|
+
return ref, response
|
79
|
+
return ref
|
openepd/api/pcr/sync_api.py
CHANGED
@@ -13,7 +13,12 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
+
from typing import Literal, overload
|
17
|
+
|
18
|
+
from requests import Response
|
19
|
+
|
16
20
|
from openepd.api.base_sync_client import BaseApiMethodGroup
|
21
|
+
from openepd.api.utils import encode_path_param
|
17
22
|
from openepd.model.pcr import Pcr, PcrRef
|
18
23
|
|
19
24
|
|
@@ -42,3 +47,33 @@ class PcrApi(BaseApiMethodGroup):
|
|
42
47
|
"""
|
43
48
|
pcr_ref_obj = self._client.do_request("post", "/pcrs", json=pcr.to_serializable()).json()
|
44
49
|
return PcrRef.parse_obj(pcr_ref_obj)
|
50
|
+
|
51
|
+
@overload
|
52
|
+
def edit(self, to_edit: Pcr, with_response: Literal[True]) -> tuple[PcrRef, Response]: ...
|
53
|
+
|
54
|
+
@overload
|
55
|
+
def edit(self, to_edit: Pcr, with_response: Literal[False] = False) -> PcrRef: ...
|
56
|
+
|
57
|
+
def edit(self, to_edit: Pcr, with_response: bool = False) -> PcrRef | tuple[PcrRef, Response]:
|
58
|
+
"""
|
59
|
+
Edit a pcr.
|
60
|
+
|
61
|
+
:param to_edit: Pcr to edit
|
62
|
+
:param with_response: if True, return a tuple of (PcrRef, Response), otherwise return only PcrRef
|
63
|
+
:return: Pcr reference or Pcr reference with HTTP Response object depending on parameter
|
64
|
+
:raise ValueError: if the pcr ID is not set
|
65
|
+
"""
|
66
|
+
entity_id = to_edit.id
|
67
|
+
if not entity_id:
|
68
|
+
msg = "The pcr ID must be set to edit a pcr."
|
69
|
+
raise ValueError(msg)
|
70
|
+
response = self._client.do_request(
|
71
|
+
"put",
|
72
|
+
f"/pcrs/{encode_path_param(entity_id)}",
|
73
|
+
json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
|
74
|
+
)
|
75
|
+
content = response.json()
|
76
|
+
ref = PcrRef.parse_obj(content)
|
77
|
+
if with_response:
|
78
|
+
return ref, response
|
79
|
+
return ref
|
@@ -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,79 @@
|
|
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
|
+
from typing import Literal, overload
|
17
|
+
|
18
|
+
from requests import Response
|
19
|
+
|
20
|
+
from openepd.api.base_sync_client import BaseApiMethodGroup
|
21
|
+
from openepd.api.utils import encode_path_param
|
22
|
+
from openepd.model.org import Plant, PlantRef
|
23
|
+
|
24
|
+
|
25
|
+
class PlantApi(BaseApiMethodGroup):
|
26
|
+
"""API methods for Plants."""
|
27
|
+
|
28
|
+
@overload
|
29
|
+
def create(self, to_create: Plant, with_response: Literal[True]) -> tuple[PlantRef, Response]: ...
|
30
|
+
|
31
|
+
@overload
|
32
|
+
def create(self, to_create: Plant, with_response: Literal[False] = False) -> PlantRef: ...
|
33
|
+
|
34
|
+
def create(self, to_create: Plant, with_response: bool = False) -> PlantRef | tuple[PlantRef, Response]:
|
35
|
+
"""
|
36
|
+
Create a new plant.
|
37
|
+
|
38
|
+
:param to_create: Plant to create
|
39
|
+
:param with_response: if True, return a tuple of (PlantRef, Response), otherwise return only PlantRef
|
40
|
+
:return: Plant reference or Plant reference with HTTP Response object depending on parameter
|
41
|
+
:raise ValidationError: if given object Plant is invalid
|
42
|
+
"""
|
43
|
+
response = self._client.do_request("post", "/plants", json=to_create.to_serializable())
|
44
|
+
content = response.json()
|
45
|
+
ref = PlantRef.parse_obj(content)
|
46
|
+
if with_response:
|
47
|
+
return ref, response
|
48
|
+
return ref
|
49
|
+
|
50
|
+
@overload
|
51
|
+
def edit(self, to_edit: Plant, with_response: Literal[True]) -> tuple[PlantRef, Response]: ...
|
52
|
+
|
53
|
+
@overload
|
54
|
+
def edit(self, to_edit: Plant, with_response: Literal[False] = False) -> PlantRef: ...
|
55
|
+
|
56
|
+
def edit(self, to_edit: Plant, with_response: bool = False) -> PlantRef | tuple[PlantRef, Response]:
|
57
|
+
"""
|
58
|
+
Edit a plant.
|
59
|
+
|
60
|
+
:param to_edit: Plant to edit
|
61
|
+
:param with_response: if True, return a tuple of (PlantRef, Response), otherwise return only PlantRef
|
62
|
+
:return: Plant reference or Plant reference with HTTP Response object depending on parameter
|
63
|
+
:raise ValueError: if the plant ID is not set
|
64
|
+
"""
|
65
|
+
entity_id = to_edit.id
|
66
|
+
if not entity_id:
|
67
|
+
msg = "The plant ID must be set to edit a plant."
|
68
|
+
raise ValueError(msg)
|
69
|
+
response = self._client.do_request(
|
70
|
+
"put",
|
71
|
+
f"/plants/{encode_path_param(entity_id)}",
|
72
|
+
json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
|
73
|
+
)
|
74
|
+
response.raise_for_status()
|
75
|
+
content = response.json()
|
76
|
+
ref = PlantRef.parse_obj(content)
|
77
|
+
if with_response:
|
78
|
+
return ref, response
|
79
|
+
return ref
|
@@ -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,79 @@
|
|
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
|
+
from typing import Literal, overload
|
17
|
+
|
18
|
+
from requests import Response
|
19
|
+
|
20
|
+
from openepd.api.base_sync_client import BaseApiMethodGroup
|
21
|
+
from openepd.api.utils import encode_path_param
|
22
|
+
from openepd.model.standard import Standard, StandardRef
|
23
|
+
|
24
|
+
|
25
|
+
class StandardApi(BaseApiMethodGroup):
|
26
|
+
"""API methods for Standards."""
|
27
|
+
|
28
|
+
@overload
|
29
|
+
def create(self, to_create: Standard, with_response: Literal[True]) -> tuple[StandardRef, Response]: ...
|
30
|
+
|
31
|
+
@overload
|
32
|
+
def create(self, to_create: Standard, with_response: Literal[False] = False) -> StandardRef: ...
|
33
|
+
|
34
|
+
def create(self, to_create: Standard, with_response: bool = False) -> StandardRef | tuple[StandardRef, Response]:
|
35
|
+
"""
|
36
|
+
Create a new standard.
|
37
|
+
|
38
|
+
:param to_create: Standard to create
|
39
|
+
:param with_response: if True, return a tuple of (StandardRef, Response), otherwise return only StandardRef
|
40
|
+
:return: Standard reference or Standard reference with HTTP Response object depending on parameter
|
41
|
+
:raise ValidationError: if given object Standard is invalid
|
42
|
+
"""
|
43
|
+
response = self._client.do_request("post", "/standards", json=to_create.to_serializable())
|
44
|
+
content = response.json()
|
45
|
+
ref = StandardRef.parse_obj(content)
|
46
|
+
if with_response:
|
47
|
+
return ref, response
|
48
|
+
return ref
|
49
|
+
|
50
|
+
@overload
|
51
|
+
def edit(self, to_edit: Standard, with_response: Literal[True]) -> tuple[StandardRef, Response]: ...
|
52
|
+
|
53
|
+
@overload
|
54
|
+
def edit(self, to_edit: Standard, with_response: Literal[False] = False) -> StandardRef: ...
|
55
|
+
|
56
|
+
def edit(self, to_edit: Standard, with_response: bool = False) -> StandardRef | tuple[StandardRef, Response]:
|
57
|
+
"""
|
58
|
+
Edit a standard.
|
59
|
+
|
60
|
+
:param to_edit: Standard to edit
|
61
|
+
:param with_response: if True, return a tuple of (StandardRef, Response), otherwise return only StandardRef
|
62
|
+
:return: Standard reference or Standard reference with HTTP Response object depending on parameter
|
63
|
+
:raise ValueError: if the standard short_name is not set
|
64
|
+
"""
|
65
|
+
entity_id = to_edit.short_name
|
66
|
+
if not entity_id:
|
67
|
+
msg = "The standard short_name must be set to edit a standard."
|
68
|
+
raise ValueError(msg)
|
69
|
+
response = self._client.do_request(
|
70
|
+
"put",
|
71
|
+
f"/standards/{encode_path_param(entity_id)}",
|
72
|
+
json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
|
73
|
+
)
|
74
|
+
response.raise_for_status()
|
75
|
+
content = response.json()
|
76
|
+
ref = StandardRef.parse_obj(content)
|
77
|
+
if with_response:
|
78
|
+
return ref, response
|
79
|
+
return ref
|
openepd/api/sync_client.py
CHANGED
@@ -22,7 +22,10 @@ from openepd.api.average_dataset.industry_epd_sync_api import IndustryEpdApi
|
|
22
22
|
from openepd.api.base_sync_client import SyncHttpClient, TokenAuth
|
23
23
|
from openepd.api.category.sync_api import CategoryApi
|
24
24
|
from openepd.api.epd.sync_api import EpdApi
|
25
|
+
from openepd.api.org.sync_api import OrgApi
|
25
26
|
from openepd.api.pcr.sync_api import PcrApi
|
27
|
+
from openepd.api.plant.sync_api import PlantApi
|
28
|
+
from openepd.api.standard.sync_api import StandardApi
|
26
29
|
|
27
30
|
|
28
31
|
class OpenEpdApiClientSync:
|
@@ -41,6 +44,9 @@ class OpenEpdApiClientSync:
|
|
41
44
|
self._http_client = SyncHttpClient(base_url, auth=auth, **kwargs)
|
42
45
|
self.__epd_api: EpdApi | None = None
|
43
46
|
self.__pcr_api: PcrApi | None = None
|
47
|
+
self.__org_api: OrgApi | None = None
|
48
|
+
self.__plant_api: PlantApi | None = None
|
49
|
+
self.__standard_api: StandardApi | None = None
|
44
50
|
self.__category_api: CategoryApi | None = None
|
45
51
|
self.__generic_estimate_api: GenericEstimateApi | None = None
|
46
52
|
self.__industry_epd_api: IndustryEpdApi | None = None
|
@@ -59,6 +65,27 @@ class OpenEpdApiClientSync:
|
|
59
65
|
self.__pcr_api = PcrApi(self._http_client)
|
60
66
|
return self.__pcr_api
|
61
67
|
|
68
|
+
@property
|
69
|
+
def orgs(self) -> OrgApi:
|
70
|
+
"""Get the Org API."""
|
71
|
+
if self.__org_api is None:
|
72
|
+
self.__org_api = OrgApi(self._http_client)
|
73
|
+
return self.__org_api
|
74
|
+
|
75
|
+
@property
|
76
|
+
def plants(self) -> PlantApi:
|
77
|
+
"""Get the Plant API."""
|
78
|
+
if self.__plant_api is None:
|
79
|
+
self.__plant_api = PlantApi(self._http_client)
|
80
|
+
return self.__plant_api
|
81
|
+
|
82
|
+
@property
|
83
|
+
def standards(self) -> StandardApi:
|
84
|
+
"""Get the Standard API."""
|
85
|
+
if self.__standard_api is None:
|
86
|
+
self.__standard_api = StandardApi(self._http_client)
|
87
|
+
return self.__standard_api
|
88
|
+
|
62
89
|
@property
|
63
90
|
def categories(self) -> CategoryApi:
|
64
91
|
"""Get the Category API."""
|
openepd/bundle/base.py
CHANGED
@@ -64,6 +64,46 @@ class BundleMixin:
|
|
64
64
|
else:
|
65
65
|
return asset_ref
|
66
66
|
|
67
|
+
@classmethod
|
68
|
+
def _asset_refs_to_str(cls, asset_refs: AssetRef | list[AssetRef] | None) -> list[str] | str | None:
|
69
|
+
"""Convert single or multiple asset references to strings."""
|
70
|
+
if asset_refs is None:
|
71
|
+
return None
|
72
|
+
if isinstance(asset_refs, list):
|
73
|
+
return [cls._asset_ref_to_str(asset_ref) for asset_ref in asset_refs]
|
74
|
+
else:
|
75
|
+
return cls._asset_ref_to_str(asset_refs)
|
76
|
+
|
77
|
+
@classmethod
|
78
|
+
def _serialize_rel_asset_for_csv(cls, rel_asset: list[str] | str | None) -> str | None:
|
79
|
+
"""
|
80
|
+
Serialize rel_asset for CSV storage.
|
81
|
+
|
82
|
+
Multiple assets are joined with semicolons.
|
83
|
+
"""
|
84
|
+
if rel_asset is None:
|
85
|
+
return None
|
86
|
+
if isinstance(rel_asset, list):
|
87
|
+
# For empty list, return None. For non-empty list, join with semicolons
|
88
|
+
return ";".join(rel_asset) if rel_asset else None
|
89
|
+
else:
|
90
|
+
# Single asset - return as string
|
91
|
+
return str(rel_asset)
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def _deserialize_rel_asset_from_csv(cls, rel_asset_str: str | None) -> list[str] | str | None:
|
95
|
+
"""
|
96
|
+
Deserialize rel_asset from CSV storage.
|
97
|
+
|
98
|
+
Semicolon-separated values become lists.
|
99
|
+
"""
|
100
|
+
if rel_asset_str is None or rel_asset_str == "":
|
101
|
+
return None
|
102
|
+
if ";" in rel_asset_str:
|
103
|
+
return rel_asset_str.split(";")
|
104
|
+
else:
|
105
|
+
return rel_asset_str
|
106
|
+
|
67
107
|
|
68
108
|
class BaseBundleReader(BundleMixin, metaclass=abc.ABCMeta):
|
69
109
|
"""Base class for bundle readers."""
|
@@ -180,7 +220,7 @@ class BaseBundleWriter(BundleMixin, metaclass=abc.ABCMeta):
|
|
180
220
|
self,
|
181
221
|
data: IO[bytes],
|
182
222
|
content_type: str | None,
|
183
|
-
rel_asset: AssetRef | None = None,
|
223
|
+
rel_asset: AssetRef | list[AssetRef] | None = None,
|
184
224
|
rel_type: str | None = None,
|
185
225
|
file_name: str | None = None,
|
186
226
|
name: str | None = None,
|
@@ -196,7 +236,7 @@ class BaseBundleWriter(BundleMixin, metaclass=abc.ABCMeta):
|
|
196
236
|
def write_object_asset(
|
197
237
|
self,
|
198
238
|
obj: TOpenEpdObject,
|
199
|
-
rel_asset: AssetRef | None = None,
|
239
|
+
rel_asset: AssetRef | list[AssetRef] | None = None,
|
200
240
|
rel_type: str | None = None,
|
201
241
|
file_name: str | None = None,
|
202
242
|
name: str | None = None,
|
openepd/bundle/model.py
CHANGED
@@ -79,6 +79,7 @@ class AssetInfo(BaseOpenEpdSchema):
|
|
79
79
|
"""The language of the asset."""
|
80
80
|
rel_type: str | None
|
81
81
|
rel_asset: str | None
|
82
|
+
"""The related asset reference (serialized as semicolon-separated for multiple assets)."""
|
82
83
|
comment: str | None = pyd.Field(default=None)
|
83
84
|
content_type: str | None = pyd.Field(default=None)
|
84
85
|
size: int | None = pyd.Field(default=None)
|
openepd/bundle/reader.py
CHANGED
@@ -61,8 +61,12 @@ class DefaultBundleReader(BaseBundleReader):
|
|
61
61
|
return False
|
62
62
|
if name is not None and a.name != name:
|
63
63
|
return False
|
64
|
-
if parent_ref is not None
|
65
|
-
|
64
|
+
if parent_ref is not None:
|
65
|
+
parent_ref_str = self._asset_ref_to_str(parent_ref)
|
66
|
+
# Get the actual list of related assets
|
67
|
+
rel_asset_list = self._get_rel_asset_list(a)
|
68
|
+
if parent_ref_str not in rel_asset_list:
|
69
|
+
return False
|
66
70
|
if ref_type is not None and a.rel_type != ref_type:
|
67
71
|
return False
|
68
72
|
if is_translated is not None and a.lang is not None and "translated" in a.lang:
|
@@ -78,6 +82,20 @@ class DefaultBundleReader(BaseBundleReader):
|
|
78
82
|
input_dict[x] = None
|
79
83
|
return input_dict
|
80
84
|
|
85
|
+
def _get_rel_asset_list(self, asset_info: AssetInfo) -> list[str]:
|
86
|
+
"""Get the list of related asset references from an AssetInfo object."""
|
87
|
+
if asset_info.rel_asset is None:
|
88
|
+
return []
|
89
|
+
|
90
|
+
# Deserialize from CSV format (semicolon-separated values)
|
91
|
+
deserialized = self._deserialize_rel_asset_from_csv(asset_info.rel_asset)
|
92
|
+
if isinstance(deserialized, list):
|
93
|
+
return deserialized
|
94
|
+
elif isinstance(deserialized, str):
|
95
|
+
return [deserialized]
|
96
|
+
else:
|
97
|
+
return []
|
98
|
+
|
81
99
|
def assets_iter(self) -> Iterator[AssetInfo]:
|
82
100
|
"""Iterate over all assets in the bundle."""
|
83
101
|
with self._bundle_archive.open("toc", "r") as toc_stream:
|
@@ -125,7 +143,8 @@ class DefaultBundleReader(BaseBundleReader):
|
|
125
143
|
rel_type = [rel_type]
|
126
144
|
asset_ref = self._asset_ref_to_str(asset)
|
127
145
|
for x in self.assets_iter():
|
128
|
-
|
146
|
+
rel_asset_list = self._get_rel_asset_list(x)
|
147
|
+
if asset_ref in rel_asset_list:
|
129
148
|
if rel_type is None or x.rel_type in rel_type:
|
130
149
|
yield x
|
131
150
|
|
openepd/bundle/writer.py
CHANGED
@@ -50,7 +50,7 @@ class DefaultBundleWriter(BaseBundleWriter):
|
|
50
50
|
self,
|
51
51
|
data: IO[bytes],
|
52
52
|
content_type: str | None = None,
|
53
|
-
rel_asset: AssetRef | None = None,
|
53
|
+
rel_asset: AssetRef | list[AssetRef] | None = None,
|
54
54
|
rel_type: str | None = None,
|
55
55
|
file_name: str | None = None,
|
56
56
|
name: str | None = None,
|
@@ -60,7 +60,10 @@ class DefaultBundleWriter(BaseBundleWriter):
|
|
60
60
|
custom_data: str | None = None,
|
61
61
|
) -> AssetInfo:
|
62
62
|
"""Write a blob asset to the bundle."""
|
63
|
-
|
63
|
+
# Convert multiple rel_asset to proper format and serialize for storage
|
64
|
+
rel_ref_converted = self._asset_refs_to_str(rel_asset)
|
65
|
+
rel_ref_serialized = self._serialize_rel_asset_for_csv(rel_ref_converted)
|
66
|
+
|
64
67
|
ref_str = self.__generate_entry_name(
|
65
68
|
AssetType.Blob, self.__get_ext_for_content_type(content_type, "bin"), file_name
|
66
69
|
)
|
@@ -70,7 +73,7 @@ class DefaultBundleWriter(BaseBundleWriter):
|
|
70
73
|
type=AssetType.Blob,
|
71
74
|
lang=lang,
|
72
75
|
rel_type=rel_type,
|
73
|
-
rel_asset=
|
76
|
+
rel_asset=rel_ref_serialized,
|
74
77
|
content_type=content_type,
|
75
78
|
comment=comment,
|
76
79
|
custom_type=custom_type,
|
@@ -83,7 +86,7 @@ class DefaultBundleWriter(BaseBundleWriter):
|
|
83
86
|
def write_object_asset(
|
84
87
|
self,
|
85
88
|
obj: TOpenEpdObject,
|
86
|
-
rel_asset: AssetRef | None = None,
|
89
|
+
rel_asset: list[AssetRef] | AssetRef | None = None,
|
87
90
|
rel_type: str | None = None,
|
88
91
|
file_name: str | None = None,
|
89
92
|
name: str | None = None,
|
@@ -98,7 +101,11 @@ class DefaultBundleWriter(BaseBundleWriter):
|
|
98
101
|
msg = f"Object {obj} does not have a valid asset type and can't be written to a bundle."
|
99
102
|
raise ValueError(msg)
|
100
103
|
asset_type = AssetType(asset_type_str)
|
101
|
-
|
104
|
+
|
105
|
+
# Convert multiple rel_asset to proper format and serialize for storage
|
106
|
+
rel_ref_converted = self._asset_refs_to_str(rel_asset)
|
107
|
+
rel_ref_serialized = self._serialize_rel_asset_for_csv(rel_ref_converted)
|
108
|
+
|
102
109
|
ref_str = self.__generate_entry_name(
|
103
110
|
asset_type, self.__get_ext_for_content_type("application/json", "json"), file_name
|
104
111
|
)
|
@@ -107,7 +114,7 @@ class DefaultBundleWriter(BaseBundleWriter):
|
|
107
114
|
name=name,
|
108
115
|
type=asset_type,
|
109
116
|
lang=lang,
|
110
|
-
rel_asset=
|
117
|
+
rel_asset=rel_ref_serialized,
|
111
118
|
rel_type=rel_type,
|
112
119
|
content_type="application/json",
|
113
120
|
comment=comment,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: openepd
|
3
|
-
Version: 6.
|
3
|
+
Version: 6.21.0
|
4
4
|
Summary: Python library to work with OpenEPD format
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: C-Change Labs
|
@@ -8,7 +8,7 @@ Author-email: support@c-change-labs.com
|
|
8
8
|
Maintainer: C-Change Labs
|
9
9
|
Maintainer-email: open-source@c-change-labs.com
|
10
10
|
Requires-Python: >=3.11,<4.0
|
11
|
-
Classifier: Development Status ::
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
12
12
|
Classifier: Intended Audience :: Developers
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
14
14
|
Classifier: Operating System :: OS Independent
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
20
|
Provides-Extra: api-client
|
21
21
|
Requires-Dist: email-validator (>=1.3.1)
|
22
22
|
Requires-Dist: idna (>=3.7)
|
23
|
-
Requires-Dist: open-xpd-uuid (>=0.2.1,<
|
23
|
+
Requires-Dist: open-xpd-uuid (>=0.2.1,<2)
|
24
24
|
Requires-Dist: openlocationcode (>=1.0.1)
|
25
25
|
Requires-Dist: pydantic (>=1.10,<3.0)
|
26
26
|
Requires-Dist: requests (>=2.0) ; extra == "api-client"
|
@@ -44,6 +44,24 @@ Description-Content-Type: text/markdown
|
|
44
44
|
|
45
45
|
This library is a Python library to work with OpenEPD format.
|
46
46
|
|
47
|
+
> ⚠️ **Version Warning**
|
48
|
+
>
|
49
|
+
> This application is currently developed in **two major versions** in parallel:
|
50
|
+
>
|
51
|
+
> - **v6.x (>=6.0.0)** — Stable and production-ready. Maintains support for Pydantic v1 and v2 through a compatibility layer.
|
52
|
+
> - **v7.x (>=7.0.0)** — Public beta. Fully functional, with native support for Pydantic v2. Still experimental and may introduce breaking changes in **internal and integration interfaces**.
|
53
|
+
>
|
54
|
+
> ⚠️ No breaking changes are expected in the **public standard or data model**, only in internal APIs and integration points.
|
55
|
+
>
|
56
|
+
> Both versions currently offer the same set of features.
|
57
|
+
> We recommend using **v6** for most production use cases as the more mature and stable option.
|
58
|
+
> **v7** is suitable for production environments that can tolerate some level of interface instability and want to adopt the latest internals.
|
59
|
+
>
|
60
|
+
> 💡 Only the **latest version of v7** is guaranteed to contain all the features and updates from v6. Earlier v7 releases may lack some recent improvements.
|
61
|
+
>
|
62
|
+
> Once **v7 is promoted to stable**, all earlier **pre-stable (beta) v7 releases** will be **marked as yanked** to prevent accidental usage in production.
|
63
|
+
>
|
64
|
+
|
47
65
|
## About OpenEPD
|
48
66
|
|
49
67
|
[openEPD](https://www.buildingtransparency.org/programs/openepd/) is an open data format for passing digital
|
@@ -63,13 +81,6 @@ documenting supply-chain specific data.
|
|
63
81
|
|
64
82
|
## Usage
|
65
83
|
|
66
|
-
**❗ ATTENTION**: Pick the right version. The cornerstone of this library models package representing openEPD models.
|
67
|
-
Models are defined with Pydantic library which is a dependency for openepd package. If you use Pydantic in your project
|
68
|
-
carefully pick the version:
|
69
|
-
|
70
|
-
* Use version **below** `2.0.0` if your project uses Pydantic version below `2.0.0`
|
71
|
-
* Use version `2.x.x` or higher if your project uses Pydantic version `2.0.0` or above
|
72
|
-
|
73
84
|
### Models
|
74
85
|
|
75
86
|
The library provides the Pydantic models for all the OpenEPD entities. The models are available in the `openepd.models`
|
@@ -1,5 +1,5 @@
|
|
1
1
|
openepd/__init__.py,sha256=fhxfEyEurLvSfvQci-vb3njzl_lvhcLXiZrecCOaMU8,794
|
2
|
-
openepd/__version__.py,sha256=
|
2
|
+
openepd/__version__.py,sha256=k7n_fLDUizaOEiiaI-LYyq8UKQvC3djSX4Z3eV0EjcI,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
|
@@ -19,16 +19,22 @@ openepd/api/epd/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,6
|
|
19
19
|
openepd/api/epd/dto.py,sha256=MqhHjaNdtOc-KT2zNI88EB9-1d2a6CS2zzSus8HefBo,4874
|
20
20
|
openepd/api/epd/sync_api.py,sha256=kBsx43q0cBm51hl3HVvzMIDrMMRi8NMyudPmHYd0qqU,7342
|
21
21
|
openepd/api/errors.py,sha256=BgZeNfMNAKVPfhpuiVapCXNBSsXygAOWql-gy7m9j7E,2868
|
22
|
+
openepd/api/org/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
23
|
+
openepd/api/org/sync_api.py,sha256=VzOrd3eB1xPVLyrKlZl3OwXIQ5nT3808sA6N7MNBu7w,3167
|
22
24
|
openepd/api/pcr/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
23
|
-
openepd/api/pcr/sync_api.py,sha256=
|
24
|
-
openepd/api/
|
25
|
+
openepd/api/pcr/sync_api.py,sha256=JWiegxoSnD2JElYORp6QdkbO3jDNhrNKxJR6orsD1TI,2849
|
26
|
+
openepd/api/plant/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
27
|
+
openepd/api/plant/sync_api.py,sha256=cryGfKojyXV78RxIPRTGscWuLnkdgTNJAw9RkxrbZWI,3121
|
28
|
+
openepd/api/standard/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
29
|
+
openepd/api/standard/sync_api.py,sha256=Oj_Os3yBPk7y7hXDvQbzr-AyX-z2b82jzbN7AuiK3Go,3264
|
30
|
+
openepd/api/sync_client.py,sha256=DiDSQU0kBd9gU17KrPUvo07pyLv15rGozuWXbkM1JzA,4037
|
25
31
|
openepd/api/test/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
26
32
|
openepd/api/utils.py,sha256=xOU8ihC0eghsoaCFhC85PU4WYRwNxVEpfK3gzq4e9ik,2092
|
27
33
|
openepd/bundle/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
28
|
-
openepd/bundle/base.py,sha256=
|
29
|
-
openepd/bundle/model.py,sha256=
|
30
|
-
openepd/bundle/reader.py,sha256=
|
31
|
-
openepd/bundle/writer.py,sha256=
|
34
|
+
openepd/bundle/base.py,sha256=2tv7KbxG9zC_nuMOGudorxZcTl5rU6ABJmEbO2WTPas,8400
|
35
|
+
openepd/bundle/model.py,sha256=uQto8zfI4wdCOpNAhhMPrsGIeyoKNMMJSmXJk6LlwC8,2700
|
36
|
+
openepd/bundle/reader.py,sha256=Qu6KM68tbaoSPRYSqkEKLgWjdl_c7pVAWIDHJK32aaQ,7653
|
37
|
+
openepd/bundle/writer.py,sha256=kX1vJ3z96W1TxYwy152hHbmHul_7jcxUXV8psOhtr18,8564
|
32
38
|
openepd/compat/__init__.py,sha256=9THJcV3LT7JDBOMz1px-QFf_sdJ0LOqJ5dmA9Dvvtd4,620
|
33
39
|
openepd/compat/compat_functional_validators.py,sha256=aWg3a80fqT8zjN0S260N-Ad2WFKAaB8ByN7ArBW3NMA,834
|
34
40
|
openepd/compat/pydantic.py,sha256=HZJmAiYO7s-LLcFHuj3iXS4MGOITExZYn2rPmteoqNI,1146
|
@@ -144,7 +150,7 @@ openepd/model/validation/quantity.py,sha256=mP4gIkeOGZuHRhprsf_BX11Cic75NssYxOTk
|
|
144
150
|
openepd/model/versioning.py,sha256=wBZdOVL3ND9FMIRU9PS3vx9M_7MBiO70xYPQvPez6po,4522
|
145
151
|
openepd/patch_pydantic.py,sha256=bO7U5HqthFol0vfycb0a42UAGL3KOQ8-9MW4yCWOFP0,4150
|
146
152
|
openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
147
|
-
openepd-6.
|
148
|
-
openepd-6.
|
149
|
-
openepd-6.
|
150
|
-
openepd-6.
|
153
|
+
openepd-6.21.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
154
|
+
openepd-6.21.0.dist-info/METADATA,sha256=5zzo-Y9jYVGStwGi6qPaqnxdc1eXKibPA8la_MuXInc,9827
|
155
|
+
openepd-6.21.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
156
|
+
openepd-6.21.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|