pyetp 0.0.45__py3-none-any.whl → 0.0.46__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 +1 -2
- pyetp/_version.py +2 -2
- pyetp/client.py +210 -136
- pyetp/uri.py +3 -1
- pyetp/utils_arrays.py +1 -7
- pyetp/utils_xml.py +1 -6
- {pyetp-0.0.45.dist-info → pyetp-0.0.46.dist-info}/METADATA +8 -3
- pyetp-0.0.46.dist-info/RECORD +22 -0
- {pyetp-0.0.45.dist-info → pyetp-0.0.46.dist-info}/WHEEL +1 -1
- resqml_objects/epc_readers.py +3 -7
- resqml_objects/parsers.py +18 -5
- resqml_objects/serializers.py +25 -2
- resqml_objects/surface_helpers.py +295 -0
- resqml_objects/v201/generated.py +540 -19
- pyetp-0.0.45.dist-info/RECORD +0 -21
- {pyetp-0.0.45.dist-info → pyetp-0.0.46.dist-info}/licenses/LICENSE.md +0 -0
- {pyetp-0.0.45.dist-info → pyetp-0.0.46.dist-info}/top_level.txt +0 -0
pyetp/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from ._version import __version__
|
|
2
|
-
from .client import ETPClient, ETPError, connect, etp_connect
|
|
2
|
+
from .client import ETPClient, ETPError, connect, etp_connect
|
|
3
3
|
from .uri import DataObjectURI, DataspaceURI
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
@@ -8,7 +8,6 @@ __all__ = [
|
|
|
8
8
|
"ETPError",
|
|
9
9
|
"connect",
|
|
10
10
|
"etp_connect",
|
|
11
|
-
"etp_persistent_connect",
|
|
12
11
|
"DataObjectURI",
|
|
13
12
|
"DataspaceURI",
|
|
14
13
|
]
|
pyetp/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.46'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 46)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
pyetp/client.py
CHANGED
|
@@ -2,7 +2,6 @@ import asyncio
|
|
|
2
2
|
import contextlib
|
|
3
3
|
import datetime
|
|
4
4
|
import logging
|
|
5
|
-
import sys
|
|
6
5
|
import typing as T
|
|
7
6
|
import uuid
|
|
8
7
|
import warnings
|
|
@@ -154,40 +153,6 @@ from resqml_objects import parse_resqml_v201_object, serialize_resqml_v201_objec
|
|
|
154
153
|
|
|
155
154
|
logger = logging.getLogger(__name__)
|
|
156
155
|
|
|
157
|
-
try:
|
|
158
|
-
# for py >3.11, we can raise grouped exceptions
|
|
159
|
-
from builtins import ExceptionGroup # type: ignore
|
|
160
|
-
except ImportError:
|
|
161
|
-
# Python 3.10
|
|
162
|
-
def ExceptionGroup(msg, errors):
|
|
163
|
-
return errors[0]
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
try:
|
|
167
|
-
# Python >= 3.11
|
|
168
|
-
from asyncio import timeout
|
|
169
|
-
except ImportError:
|
|
170
|
-
# Python 3.10
|
|
171
|
-
from contextlib import asynccontextmanager
|
|
172
|
-
|
|
173
|
-
import async_timeout
|
|
174
|
-
|
|
175
|
-
@asynccontextmanager
|
|
176
|
-
async def timeout(delay: T.Optional[float]) -> T.Any:
|
|
177
|
-
try:
|
|
178
|
-
async with async_timeout.timeout(delay):
|
|
179
|
-
yield None
|
|
180
|
-
except asyncio.CancelledError as e:
|
|
181
|
-
raise asyncio.TimeoutError(f"Timeout ({delay}s)") from e
|
|
182
|
-
|
|
183
|
-
TimeoutError = asyncio.TimeoutError
|
|
184
|
-
|
|
185
|
-
try:
|
|
186
|
-
# Python >= 3.11
|
|
187
|
-
from typing import Self
|
|
188
|
-
except ImportError:
|
|
189
|
-
Self = "ETPClient"
|
|
190
|
-
|
|
191
156
|
|
|
192
157
|
class ETPError(Exception):
|
|
193
158
|
def __init__(self, message: str, code: int):
|
|
@@ -315,7 +280,7 @@ class ETPClient(ETPConnection):
|
|
|
315
280
|
for ti in timeout_intervals(self.etp_timeout):
|
|
316
281
|
try:
|
|
317
282
|
# Wait for an event for `ti` seconds.
|
|
318
|
-
async with timeout(ti):
|
|
283
|
+
async with asyncio.timeout(ti):
|
|
319
284
|
await self._recv_events[correlation_id].wait()
|
|
320
285
|
except TimeoutError:
|
|
321
286
|
# Check if the receiver task is still running.
|
|
@@ -323,11 +288,18 @@ class ETPClient(ETPConnection):
|
|
|
323
288
|
# Raise any errors by waiting for the task to finish.
|
|
324
289
|
await self.__recvtask
|
|
325
290
|
|
|
326
|
-
|
|
327
|
-
|
|
291
|
+
# Check that the receiver task stopped due to a
|
|
292
|
+
# (successfully) closed websockets connection.
|
|
293
|
+
try:
|
|
294
|
+
await self.ws.recv()
|
|
295
|
+
except websockets.ConnectionClosedOK:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
# Terminate client with an error.
|
|
299
|
+
raise ReceiveWorkerExited(
|
|
300
|
+
"Receiver task terminated prematurely due to a closed "
|
|
301
|
+
"websockets connection"
|
|
328
302
|
)
|
|
329
|
-
|
|
330
|
-
raise ReceiveWorkerExited
|
|
331
303
|
else:
|
|
332
304
|
# Break out of for-loop, and start processing message.
|
|
333
305
|
break
|
|
@@ -372,13 +344,15 @@ class ETPClient(ETPConnection):
|
|
|
372
344
|
errors.extend(body.errors.values())
|
|
373
345
|
return errors
|
|
374
346
|
|
|
375
|
-
async def __aexit__(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
347
|
+
async def __aexit__(
|
|
348
|
+
self,
|
|
349
|
+
exc_type: T.Type[BaseException] | None,
|
|
350
|
+
exc_value: BaseException | None,
|
|
351
|
+
traceback: TracebackType | None,
|
|
352
|
+
) -> None:
|
|
379
353
|
close_session_sent = False
|
|
380
354
|
try:
|
|
381
|
-
await self._send(CloseSession(reason=
|
|
355
|
+
await self._send(CloseSession(reason="Client exiting"))
|
|
382
356
|
close_session_sent = True
|
|
383
357
|
except websockets.ConnectionClosed:
|
|
384
358
|
logger.error(
|
|
@@ -419,7 +393,7 @@ class ETPClient(ETPConnection):
|
|
|
419
393
|
# In some cases the server does not drop the connection after we
|
|
420
394
|
# have sent the `CloseSession`-message. We therefore add a timeout
|
|
421
395
|
# to the reading of possibly lost messages.
|
|
422
|
-
async with timeout(self.etp_timeout or 10):
|
|
396
|
+
async with asyncio.timeout(self.etp_timeout or 10):
|
|
423
397
|
async for msg in self.ws:
|
|
424
398
|
counter += 1
|
|
425
399
|
except websockets.ConnectionClosed:
|
|
@@ -428,7 +402,7 @@ class ETPClient(ETPConnection):
|
|
|
428
402
|
pass
|
|
429
403
|
except TimeoutError:
|
|
430
404
|
if close_session_sent:
|
|
431
|
-
logger.
|
|
405
|
+
logger.warning(
|
|
432
406
|
"Websockets connection was not closed within "
|
|
433
407
|
f"{self.etp_timeout or 10} seconds after the "
|
|
434
408
|
"`CloseSession`-message was sent"
|
|
@@ -442,17 +416,30 @@ class ETPClient(ETPConnection):
|
|
|
442
416
|
|
|
443
417
|
logger.debug("Client closed")
|
|
444
418
|
|
|
419
|
+
async def close(self):
|
|
420
|
+
"""Closing method that tears down the ETP-connection via the
|
|
421
|
+
`ETPClient.__aexit__`-method, and closes the websockets connection.
|
|
422
|
+
This method should _only_ be used if the user has set up a connection
|
|
423
|
+
via `etp_client = await connect(...)` or `etp_client = await
|
|
424
|
+
etp_connect(...)` and will handle the closing of the connection
|
|
425
|
+
manually.
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
await self.__aexit__(None, None, None)
|
|
429
|
+
# The websockets connection should be closed from the ETP-server once
|
|
430
|
+
# it has received a `CloseSession`-message. However, calling close on
|
|
431
|
+
# the websockets connection does not do anything if it is already
|
|
432
|
+
# closed.
|
|
433
|
+
await self.ws.close()
|
|
434
|
+
|
|
445
435
|
async def __recv(self):
|
|
446
436
|
logger.debug("Starting receiver loop")
|
|
447
437
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
# for`-case a closing code of `1000` (normal closing) just exits
|
|
454
|
-
# the loop.
|
|
455
|
-
msg_data = await self.ws.recv()
|
|
438
|
+
# Using `async for` makes the receiver task exit without errors on a
|
|
439
|
+
# `websockets.exceptions.ConnectionClosedOK`-exception. This ensures a
|
|
440
|
+
# smoother clean-up in case the main-task errors resulting in a closed
|
|
441
|
+
# websockets connection down the line.
|
|
442
|
+
async for msg_data in self.ws:
|
|
456
443
|
msg = Message.decode_binary_message(
|
|
457
444
|
T.cast(bytes, msg_data), ETPClient.generic_transition_table
|
|
458
445
|
)
|
|
@@ -468,11 +455,9 @@ class ETPClient(ETPConnection):
|
|
|
468
455
|
# set response on send event
|
|
469
456
|
self._recv_events[msg.header.correlation_id].set()
|
|
470
457
|
|
|
471
|
-
|
|
472
|
-
# session related
|
|
473
|
-
#
|
|
458
|
+
logger.info("Websockets connection closed and receiver task stopped")
|
|
474
459
|
|
|
475
|
-
async def __aenter__(self) -> Self:
|
|
460
|
+
async def __aenter__(self) -> T.Self:
|
|
476
461
|
return await self.request_session()
|
|
477
462
|
|
|
478
463
|
async def request_session(self):
|
|
@@ -811,20 +796,19 @@ class ETPClient(ETPConnection):
|
|
|
811
796
|
"obj must be Grid2DRepresentation"
|
|
812
797
|
)
|
|
813
798
|
sgeo = gri.grid2d_patch.geometry.points.supporting_geometry # type: ignore
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
)
|
|
799
|
+
assert isinstance(gri.grid2d_patch.geometry.points, ro.Point3dZValueArray), (
|
|
800
|
+
"Points must be Point3dZValueArray"
|
|
801
|
+
)
|
|
802
|
+
assert isinstance(
|
|
803
|
+
gri.grid2d_patch.geometry.points.zvalues, ro.DoubleHdf5Array
|
|
804
|
+
), "Values must be DoubleHdf5Array"
|
|
805
|
+
assert isinstance(
|
|
806
|
+
gri.grid2d_patch.geometry.points.supporting_geometry,
|
|
807
|
+
ro.Point3dLatticeArray,
|
|
808
|
+
), "Points support_geo must be Point3dLatticeArray"
|
|
809
|
+
assert isinstance(sgeo, ro.Point3dLatticeArray), (
|
|
810
|
+
"supporting_geometry must be Point3dLatticeArray"
|
|
811
|
+
)
|
|
828
812
|
assert isinstance(
|
|
829
813
|
gri.grid2d_patch.geometry.points.zvalues.values, ro.Hdf5Dataset
|
|
830
814
|
), "Values must be Hdf5Dataset"
|
|
@@ -1407,6 +1391,7 @@ class connect:
|
|
|
1407
1391
|
# ... = await connect(...)
|
|
1408
1392
|
|
|
1409
1393
|
def __await__(self):
|
|
1394
|
+
# The caller is response for calling `close()` on the client.
|
|
1410
1395
|
return self.__aenter__().__await__()
|
|
1411
1396
|
|
|
1412
1397
|
# async with connect(...) as ...:
|
|
@@ -1420,7 +1405,7 @@ class connect:
|
|
|
1420
1405
|
if self.data_partition is not None:
|
|
1421
1406
|
headers["data-partition-id"] = self.data_partition
|
|
1422
1407
|
|
|
1423
|
-
|
|
1408
|
+
ws = await websockets.connect(
|
|
1424
1409
|
self.server_url,
|
|
1425
1410
|
subprotocols=[ETPClient.SUB_PROTOCOL], # type: ignore
|
|
1426
1411
|
additional_headers=headers,
|
|
@@ -1430,7 +1415,7 @@ class connect:
|
|
|
1430
1415
|
)
|
|
1431
1416
|
|
|
1432
1417
|
self.client = ETPClient(
|
|
1433
|
-
|
|
1418
|
+
ws=ws,
|
|
1434
1419
|
etp_timeout=self.timeout,
|
|
1435
1420
|
max_message_size=SETTINGS.MaxWebSocketMessagePayloadSize,
|
|
1436
1421
|
application_name=SETTINGS.application_name,
|
|
@@ -1448,67 +1433,156 @@ class connect:
|
|
|
1448
1433
|
|
|
1449
1434
|
# exit the async context manager
|
|
1450
1435
|
async def __aexit__(self, exc_type, exc: Exception, tb: TracebackType):
|
|
1436
|
+
# The `ETPClient.close`-method also closes the websockets connection.
|
|
1451
1437
|
await self.client.close()
|
|
1452
|
-
await self.ws.close()
|
|
1453
1438
|
|
|
1454
1439
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1440
|
+
class etp_connect:
|
|
1441
|
+
"""
|
|
1442
|
+
Connect to an ETP server via websockets.
|
|
1443
|
+
|
|
1444
|
+
This class can act as:
|
|
1445
|
+
|
|
1446
|
+
1. A context manager handling setup and tear-down of the connection.
|
|
1447
|
+
2. An asynchronous iterator which can be used to persistently retry to
|
|
1448
|
+
connect if the websockets connection drops.
|
|
1449
|
+
3. An awaitable connection that must be manually closed by the user.
|
|
1450
|
+
|
|
1451
|
+
See below for examples of all three cases.
|
|
1452
|
+
|
|
1453
|
+
Parameters
|
|
1454
|
+
----------
|
|
1455
|
+
uri: str
|
|
1456
|
+
The uri to the ETP server. This should be the uri to a websockets
|
|
1457
|
+
endpoint.
|
|
1458
|
+
data_partition_id: str | None
|
|
1459
|
+
The data partition id used when connecting to the OSDU open-etp-server
|
|
1460
|
+
in multi-partition mode. Default is `None`.
|
|
1461
|
+
authorization: str | SecretStr | None
|
|
1462
|
+
Bearer token used for authenticating to the ETP server. This token
|
|
1463
|
+
should be on the form `"Bearer 1234..."`. Default is `None`.
|
|
1464
|
+
etp_timeout: float | None
|
|
1465
|
+
The timeout in seconds for when to stop waiting for a message from the
|
|
1466
|
+
ETP server. Setting it to `None` will persist the connection
|
|
1467
|
+
indefinetly. Default is `None`.
|
|
1468
|
+
max_message_size: float
|
|
1469
|
+
The maximum number of bytes for a single websockets message. Default is
|
|
1470
|
+
`2**20` corresponding to `1` MiB.
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
Examples
|
|
1474
|
+
--------
|
|
1475
|
+
An example of connecting to the ETP server using :func:`etp_connect` as a
|
|
1476
|
+
context manager is:
|
|
1477
|
+
|
|
1478
|
+
async with etp_connect(...) as etp_client:
|
|
1479
|
+
...
|
|
1480
|
+
|
|
1481
|
+
In this case the closing message and the websockets connection is closed
|
|
1482
|
+
once the program exits the context manager.
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
To persist a connection if the websockets connection is dropped (for any
|
|
1486
|
+
reason), use :func:`etp_connect` as an asynchronous generator, viz.:
|
|
1487
|
+
|
|
1488
|
+
import websockets
|
|
1489
|
+
|
|
1490
|
+
async for etp_client in etp_connect(...):
|
|
1491
|
+
try:
|
|
1492
|
+
...
|
|
1493
|
+
except websockets.ConnectionClosed:
|
|
1494
|
+
continue
|
|
1495
|
+
|
|
1496
|
+
# Include `break` to avoid re-running the whole block if the
|
|
1497
|
+
# iteration runs without any errors.
|
|
1498
|
+
break
|
|
1499
|
+
|
|
1500
|
+
Note that in this case the whole program under the `try`-block is re-run
|
|
1501
|
+
from the start if the iteration completes normally, or if the websockets
|
|
1502
|
+
connection is dropped. Therefore, make sure to include a `break` at the end
|
|
1503
|
+
of the `try`-block (as in the example above).
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
The third option is to set up a connection via `await` and then manually
|
|
1507
|
+
close the connection once done:
|
|
1508
|
+
|
|
1509
|
+
etp_client = await etp_connect(...)
|
|
1510
|
+
...
|
|
1511
|
+
await etp_client.close()
|
|
1512
|
+
"""
|
|
1513
|
+
|
|
1514
|
+
def __init__(
|
|
1515
|
+
self,
|
|
1516
|
+
uri: str,
|
|
1517
|
+
data_partition_id: str | None = None,
|
|
1518
|
+
authorization: str | SecretStr | None = None,
|
|
1519
|
+
etp_timeout: float | None = None,
|
|
1520
|
+
max_message_size: float = 2**20,
|
|
1521
|
+
) -> None:
|
|
1522
|
+
self.uri = uri
|
|
1523
|
+
self.data_partition_id = data_partition_id
|
|
1524
|
+
self.authorization = SecretStr(authorization)
|
|
1525
|
+
self.etp_timeout = etp_timeout
|
|
1526
|
+
self.max_message_size = max_message_size
|
|
1527
|
+
self.subprotocols = ["etp12.energistics.org"]
|
|
1528
|
+
|
|
1529
|
+
def __await__(self) -> ETPClient:
|
|
1530
|
+
# The caller is responsible for calling `close()` on the client.
|
|
1531
|
+
return self.__aenter__().__await__()
|
|
1532
|
+
|
|
1533
|
+
def get_additional_headers(self) -> dict[str, str]:
|
|
1534
|
+
additional_headers = {}
|
|
1535
|
+
|
|
1536
|
+
if self.authorization.get_secret_value() is not None:
|
|
1537
|
+
additional_headers["Authorization"] = self.authorization.get_secret_value()
|
|
1538
|
+
|
|
1539
|
+
if self.data_partition_id is not None:
|
|
1540
|
+
additional_headers["data-partition-id"] = self.data_partition_id
|
|
1541
|
+
|
|
1542
|
+
return additional_headers
|
|
1543
|
+
|
|
1544
|
+
async def __aenter__(self) -> ETPClient:
|
|
1545
|
+
self.stack = contextlib.AsyncExitStack()
|
|
1546
|
+
try:
|
|
1547
|
+
ws = await self.stack.enter_async_context(
|
|
1548
|
+
websockets.connect(
|
|
1549
|
+
uri=self.uri,
|
|
1550
|
+
subprotocols=self.subprotocols,
|
|
1551
|
+
max_size=self.max_message_size,
|
|
1552
|
+
additional_headers=self.get_additional_headers(),
|
|
1553
|
+
)
|
|
1554
|
+
)
|
|
1555
|
+
etp_client = await self.stack.enter_async_context(
|
|
1556
|
+
ETPClient(
|
|
1557
|
+
ws=ws,
|
|
1558
|
+
etp_timeout=self.etp_timeout,
|
|
1559
|
+
max_message_size=self.max_message_size,
|
|
1560
|
+
)
|
|
1561
|
+
)
|
|
1562
|
+
except BaseException:
|
|
1563
|
+
await self.stack.aclose()
|
|
1564
|
+
raise
|
|
1565
|
+
|
|
1566
|
+
return etp_client
|
|
1567
|
+
|
|
1568
|
+
async def __aexit__(
|
|
1569
|
+
self,
|
|
1570
|
+
exc_type: T.Type[BaseException] | None,
|
|
1571
|
+
exc_value: BaseException | None,
|
|
1572
|
+
traceback: TracebackType | None,
|
|
1573
|
+
) -> None:
|
|
1574
|
+
return await self.stack.aclose()
|
|
1575
|
+
|
|
1576
|
+
async def __aiter__(self) -> AsyncGenerator[ETPClient]:
|
|
1577
|
+
async for ws in websockets.connect(
|
|
1578
|
+
uri=self.uri,
|
|
1579
|
+
subprotocols=self.subprotocols,
|
|
1580
|
+
max_size=self.max_message_size,
|
|
1581
|
+
additional_headers=self.get_additional_headers(),
|
|
1582
|
+
):
|
|
1583
|
+
async with ETPClient(
|
|
1584
|
+
ws=ws,
|
|
1585
|
+
etp_timeout=self.etp_timeout,
|
|
1586
|
+
max_message_size=self.max_message_size,
|
|
1587
|
+
) as etp_client:
|
|
1588
|
+
yield etp_client
|
pyetp/uri.py
CHANGED
|
@@ -83,7 +83,9 @@ class DataObjectURI(_DataObjectURI, _Mixin):
|
|
|
83
83
|
obj.Meta, "target_namespace"
|
|
84
84
|
)
|
|
85
85
|
namespace = namespace.lower()
|
|
86
|
-
# TODO: we can rather look at citation.format - which could be used
|
|
86
|
+
# TODO: we can rather look at citation.format - which could be used
|
|
87
|
+
# for xmlformat ? - however to be backward capatiable we check
|
|
88
|
+
# namespaces instead
|
|
87
89
|
if namespace.endswith("resqmlv2"):
|
|
88
90
|
domain = "resqml20"
|
|
89
91
|
elif namespace.endswith("data/commonv2"):
|
pyetp/utils_arrays.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
import typing as T
|
|
3
2
|
|
|
4
3
|
import numpy as np
|
|
@@ -50,12 +49,7 @@ _ANY_LOGICAL_ARRAY_TYPE_MAP: dict[npt.DTypeLike, AnyLogicalArrayType] = {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
valid_logical_array_dtypes = list(_ANY_LOGICAL_ARRAY_TYPE_MAP)
|
|
53
|
-
|
|
54
|
-
LogicalArrayDTypes: T.TypeAlias = T.Union[
|
|
55
|
-
tuple(v.type for v in valid_logical_array_dtypes)
|
|
56
|
-
]
|
|
57
|
-
else:
|
|
58
|
-
LogicalArrayDTypes: T.TypeAlias = T.Union[tuple(valid_logical_array_dtypes)]
|
|
52
|
+
LogicalArrayDTypes: T.TypeAlias = T.Union[tuple(valid_logical_array_dtypes)]
|
|
59
53
|
|
|
60
54
|
_INV_ANY_LOGICAL_ARRAY_TYPE_MAP: dict[AnyLogicalArrayType, npt.DTypeLike] = {
|
|
61
55
|
v: k for k, v in _ANY_LOGICAL_ARRAY_TYPE_MAP.items()
|
pyetp/utils_xml.py
CHANGED
|
@@ -118,13 +118,8 @@ def instantiate_resqml_grid(
|
|
|
118
118
|
grid2d_patch=ro.Grid2dPatch(
|
|
119
119
|
# TODO: Perhaps we can use this for tiling?
|
|
120
120
|
patch_index=0,
|
|
121
|
-
# NumPy-arrays are C-ordered, meaning that the last index is
|
|
122
|
-
# the index that changes most rapidly. However, xtgeo uses nrow for
|
|
123
|
-
# axis 1 in the array, and ncol for axis 0. This means that
|
|
124
|
-
# surf.nrow is the fastest changing axis, and surf.ncol the slowest
|
|
125
|
-
# changing axis (as surf.values.shape == (surf.ncol, surf.nrow))
|
|
126
|
-
fastest_axis_count=ny,
|
|
127
121
|
slowest_axis_count=nx,
|
|
122
|
+
fastest_axis_count=ny,
|
|
128
123
|
geometry=ro.PointGeometry(
|
|
129
124
|
local_crs=get_data_object_reference(crs),
|
|
130
125
|
points=ro.Point3dZValueArray(
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyetp
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.46
|
|
4
4
|
Summary: Interface with OSDU RDDMS using ETP protocol
|
|
5
5
|
Author-email: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: homepage, https://github.com/equinor/pyetp
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
10
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.11
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
15
14
|
License-File: LICENSE.md
|
|
16
15
|
Requires-Dist: numpy>=1.26.0
|
|
@@ -29,6 +28,12 @@ Dynamic: license-file
|
|
|
29
28
|
[](https://badge.fury.io/py/pyetp)
|
|
30
29
|

|
|
31
30
|
|
|
31
|
+
Pyetp is a library implementing an ETP v1.2 client with utilities and support
|
|
32
|
+
for working with RESQML v2.0.1 models.
|
|
33
|
+
|
|
34
|
+
> The following Energistics (c) products were used in the creation of this work:
|
|
35
|
+
> Energistics Transfer Protocol (ETP) v1.2 and RESQML v2.0.1
|
|
36
|
+
|
|
32
37
|
# Installing the library
|
|
33
38
|
This package is published to PyPI, and can be installed via:
|
|
34
39
|
```bash
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
pyetp/__init__.py,sha256=nh-eVYEWMMxqx4YZLzN7fV7NXwbutr1soD_ot48860U,283
|
|
2
|
+
pyetp/_version.py,sha256=Zfksky0dIFN2RZprnAMflcd2YbMf3D0JpTlHasmdh24,706
|
|
3
|
+
pyetp/client.py,sha256=7cCDUURjWRBcmR94hbnwrLfzQoiAUV4KO0qIptQ0Hx4,57935
|
|
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=zbwAeyqLgRqgN-xAbOYmiTtHtVJwfveaMbjl7yc2ifU,3143
|
|
8
|
+
pyetp/utils_arrays.py,sha256=P9F_FWxf9IkqMI1CDdoRjKqD1bWB5DCOJWdHrT7UHfQ,13251
|
|
9
|
+
pyetp/utils_xml.py,sha256=qETAMooFWXBvV_SIMGv1TEG2tLWCbgPraARkftqblKw,6444
|
|
10
|
+
pyetp-0.0.46.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
+
resqml_objects/__init__.py,sha256=ilimTrQScFryxHrfZwZURVpW0li19bVnayUcHR7S_Fs,183
|
|
12
|
+
resqml_objects/epc_readers.py,sha256=-k2GGK8VgVoqX3-ThyLjs_5OlgNHu1Sd4LZAq6YSF18,3306
|
|
13
|
+
resqml_objects/parsers.py,sha256=aX7W4sP68NlEp8bFB88A8mXWpBHmr79ahpvgD04vCdY,724
|
|
14
|
+
resqml_objects/serializers.py,sha256=wgWhO7ynn51cWRvt5BKdLvxZRqWuopoHFQn0tX8d2NA,1155
|
|
15
|
+
resqml_objects/surface_helpers.py,sha256=-x9DqCliI0dUP4ierTypVFWx7eOH_0j6iDdLxzpGPtk,11207
|
|
16
|
+
resqml_objects/v201/__init__.py,sha256=yL3jWgkyGAzr-wt_WJDV_eARE75NoA6SPEKBrKM4Crk,51630
|
|
17
|
+
resqml_objects/v201/generated.py,sha256=vr9fRNen5GmU75g7PlX1hgusJ4vD_K1nMueV-XgTMis,789217
|
|
18
|
+
resqml_objects/v201/utils.py,sha256=WiywauiJRBWhdjUvbKhpltRjoBX3qWd7qQ0_FAmIzUc,1442
|
|
19
|
+
pyetp-0.0.46.dist-info/METADATA,sha256=xuiA72Cwtb83H1kpdhdRlCY1MiwE23K9YweoYfqDFaI,3096
|
|
20
|
+
pyetp-0.0.46.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
21
|
+
pyetp-0.0.46.dist-info/top_level.txt,sha256=NrdXbidkT5QR4NjH6nv2Frixknqse3jZq7bnqNdVb5k,21
|
|
22
|
+
pyetp-0.0.46.dist-info/RECORD,,
|
resqml_objects/epc_readers.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import pathlib
|
|
3
|
-
import sys
|
|
4
3
|
import zipfile
|
|
5
4
|
|
|
6
5
|
import h5py
|
|
7
6
|
import numpy as np
|
|
8
7
|
import numpy.typing as npt
|
|
8
|
+
from xsdata.exceptions import ParserError
|
|
9
9
|
|
|
10
10
|
import resqml_objects.v201 as ro_201
|
|
11
11
|
|
|
@@ -46,12 +46,6 @@ def get_resqml_v201_objects(
|
|
|
46
46
|
get_arrays_and_paths_in_hdf_file
|
|
47
47
|
"""
|
|
48
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
49
|
robjs = []
|
|
56
50
|
fail = {}
|
|
57
51
|
|
|
@@ -64,6 +58,8 @@ def get_resqml_v201_objects(
|
|
|
64
58
|
robjs.append(parse_resqml_v201_object(c))
|
|
65
59
|
except AttributeError as e:
|
|
66
60
|
fail[zi.filename] = e
|
|
61
|
+
except ParserError as e:
|
|
62
|
+
fail[zi.filename] = e
|
|
67
63
|
|
|
68
64
|
if log_failed_objects:
|
|
69
65
|
logger.info(f"Failed to parse: {fail}")
|
resqml_objects/parsers.py
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
from lxml import etree
|
|
2
|
-
from xsdata.formats.dataclass.
|
|
2
|
+
from xsdata.formats.dataclass.models.generics import DerivedElement
|
|
3
3
|
from xsdata.formats.dataclass.parsers import XmlParser
|
|
4
4
|
|
|
5
5
|
import resqml_objects.v201 as ro_201
|
|
6
6
|
|
|
7
|
+
xsi_type_key = "{http://www.w3.org/2001/XMLSchema-instance}type"
|
|
7
8
|
|
|
8
|
-
def parse_resqml_v201_object(raw_data: bytes) -> ro_201.AbstractObject:
|
|
9
|
-
parser = XmlParser(context=XmlContext())
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
def parse_resqml_v201_object(
|
|
11
|
+
raw_data: bytes,
|
|
12
|
+
) -> ro_201.AbstractObject:
|
|
13
|
+
parser = XmlParser()
|
|
14
|
+
|
|
15
|
+
xml_obj = etree.fromstring(raw_data)
|
|
16
|
+
obj_type = xml_obj.get(xsi_type_key) or etree.QName(xml_obj.tag).localname
|
|
17
|
+
|
|
18
|
+
if ":" in obj_type:
|
|
19
|
+
obj_type = obj_type.split(":")[1]
|
|
20
|
+
|
|
21
|
+
parsed_obj = parser.from_bytes(raw_data, getattr(ro_201, obj_type))
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
parsed_obj if not isinstance(parsed_obj, DerivedElement) else parsed_obj.value
|
|
25
|
+
)
|