openepd 4.4.0__py3-none-any.whl → 4.5.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/__init__.py +6 -0
- openepd/__version__.py +1 -1
- openepd/model/base.py +3 -23
- openepd/model/epd.py +101 -97
- openepd/model/factory.py +26 -6
- openepd/model/generic_estimate.py +85 -0
- openepd/model/geography.py +1739 -0
- openepd/patch_pydantic.py +101 -0
- {openepd-4.4.0.dist-info → openepd-4.5.0.dist-info}/METADATA +23 -10
- {openepd-4.4.0.dist-info → openepd-4.5.0.dist-info}/RECORD +12 -11
- openepd/mypy/__init__.py +0 -15
- openepd/mypy/custom_pydantic_plugin.py +0 -91
- {openepd-4.4.0.dist-info → openepd-4.5.0.dist-info}/LICENSE +0 -0
- {openepd-4.4.0.dist-info → openepd-4.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2024 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 Any
|
17
|
+
|
18
|
+
from pydantic import utils as pydantic_utils
|
19
|
+
|
20
|
+
from openepd.compat.pydantic import pyd
|
21
|
+
|
22
|
+
|
23
|
+
def patch_pydantic_metaclass():
|
24
|
+
"""
|
25
|
+
Patch pydantic's ModelMetaclass to restore class attribute lookup for fields.
|
26
|
+
|
27
|
+
In pydantic, while the model fields are defined in the model as class-level attributes, in runtime they disappear
|
28
|
+
due to ModelMetaclass logic. ModelMetaclass takes the defined attributes, removes them from class dict and puts
|
29
|
+
into a special __fields__ attribute to avoid naming conflict.
|
30
|
+
|
31
|
+
We would like to be able to access the attributes via dot notation in the runtimes, since it makes refactoring
|
32
|
+
easier.
|
33
|
+
|
34
|
+
This class exposes the original fields when accessed via class name. For example, one can call `Pcr.name` and get
|
35
|
+
`ModelField`, in addition to calling `pcr.__fields__` on an instance.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def model_metaclass__getattr__(cls, name: str) -> Any:
|
39
|
+
if name in cls.__fields__:
|
40
|
+
return cls.__fields__[name]
|
41
|
+
return getattr(super, name)
|
42
|
+
|
43
|
+
pyd.main.ModelMetaclass.__getattr__ = model_metaclass__getattr__
|
44
|
+
|
45
|
+
|
46
|
+
def patch_pydantic_metaclass_validator():
|
47
|
+
"""
|
48
|
+
Patch the internal validator function used during model construction to support modified metaclass.
|
49
|
+
|
50
|
+
Pydantic has a special guard which stops execution if a model defines field which shadows a basemodel interface.
|
51
|
+
For example, if someone would define a field named `__fields__` this would break code.
|
52
|
+
|
53
|
+
With the modified metaclass functionality, we are exposing the original fields as class attributes, and this
|
54
|
+
breaks this check.
|
55
|
+
|
56
|
+
This patcher method modifies the pydantic internals so that the check is retained, but it is not causing exception
|
57
|
+
when doing the normal field inheritance.
|
58
|
+
"""
|
59
|
+
model_field_classes = list()
|
60
|
+
|
61
|
+
try:
|
62
|
+
from pydantic.v1.fields import ModelField as ModelFieldV1
|
63
|
+
|
64
|
+
model_field_classes.append(ModelFieldV1)
|
65
|
+
except ImportError:
|
66
|
+
pass
|
67
|
+
|
68
|
+
try:
|
69
|
+
from pydantic_core.core_schema import ModelField as ModelFieldV2
|
70
|
+
|
71
|
+
model_field_classes.append(ModelFieldV2)
|
72
|
+
except ImportError:
|
73
|
+
pass
|
74
|
+
model_field_classes_tuple = tuple(model_field_classes)
|
75
|
+
|
76
|
+
def pydantic_utils__validate_field_name(bases: list[type[pyd.BaseModel]], field_name: str) -> None:
|
77
|
+
for base in bases:
|
78
|
+
if attr := getattr(base, field_name, None):
|
79
|
+
if isinstance(attr, model_field_classes_tuple):
|
80
|
+
continue
|
81
|
+
|
82
|
+
raise NameError(
|
83
|
+
f'Field name "{field_name}" shadows a BaseModel attribute; '
|
84
|
+
f"use a different field name with \"alias='{field_name}'\"."
|
85
|
+
)
|
86
|
+
|
87
|
+
pyd.main.validate_field_name = pydantic_utils__validate_field_name
|
88
|
+
pydantic_utils.validate_field_name = pydantic_utils__validate_field_name
|
89
|
+
|
90
|
+
|
91
|
+
def patch_pydantic():
|
92
|
+
"""
|
93
|
+
Modify Pydantic to support field attribute access via class.
|
94
|
+
|
95
|
+
Example: Field the_field in the model TheModel(BaseModel). Before this patch, one can do
|
96
|
+
`TheModel().__fields__["the_field"]' to get field descriptor. After the fix, one can do TheModel.the_field.
|
97
|
+
|
98
|
+
To disable, set env variable `OPENEPD_DISABLE_PYDANTIC_PATCH` to "1", "yes" or "true".
|
99
|
+
"""
|
100
|
+
patch_pydantic_metaclass()
|
101
|
+
patch_pydantic_metaclass_validator()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: openepd
|
3
|
-
Version: 4.
|
3
|
+
Version: 4.5.0
|
4
4
|
Summary: Python library to work with OpenEPD format
|
5
5
|
Home-page: https://github.com/cchangelabs/openepd
|
6
6
|
License: Apache-2.0
|
@@ -60,9 +60,7 @@ documenting supply-chain specific data.
|
|
60
60
|
|
61
61
|
## Usage
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
**❗ ATTENTION**: Pick the right version. The cornerstone of this library models package representing openEPD models.
|
63
|
+
**❗ ATTENTION**: Pick the right version. The cornerstone of this library models package representing openEPD models.
|
66
64
|
Models are defined with Pydantic library which is a dependency for openepd package. If you use Pydantic in your project
|
67
65
|
carefully pick the version:
|
68
66
|
|
@@ -76,9 +74,11 @@ module. For mode details on the usage please refer to Pydantic documentation.
|
|
76
74
|
|
77
75
|
### API Client
|
78
76
|
|
79
|
-
The library provides the API client to work with the OpenEPD API. The client is available in the `openepd.client`
|
77
|
+
The library provides the API client to work with the OpenEPD API. The client is available in the `openepd.client`
|
78
|
+
module.
|
80
79
|
Currently, the only available implementation is based on synchronous [requests]() library. Client provides the following
|
81
80
|
features:
|
81
|
+
|
82
82
|
* Error handling - depending on HTTP status code the client raises different exceptions allowing to handle errors
|
83
83
|
in a more granular way.
|
84
84
|
* Throttling - the client is able to throttle the requests to the API to avoid hitting the rate limits.
|
@@ -144,12 +144,25 @@ with DefaultBundleWriter("my-bundle.epb") as writer, open("test-pcr.pdf", "rb")
|
|
144
144
|
writer.write_blob_asset(pcr_pdf_file, "application/pdf", pcr_asset, RelType.Pdf)
|
145
145
|
```
|
146
146
|
|
147
|
-
###
|
147
|
+
### Model attribute access
|
148
|
+
|
149
|
+
OpenEPD extends its pydantic models with extra functionality: field descriptors can be accessed via dot notation from
|
150
|
+
class name:
|
151
|
+
|
152
|
+
* Usual pydantic way: TheModel().__field__["the_field"]
|
153
|
+
* In openEPD: TheModel.the_field
|
154
|
+
|
155
|
+
Instances hold data as usual.
|
156
|
+
|
157
|
+
This behaviour is enabled by default. To disable, run the code with `OPENEPD_DISABLE_PYDANTIC_PATCH` set to `true`.
|
158
|
+
|
159
|
+
See src/openepd/patch_pydantic.py for details.
|
160
|
+
|
161
|
+
### Generated enums
|
148
162
|
|
149
|
-
|
150
|
-
|
151
|
-
`
|
152
|
-
file. See [Mypy configuration](https://mypy.readthedocs.io/en/stable/extending_mypy.html)
|
163
|
+
The geography and country enums are generated from several sources, including pycountry list of 2-character country
|
164
|
+
codes, UN m49 codification, and special regions. To update the enums, first update any of these sources, then use
|
165
|
+
`make codegen`. See 'tools/openepd/codegen' for details.
|
153
166
|
|
154
167
|
# Credits
|
155
168
|
|
@@ -1,5 +1,5 @@
|
|
1
|
-
openepd/__init__.py,sha256=
|
2
|
-
openepd/__version__.py,sha256=
|
1
|
+
openepd/__init__.py,sha256=Shkfh0Kun0YRhmRDw7LkUj2eQL3X-HnP55u2THOEALw,794
|
2
|
+
openepd/__version__.py,sha256=Hcw_oQZEZ9hzNn_8In_JiWYNOFLzNVvUkS7-nl2hWyg,638
|
3
3
|
openepd/api/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
4
4
|
openepd/api/base_sync_client.py,sha256=jviqtQgsOVdRq5x7_Yh_Tg8zIdWtVTIUqNCgebf6YDg,20925
|
5
5
|
openepd/api/category/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
@@ -30,11 +30,13 @@ openepd/compat/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,62
|
|
30
30
|
openepd/compat/compat_functional_validators.py,sha256=yz6DfWeg7knBHEN_enpCGGTLRknEsecXfpzD1FDlywY,834
|
31
31
|
openepd/compat/pydantic.py,sha256=DOjSixsylLqMtFAIARu50sGcT4VPXN_c473q_2JwZQ0,1146
|
32
32
|
openepd/model/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
|
33
|
-
openepd/model/base.py,sha256=
|
33
|
+
openepd/model/base.py,sha256=uVEGndgLm8JFwffsCXdusfRE01DQSKJUJLxsd-GXba0,8988
|
34
34
|
openepd/model/category.py,sha256=IQXNGQFQmFZ_H9PRONloX_UOSf1sTMDq1rM1yz8JR0Y,1639
|
35
35
|
openepd/model/common.py,sha256=aa_bfotPybPoYyzHtwj5E5X1T-fCEyznMfVUWvpUhiM,5460
|
36
|
-
openepd/model/epd.py,sha256=
|
37
|
-
openepd/model/factory.py,sha256=
|
36
|
+
openepd/model/epd.py,sha256=2mdXxXZFoPOoXI_zSC1VzdBf0Q2sUNKg7vrtXZ8c8EE,14137
|
37
|
+
openepd/model/factory.py,sha256=StQUH7GNmRqskMnnRYkr_PLCl5X5sSLEPiYZOIQI9eo,2541
|
38
|
+
openepd/model/generic_estimate.py,sha256=txr2bCbrZPIaCNJZypkna0vzz7e0AizOp6MMhEU018A,3324
|
39
|
+
openepd/model/geography.py,sha256=amL_-0ojKhsiy_pVDbkM9Rm3yzBZC0KGpHQ_rZAYbM4,37417
|
38
40
|
openepd/model/lcia.py,sha256=-5bMz5ZyoZJnggp66v9upTT0yhcyIZYlwfFh83-4ZvY,16968
|
39
41
|
openepd/model/org.py,sha256=FHcYh2WOOQrCMyzm0Ow-iP79jMTBPcneidjH6NXIklA,3760
|
40
42
|
openepd/model/pcr.py,sha256=SwqLWMj9k_jqIzxz5mh6ttqvtLCspKSpywF5YTBOMsA,5397
|
@@ -86,10 +88,9 @@ openepd/model/validation/common.py,sha256=FLYqK8gYFagx08LCkS0jy3qo4-Zq9VAv5i8ZwF
|
|
86
88
|
openepd/model/validation/numbers.py,sha256=tgirqrDGgrSo6APGlW1ozNuVV8mJz_4HCAXS2OUENq0,888
|
87
89
|
openepd/model/validation/quantity.py,sha256=kzug0MZ3Ao0zeVzN-aleyxUg5hA_7D5tNOOerverfRQ,7415
|
88
90
|
openepd/model/versioning.py,sha256=R_zm6rCrgF3vlJQYbpyWhirdS_Oek16cv_mvZmpuE8I,4473
|
89
|
-
openepd/
|
90
|
-
openepd/mypy/custom_pydantic_plugin.py,sha256=JpfQHAmqS95lKm68tTlQ12RFO_O2tvd4ZM_DFBNUa8s,4034
|
91
|
+
openepd/patch_pydantic.py,sha256=W4n5nmCeurGfJNxiUiiDs73YqbG4JpMdTc2vPv5_6Ag,3946
|
91
92
|
openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
92
|
-
openepd-4.
|
93
|
-
openepd-4.
|
94
|
-
openepd-4.
|
95
|
-
openepd-4.
|
93
|
+
openepd-4.5.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
94
|
+
openepd-4.5.0.dist-info/METADATA,sha256=6RbVFGrMX0amNJi-F-by7jBBeUnVFaVB27guVslSiZ4,8534
|
95
|
+
openepd-4.5.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
96
|
+
openepd-4.5.0.dist-info/RECORD,,
|
openepd/mypy/__init__.py
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Copyright 2024 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
|
-
#
|
@@ -1,91 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Copyright 2024 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
|
-
from collections.abc import Callable
|
18
|
-
|
19
|
-
from mypy.nodes import AssignmentStmt, CallExpr, MemberExpr, TypeInfo
|
20
|
-
from mypy.plugin import ClassDefContext
|
21
|
-
import pydantic.mypy
|
22
|
-
from pydantic.mypy import (
|
23
|
-
MODEL_METACLASS_FULLNAME,
|
24
|
-
ModelConfigData,
|
25
|
-
PydanticModelClassVar,
|
26
|
-
PydanticModelField,
|
27
|
-
PydanticModelTransformer,
|
28
|
-
PydanticPlugin,
|
29
|
-
)
|
30
|
-
|
31
|
-
# Using this plugin fixes the issue.
|
32
|
-
|
33
|
-
CUSTOM_OPENEPD_MODEL_METACLASS_FULLNAME = "openepd.model.base.PydanticClassAttributeExposeModelMetaclass"
|
34
|
-
MODEL_METACLASSES_FULL_NAMES = (MODEL_METACLASS_FULLNAME, CUSTOM_OPENEPD_MODEL_METACLASS_FULLNAME)
|
35
|
-
|
36
|
-
DECORATOR_FULLNAMES = pydantic.mypy.DECORATOR_FULLNAMES | {
|
37
|
-
"pydantic.v1.class_validators.validator",
|
38
|
-
}
|
39
|
-
|
40
|
-
|
41
|
-
class CustomPydanticModelTransformer(PydanticModelTransformer):
|
42
|
-
"""Extension of the mypy/pydantic model transformer which also understands validator definitions via v1 compat."""
|
43
|
-
|
44
|
-
def collect_field_or_class_var_from_stmt(
|
45
|
-
self, stmt: AssignmentStmt, model_config: ModelConfigData, class_vars: dict[str, PydanticModelClassVar]
|
46
|
-
) -> PydanticModelField | PydanticModelClassVar | None:
|
47
|
-
"""Extend implementation of the original Pydantic method with one more case for validator."""
|
48
|
-
if not stmt.new_syntax and (
|
49
|
-
isinstance(stmt.rvalue, CallExpr)
|
50
|
-
and isinstance(stmt.rvalue.callee, CallExpr)
|
51
|
-
and isinstance(stmt.rvalue.callee.callee, MemberExpr)
|
52
|
-
and stmt.rvalue.callee.callee.fullname in DECORATOR_FULLNAMES
|
53
|
-
):
|
54
|
-
# Required to detect compat-imported v1 validators and not treat them as fields.
|
55
|
-
return None
|
56
|
-
return super().collect_field_or_class_var_from_stmt(stmt, model_config, class_vars)
|
57
|
-
|
58
|
-
|
59
|
-
class CustomMetaclassPydanticPlugin(PydanticPlugin):
|
60
|
-
"""
|
61
|
-
Custom metaclass pydantic plugin.
|
62
|
-
|
63
|
-
Extends a standard pydantic mypy plugin, and adds certain behaviours required for us:
|
64
|
-
1. Support for a non-standard metaclass for pydantic models. We use it allow for access via Class.field notation
|
65
|
-
2. Support for our modified compat import of pydantic v1 when using this metaclass.
|
66
|
-
"""
|
67
|
-
|
68
|
-
def get_metaclass_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
|
69
|
-
"""Update Pydantic `ModelMetaclass` definition."""
|
70
|
-
if fullname in MODEL_METACLASSES_FULL_NAMES:
|
71
|
-
return self._pydantic_model_metaclass_marker_callback
|
72
|
-
return None
|
73
|
-
|
74
|
-
def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], bool] | None: # type: ignore
|
75
|
-
"""Update Pydantic model class."""
|
76
|
-
sym = self.lookup_fully_qualified(fullname)
|
77
|
-
if sym and isinstance(sym.node, TypeInfo): # pragma: no branch
|
78
|
-
# No branching may occur if the mypy cache has not been cleared
|
79
|
-
if any(base.fullname in ["pydantic.main.BaseModel", "pydantic.v1.main.BaseModel"] for base in sym.node.mro):
|
80
|
-
return self._pydantic_model_class_maker_callback
|
81
|
-
return None
|
82
|
-
|
83
|
-
def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> bool:
|
84
|
-
# extended to replace the mypy-pydantic transformer with our custom transformer - see validator note.
|
85
|
-
transformer = CustomPydanticModelTransformer(ctx.cls, ctx.reason, ctx.api, self.plugin_config)
|
86
|
-
return transformer.transform()
|
87
|
-
|
88
|
-
|
89
|
-
def plugin(version: str):
|
90
|
-
"""Entry point to the mypy plugin."""
|
91
|
-
return CustomMetaclassPydanticPlugin
|
File without changes
|
File without changes
|