pyetp 0.0.39__py3-none-any.whl → 0.0.43a0__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.
- pyetp/__init__.py +10 -2
- pyetp/_version.py +34 -0
- pyetp/client.py +561 -620
- pyetp/config.py +9 -10
- pyetp/{resqml_objects/__init__.py → resqml_objects.py} +82 -72
- pyetp/types.py +130 -93
- pyetp/uri.py +28 -16
- pyetp/utils_arrays.py +224 -98
- pyetp/utils_xml.py +44 -678
- pyetp-0.0.43a0.dist-info/METADATA +88 -0
- pyetp-0.0.43a0.dist-info/RECORD +21 -0
- {pyetp-0.0.39.dist-info → pyetp-0.0.43a0.dist-info}/WHEEL +2 -1
- pyetp-0.0.43a0.dist-info/top_level.txt +2 -0
- resqml_objects/__init__.py +7 -0
- resqml_objects/epc_readers.py +114 -0
- resqml_objects/parsers.py +12 -0
- resqml_objects/serializers.py +10 -0
- resqml_objects/v201/__init__.py +1847 -0
- {pyetp/resqml_objects → resqml_objects/v201}/generated.py +2244 -2185
- resqml_objects/v201/utils.py +46 -0
- pyetp/utils.py +0 -15
- pyetp-0.0.39.dist-info/METADATA +0 -56
- pyetp-0.0.39.dist-info/RECORD +0 -14
- {pyetp-0.0.39.dist-info → pyetp-0.0.43a0.dist-info/licenses}/LICENSE.md +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyetp
|
|
3
|
+
Version: 0.0.43a0
|
|
4
|
+
Summary: Interface with OSDU RDDMS using ETP protocol
|
|
5
|
+
Author-email: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: homepage, https://github.com/equinor/pyetp
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Requires-Dist: numpy>=1.26.3
|
|
12
|
+
Requires-Dist: websockets>=12.0
|
|
13
|
+
Requires-Dist: lxml>=4.9.4
|
|
14
|
+
Requires-Dist: pydantic>=1.10
|
|
15
|
+
Requires-Dist: async-timeout>=5.0
|
|
16
|
+
Requires-Dist: xtgeo>=4.0.0
|
|
17
|
+
Requires-Dist: xsdata>=24.3.1
|
|
18
|
+
Requires-Dist: etpproto>=1.0.7
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+
[](https://badge.fury.io/py/pyetp)
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
# Installing the library
|
|
28
|
+
This package is published to PyPI, and can be installed via:
|
|
29
|
+
```bash
|
|
30
|
+
pip install pyetp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Local development
|
|
34
|
+
Locally we suggest setting up a virtual environment, and installing the latest
|
|
35
|
+
version of pip. Then install the library in editable mode along with the
|
|
36
|
+
`dev`-dependency group. That is:
|
|
37
|
+
```bash
|
|
38
|
+
python -m venv .venv
|
|
39
|
+
source .venv/bin/activate
|
|
40
|
+
pip install pip --upgrade
|
|
41
|
+
pip install -e .
|
|
42
|
+
pip install --group dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Linting and formatting
|
|
47
|
+
We use ruff as a linter and formatter. To lint run:
|
|
48
|
+
```bash
|
|
49
|
+
ruff check
|
|
50
|
+
```
|
|
51
|
+
To run the formatter do:
|
|
52
|
+
```bash
|
|
53
|
+
ruff format
|
|
54
|
+
```
|
|
55
|
+
Or if you just want to check what could have been formatted:
|
|
56
|
+
```bash
|
|
57
|
+
ruff format --check
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# RESQML versions
|
|
62
|
+
The library is built and tested against RESQML v2.0.1. The spec can be
|
|
63
|
+
downloaded
|
|
64
|
+
[here](https://publications.opengroup.org/standards/energistics-standards/v231a).
|
|
65
|
+
|
|
66
|
+
# Generated Python objects from RESQML spec
|
|
67
|
+
Under `src/pyetp/resqml_objects` you will find Python objects generated from
|
|
68
|
+
the RESQML xml spec.
|
|
69
|
+
|
|
70
|
+
# Documentation
|
|
71
|
+
See `/examples` for 2D grid usage
|
|
72
|
+
|
|
73
|
+
`tests/test_mesh.py` for Unstructured/structured mesh
|
|
74
|
+
|
|
75
|
+
# Running the unit tests
|
|
76
|
+
We have set up unit tests against a local open-etp-server. To start this server
|
|
77
|
+
run:
|
|
78
|
+
```bash
|
|
79
|
+
docker compose -f tests/compose.yml up [--detach]
|
|
80
|
+
```
|
|
81
|
+
If you want to re-use the same terminal window you should use the
|
|
82
|
+
`--detach`-option, otherwise start a new terminal. We use `pytest` for testing,
|
|
83
|
+
which can be run via:
|
|
84
|
+
```bash
|
|
85
|
+
py.test
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
# This library is under active development and subject to breaking changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
pyetp/__init__.py,sha256=Vu3_qz0AazlD4Q6ZLGdQVqZ7lhzqNLivpQRS3owD8SY,251
|
|
2
|
+
pyetp/_version.py,sha256=b-D25r0F2ojY_bfYgOb171Q-k4AtL4xVxGGim3FNCxk,714
|
|
3
|
+
pyetp/client.py,sha256=kZsPi2OPvbQKX6IVoAsrdZ9_ZDbZYEhHuHOhfbf3XxQ,37427
|
|
4
|
+
pyetp/config.py,sha256=uGEx6n-YF7Rtgwckf0ovRKNOKgCUsiQx4IA0Tyiqafk,870
|
|
5
|
+
pyetp/resqml_objects.py,sha256=j00e8scSh-yYv4Lpp9WjzLiaKqJWd5Cs7ROcJcxxFVw,50721
|
|
6
|
+
pyetp/types.py,sha256=zOfUzEQcgBvveWJyM6dD7U3xJ4SCkWElewNL0Ml-PPY,6761
|
|
7
|
+
pyetp/uri.py,sha256=Y05vuTO-XOurgDavBeoPGOidALoCKjCBIb7YHmfbAco,3115
|
|
8
|
+
pyetp/utils_arrays.py,sha256=bLrb8H8TMjbpNLo0Zb9xYRUnM2W4BOf7WxOxWK4fcTc,10218
|
|
9
|
+
pyetp/utils_xml.py,sha256=i11Zv2PuxW-ejEXxfGPYHvuJv7EHPYXwvgoe1U8fOUM,6826
|
|
10
|
+
pyetp-0.0.43a0.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
+
resqml_objects/__init__.py,sha256=ilimTrQScFryxHrfZwZURVpW0li19bVnayUcHR7S_Fs,183
|
|
12
|
+
resqml_objects/epc_readers.py,sha256=InYMlwjiZZRG9poQlWKFEOUNJmowGahXoNu_GniOaBw,3458
|
|
13
|
+
resqml_objects/parsers.py,sha256=UZdBi3QmBq4ejwKI9Dse_lMqL5Bx2s3QNTs8TKS4fO0,427
|
|
14
|
+
resqml_objects/serializers.py,sha256=JqRO6D6ExT5DrVyiNwBgNW28108f6spvxiVqJU0D9mc,352
|
|
15
|
+
resqml_objects/v201/__init__.py,sha256=yL3jWgkyGAzr-wt_WJDV_eARE75NoA6SPEKBrKM4Crk,51630
|
|
16
|
+
resqml_objects/v201/generated.py,sha256=Se0eePS6w25sfmnp2UBSkgzDGJ9c9Y2QqJgDRUTt_-Q,769527
|
|
17
|
+
resqml_objects/v201/utils.py,sha256=WiywauiJRBWhdjUvbKhpltRjoBX3qWd7qQ0_FAmIzUc,1442
|
|
18
|
+
pyetp-0.0.43a0.dist-info/METADATA,sha256=l1HO7RymATEmwlG6PCYhjLjzeBdl8Aj5qRd5pwtmrwY,2589
|
|
19
|
+
pyetp-0.0.43a0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
pyetp-0.0.43a0.dist-info/top_level.txt,sha256=NrdXbidkT5QR4NjH6nv2Frixknqse3jZq7bnqNdVb5k,21
|
|
21
|
+
pyetp-0.0.43a0.dist-info/RECORD,,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import pathlib
|
|
3
|
+
import sys
|
|
4
|
+
import zipfile
|
|
5
|
+
|
|
6
|
+
import h5py
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
import resqml_objects.v201 as ro_201
|
|
11
|
+
|
|
12
|
+
from .parsers import parse_resqml_v201_object
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_resqml_v201_objects(
|
|
18
|
+
epc_filename: str | pathlib.Path, log_failed_objects: bool = False
|
|
19
|
+
) -> list[ro_201.AbstractObject]:
|
|
20
|
+
"""This function is fairly brute-force; the function reads all files in the
|
|
21
|
+
provided EPC-file, and tries to parse it as one of the auto-generated
|
|
22
|
+
RESQML v2.0.1 objects. If you are unsure that all objects were included you
|
|
23
|
+
should set `log_failed_objects=True` and see if there are any missed
|
|
24
|
+
RESQML-objects. Objects such as `[Content_Types].xml`, `docProps/core.xml`,
|
|
25
|
+
and anything under the `_rels/`-directory are not RESQML-objects, and will
|
|
26
|
+
be logged as failed.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
epc_filename: str | pathlib.Path
|
|
32
|
+
The name of the EPC-file. Typically with an `.epc`-extension.
|
|
33
|
+
log_failed_objects: bool
|
|
34
|
+
Set to `True` in order to log the objects that were not parsed as
|
|
35
|
+
RESQML v2.0.1 objects along with their errors. Default is `False`.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
list[ro_201.AbstractObject]
|
|
41
|
+
A list of the parsed RESQML v2.0.1 objects.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
See Also
|
|
45
|
+
--------
|
|
46
|
+
get_arrays_and_paths_in_hdf_file
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
if sys.version_info.major == 3 and sys.version_info.minor == 10:
|
|
50
|
+
raise NotImplementedError(
|
|
51
|
+
"The epc-reader 'get_resqml_v201_objects'-function does not work for "
|
|
52
|
+
"Python 3.10. Consider switching to Python 3.11 or up."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
robjs = []
|
|
56
|
+
fail = {}
|
|
57
|
+
|
|
58
|
+
with zipfile.ZipFile(epc_filename, "r") as zf:
|
|
59
|
+
for zi in zf.infolist():
|
|
60
|
+
with zf.open(zi.filename) as f:
|
|
61
|
+
c = f.read()
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
robjs.append(parse_resqml_v201_object(c))
|
|
65
|
+
except AttributeError as e:
|
|
66
|
+
fail[zi.filename] = e
|
|
67
|
+
|
|
68
|
+
if log_failed_objects:
|
|
69
|
+
logger.info(f"Failed to parse: {fail}")
|
|
70
|
+
|
|
71
|
+
return robjs
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_arrays_and_paths_in_hdf_file(
|
|
75
|
+
epc_hdf_filename: str | pathlib.Path,
|
|
76
|
+
) -> dict[str, npt.NDArray[np.bool_ | np.number | np.character]]:
|
|
77
|
+
"""In this function we read all arrays and their full paths in an
|
|
78
|
+
HDF5-file. In the context of an EPC-file this corresponds to the
|
|
79
|
+
`pathInHdfFile`-attribute in a RESQML/WITSML/PRODML-object that has
|
|
80
|
+
connected array data.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
epc_hdf_filename: str | pathlib.Path
|
|
86
|
+
The filename of the HDF5-file that is connected to an EPC-file. This is
|
|
87
|
+
typically the same filename as the EPC-file, but with the
|
|
88
|
+
`.epc`-extension replaced by `.h5`.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
dict[str | npt.NDArray[np.bool_ | np.number | np.character]
|
|
94
|
+
A dictionary mapping the `pathInHdfFile`-key to the actual array as a
|
|
95
|
+
NumPy-array.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
See Also
|
|
99
|
+
--------
|
|
100
|
+
get_resqml_v201_objects
|
|
101
|
+
"""
|
|
102
|
+
datasets = {}
|
|
103
|
+
|
|
104
|
+
def populate_datasets(name: str, obj: h5py.Dataset | h5py.Group) -> None:
|
|
105
|
+
if isinstance(obj, h5py.Dataset):
|
|
106
|
+
# The name does not include the top level group "/". This creates a
|
|
107
|
+
# mismatch between the PathInHdfFile-element in the RESQML
|
|
108
|
+
# documents.
|
|
109
|
+
datasets["/" + name] = np.array(obj)
|
|
110
|
+
|
|
111
|
+
with h5py.File(epc_hdf_filename, "r") as f:
|
|
112
|
+
f.visititems(populate_datasets)
|
|
113
|
+
|
|
114
|
+
return datasets
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from lxml import etree
|
|
2
|
+
from xsdata.formats.dataclass.context import XmlContext
|
|
3
|
+
from xsdata.formats.dataclass.parsers import XmlParser
|
|
4
|
+
|
|
5
|
+
import resqml_objects.v201 as ro_201
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_resqml_v201_object(raw_data: bytes) -> ro_201.AbstractObject:
|
|
9
|
+
parser = XmlParser(context=XmlContext())
|
|
10
|
+
|
|
11
|
+
obj_name = etree.QName(etree.fromstring(raw_data).tag).localname
|
|
12
|
+
return parser.from_bytes(raw_data, getattr(ro_201, obj_name))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from xsdata.formats.dataclass.serializers import XmlSerializer
|
|
2
|
+
from xsdata.formats.dataclass.serializers.config import SerializerConfig
|
|
3
|
+
|
|
4
|
+
import resqml_objects.v201 as ro_201
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def serialize_resqml_v201_object(obj: ro_201.AbstractObject) -> bytes:
|
|
8
|
+
serializer = XmlSerializer(config=SerializerConfig())
|
|
9
|
+
|
|
10
|
+
return str.encode(serializer.render(obj))
|