pyetp 0.0.45__py3-none-any.whl → 0.0.47__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.
- energistics/__init__.py +0 -0
- energistics/etp/__init__.py +0 -0
- energistics/etp/v12/__init__.py +0 -0
- energistics/etp/v12/datatypes/__init__.py +25 -0
- energistics/etp/v12/datatypes/data_array_types/__init__.py +27 -0
- energistics/etp/v12/datatypes/object/__init__.py +22 -0
- energistics/etp/v12/protocol/__init__.py +0 -0
- energistics/etp/v12/protocol/core/__init__.py +19 -0
- energistics/etp/v12/protocol/data_array/__init__.py +51 -0
- energistics/etp/v12/protocol/dataspace/__init__.py +23 -0
- energistics/etp/v12/protocol/discovery/__init__.py +21 -0
- energistics/etp/v12/protocol/store/__init__.py +27 -0
- energistics/etp/v12/protocol/transaction/__init__.py +27 -0
- pyetp/__init__.py +1 -2
- pyetp/_version.py +2 -2
- pyetp/client.py +426 -306
- pyetp/errors.py +39 -0
- 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.47.dist-info}/METADATA +8 -3
- pyetp-0.0.47.dist-info/RECORD +39 -0
- {pyetp-0.0.45.dist-info → pyetp-0.0.47.dist-info}/WHEEL +1 -1
- {pyetp-0.0.45.dist-info → pyetp-0.0.47.dist-info}/top_level.txt +2 -0
- rddms_io/__init__.py +0 -0
- rddms_io/client.py +1234 -0
- rddms_io/data_types.py +11 -0
- 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 +582 -19
- resqml_objects/v201/utils.py +38 -0
- pyetp-0.0.45.dist-info/RECORD +0 -21
- {pyetp-0.0.45.dist-info → pyetp-0.0.47.dist-info}/licenses/LICENSE.md +0 -0
pyetp/client.py
CHANGED
|
@@ -2,12 +2,11 @@ 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
|
|
9
8
|
from collections import defaultdict
|
|
10
|
-
from collections.abc import AsyncGenerator
|
|
9
|
+
from collections.abc import AsyncGenerator, Generator
|
|
11
10
|
from types import TracebackType
|
|
12
11
|
|
|
13
12
|
import numpy as np
|
|
@@ -17,177 +16,94 @@ import websockets.client
|
|
|
17
16
|
from etpproto.connection import CommunicationProtocol, ConnectionType, ETPConnection
|
|
18
17
|
from etpproto.messages import Message, MessageFlags
|
|
19
18
|
from etptypes import ETPModel
|
|
20
|
-
from
|
|
21
|
-
from
|
|
19
|
+
from pydantic import SecretStr
|
|
20
|
+
from xtgeo import RegularSurface
|
|
21
|
+
|
|
22
|
+
import resqml_objects.v201 as ro
|
|
23
|
+
from energistics.etp.v12.datatypes import (
|
|
24
|
+
AnyArrayType,
|
|
22
25
|
AnyLogicalArrayType,
|
|
26
|
+
ArrayOfString,
|
|
27
|
+
DataValue,
|
|
28
|
+
ErrorInfo,
|
|
29
|
+
SupportedDataObject,
|
|
30
|
+
SupportedProtocol,
|
|
31
|
+
Uuid,
|
|
32
|
+
Version,
|
|
23
33
|
)
|
|
24
|
-
from
|
|
25
|
-
from etptypes.energistics.etp.v12.datatypes.data_array_types.data_array_identifier import (
|
|
34
|
+
from energistics.etp.v12.datatypes.data_array_types import (
|
|
26
35
|
DataArrayIdentifier,
|
|
27
|
-
)
|
|
28
|
-
from etptypes.energistics.etp.v12.datatypes.data_array_types.data_array_metadata import (
|
|
29
36
|
DataArrayMetadata,
|
|
30
|
-
)
|
|
31
|
-
from etptypes.energistics.etp.v12.datatypes.data_array_types.get_data_subarrays_type import (
|
|
32
37
|
GetDataSubarraysType,
|
|
33
|
-
)
|
|
34
|
-
from etptypes.energistics.etp.v12.datatypes.data_array_types.put_data_arrays_type import (
|
|
35
38
|
PutDataArraysType,
|
|
36
|
-
)
|
|
37
|
-
from etptypes.energistics.etp.v12.datatypes.data_array_types.put_data_subarrays_type import (
|
|
38
39
|
PutDataSubarraysType,
|
|
39
|
-
)
|
|
40
|
-
from etptypes.energistics.etp.v12.datatypes.data_array_types.put_uninitialized_data_array_type import (
|
|
41
40
|
PutUninitializedDataArrayType,
|
|
42
41
|
)
|
|
43
|
-
from
|
|
44
|
-
|
|
45
|
-
from etptypes.energistics.etp.v12.datatypes.object.context_info import ContextInfo
|
|
46
|
-
from etptypes.energistics.etp.v12.datatypes.object.context_scope_kind import (
|
|
42
|
+
from energistics.etp.v12.datatypes.object import (
|
|
43
|
+
ContextInfo,
|
|
47
44
|
ContextScopeKind,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
from etptypes.energistics.etp.v12.datatypes.object.dataspace import Dataspace
|
|
51
|
-
from etptypes.energistics.etp.v12.datatypes.object.relationship_kind import (
|
|
45
|
+
DataObject,
|
|
46
|
+
Dataspace,
|
|
52
47
|
RelationshipKind,
|
|
48
|
+
Resource,
|
|
53
49
|
)
|
|
54
|
-
from
|
|
55
|
-
|
|
56
|
-
SupportedDataObject,
|
|
57
|
-
)
|
|
58
|
-
from etptypes.energistics.etp.v12.datatypes.supported_protocol import SupportedProtocol
|
|
59
|
-
from etptypes.energistics.etp.v12.datatypes.uuid import Uuid
|
|
60
|
-
from etptypes.energistics.etp.v12.datatypes.version import Version
|
|
61
|
-
from etptypes.energistics.etp.v12.protocol.core.authorize import Authorize
|
|
62
|
-
from etptypes.energistics.etp.v12.protocol.core.authorize_response import (
|
|
50
|
+
from energistics.etp.v12.protocol.core import (
|
|
51
|
+
Authorize,
|
|
63
52
|
AuthorizeResponse,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
from etptypes.energistics.etp.v12.protocol.core.open_session import OpenSession
|
|
67
|
-
from etptypes.energistics.etp.v12.protocol.core.protocol_exception import (
|
|
53
|
+
CloseSession,
|
|
54
|
+
OpenSession,
|
|
68
55
|
ProtocolException,
|
|
56
|
+
RequestSession,
|
|
69
57
|
)
|
|
70
|
-
from
|
|
71
|
-
from etptypes.energistics.etp.v12.protocol.data_array.get_data_array_metadata import (
|
|
58
|
+
from energistics.etp.v12.protocol.data_array import (
|
|
72
59
|
GetDataArrayMetadata,
|
|
73
|
-
)
|
|
74
|
-
from etptypes.energistics.etp.v12.protocol.data_array.get_data_array_metadata_response import (
|
|
75
60
|
GetDataArrayMetadataResponse,
|
|
76
|
-
)
|
|
77
|
-
from etptypes.energistics.etp.v12.protocol.data_array.get_data_arrays import (
|
|
78
61
|
GetDataArrays,
|
|
79
|
-
)
|
|
80
|
-
from etptypes.energistics.etp.v12.protocol.data_array.get_data_arrays_response import (
|
|
81
62
|
GetDataArraysResponse,
|
|
82
|
-
)
|
|
83
|
-
from etptypes.energistics.etp.v12.protocol.data_array.get_data_subarrays import (
|
|
84
63
|
GetDataSubarrays,
|
|
85
|
-
)
|
|
86
|
-
from etptypes.energistics.etp.v12.protocol.data_array.get_data_subarrays_response import (
|
|
87
64
|
GetDataSubarraysResponse,
|
|
88
|
-
)
|
|
89
|
-
from etptypes.energistics.etp.v12.protocol.data_array.put_data_arrays import (
|
|
90
65
|
PutDataArrays,
|
|
91
|
-
)
|
|
92
|
-
from etptypes.energistics.etp.v12.protocol.data_array.put_data_arrays_response import (
|
|
93
66
|
PutDataArraysResponse,
|
|
94
|
-
)
|
|
95
|
-
from etptypes.energistics.etp.v12.protocol.data_array.put_data_subarrays import (
|
|
96
67
|
PutDataSubarrays,
|
|
97
|
-
)
|
|
98
|
-
from etptypes.energistics.etp.v12.protocol.data_array.put_data_subarrays_response import (
|
|
99
68
|
PutDataSubarraysResponse,
|
|
100
|
-
)
|
|
101
|
-
from etptypes.energistics.etp.v12.protocol.data_array.put_uninitialized_data_arrays import (
|
|
102
69
|
PutUninitializedDataArrays,
|
|
103
|
-
)
|
|
104
|
-
from etptypes.energistics.etp.v12.protocol.data_array.put_uninitialized_data_arrays_response import (
|
|
105
70
|
PutUninitializedDataArraysResponse,
|
|
106
71
|
)
|
|
107
|
-
from
|
|
72
|
+
from energistics.etp.v12.protocol.dataspace import (
|
|
108
73
|
DeleteDataspaces,
|
|
109
|
-
)
|
|
110
|
-
from etptypes.energistics.etp.v12.protocol.dataspace.delete_dataspaces_response import (
|
|
111
74
|
DeleteDataspacesResponse,
|
|
112
|
-
|
|
113
|
-
from etptypes.energistics.etp.v12.protocol.dataspace.get_dataspaces import GetDataspaces
|
|
114
|
-
from etptypes.energistics.etp.v12.protocol.dataspace.get_dataspaces_response import (
|
|
75
|
+
GetDataspaces,
|
|
115
76
|
GetDataspacesResponse,
|
|
116
|
-
|
|
117
|
-
from etptypes.energistics.etp.v12.protocol.dataspace.put_dataspaces import PutDataspaces
|
|
118
|
-
from etptypes.energistics.etp.v12.protocol.dataspace.put_dataspaces_response import (
|
|
77
|
+
PutDataspaces,
|
|
119
78
|
PutDataspacesResponse,
|
|
120
79
|
)
|
|
121
|
-
from
|
|
122
|
-
|
|
123
|
-
|
|
80
|
+
from energistics.etp.v12.protocol.discovery import (
|
|
81
|
+
GetResources,
|
|
82
|
+
GetResourcesResponse,
|
|
124
83
|
)
|
|
125
|
-
from
|
|
84
|
+
from energistics.etp.v12.protocol.store import (
|
|
85
|
+
DeleteDataObjects,
|
|
126
86
|
DeleteDataObjectsResponse,
|
|
127
|
-
|
|
128
|
-
from etptypes.energistics.etp.v12.protocol.store.get_data_objects import GetDataObjects
|
|
129
|
-
from etptypes.energistics.etp.v12.protocol.store.get_data_objects_response import (
|
|
87
|
+
GetDataObjects,
|
|
130
88
|
GetDataObjectsResponse,
|
|
131
|
-
|
|
132
|
-
from etptypes.energistics.etp.v12.protocol.store.put_data_objects import PutDataObjects
|
|
133
|
-
from etptypes.energistics.etp.v12.protocol.store.put_data_objects_response import (
|
|
89
|
+
PutDataObjects,
|
|
134
90
|
PutDataObjectsResponse,
|
|
135
91
|
)
|
|
136
|
-
from
|
|
92
|
+
from energistics.etp.v12.protocol.transaction import (
|
|
137
93
|
CommitTransaction,
|
|
138
|
-
)
|
|
139
|
-
from etptypes.energistics.etp.v12.protocol.transaction.rollback_transaction import (
|
|
140
94
|
RollbackTransaction,
|
|
141
|
-
)
|
|
142
|
-
from etptypes.energistics.etp.v12.protocol.transaction.start_transaction import (
|
|
143
95
|
StartTransaction,
|
|
96
|
+
StartTransactionResponse,
|
|
144
97
|
)
|
|
145
|
-
from pydantic import SecretStr
|
|
146
|
-
from xtgeo import RegularSurface
|
|
147
|
-
|
|
148
|
-
import resqml_objects.v201 as ro
|
|
149
98
|
from pyetp import utils_arrays, utils_xml
|
|
150
99
|
from pyetp._version import version
|
|
151
100
|
from pyetp.config import SETTINGS
|
|
101
|
+
from pyetp.errors import ETPTransactionFailure
|
|
152
102
|
from pyetp.uri import DataObjectURI, DataspaceURI
|
|
153
103
|
from resqml_objects import parse_resqml_v201_object, serialize_resqml_v201_object
|
|
154
104
|
|
|
155
105
|
logger = logging.getLogger(__name__)
|
|
156
106
|
|
|
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
107
|
|
|
192
108
|
class ETPError(Exception):
|
|
193
109
|
def __init__(self, message: str, code: int):
|
|
@@ -258,11 +174,11 @@ class ETPClient(ETPConnection):
|
|
|
258
174
|
# client
|
|
259
175
|
#
|
|
260
176
|
|
|
261
|
-
async def send(self, body: ETPModel):
|
|
177
|
+
async def send(self, body: ETPModel) -> list[ETPModel]:
|
|
262
178
|
correlation_id = await self._send(body)
|
|
263
179
|
return await self._recv(correlation_id)
|
|
264
180
|
|
|
265
|
-
async def _send(self, body: ETPModel):
|
|
181
|
+
async def _send(self, body: ETPModel) -> int:
|
|
266
182
|
msg = Message.get_object_message(body, message_flags=MessageFlags.FINALPART)
|
|
267
183
|
if msg is None:
|
|
268
184
|
raise TypeError(f"{type(body)} not valid etp protocol")
|
|
@@ -281,41 +197,15 @@ class ETPClient(ETPConnection):
|
|
|
281
197
|
|
|
282
198
|
return msg.header.message_id
|
|
283
199
|
|
|
284
|
-
async def _recv(self, correlation_id: int) -> ETPModel:
|
|
200
|
+
async def _recv(self, correlation_id: int) -> list[ETPModel]:
|
|
285
201
|
assert correlation_id in self._recv_events, (
|
|
286
202
|
"Trying to receive a response on non-existing message"
|
|
287
203
|
)
|
|
288
204
|
|
|
289
|
-
def timeout_intervals(etp_timeout):
|
|
290
|
-
# Local function generating progressively longer timeout intervals.
|
|
291
|
-
|
|
292
|
-
# Use the timeout-interval generator from the Python websockets
|
|
293
|
-
# library.
|
|
294
|
-
backoff_generator = websockets.client.backoff(
|
|
295
|
-
initial_delay=5.0, min_delay=5.0, max_delay=20.0
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
# Check if we should never time out.
|
|
299
|
-
if etp_timeout is None:
|
|
300
|
-
# This is an infinite generator, so it should never exit.
|
|
301
|
-
yield from backoff_generator
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
# Generate timeout intervals until we have reached the
|
|
305
|
-
# `etp_timeout`-threshold.
|
|
306
|
-
csum = 0.0
|
|
307
|
-
for d in backoff_generator:
|
|
308
|
-
yield d
|
|
309
|
-
|
|
310
|
-
csum += d
|
|
311
|
-
|
|
312
|
-
if csum >= etp_timeout:
|
|
313
|
-
break
|
|
314
|
-
|
|
315
205
|
for ti in timeout_intervals(self.etp_timeout):
|
|
316
206
|
try:
|
|
317
207
|
# Wait for an event for `ti` seconds.
|
|
318
|
-
async with timeout(ti):
|
|
208
|
+
async with asyncio.timeout(ti):
|
|
319
209
|
await self._recv_events[correlation_id].wait()
|
|
320
210
|
except TimeoutError:
|
|
321
211
|
# Check if the receiver task is still running.
|
|
@@ -323,11 +213,18 @@ class ETPClient(ETPConnection):
|
|
|
323
213
|
# Raise any errors by waiting for the task to finish.
|
|
324
214
|
await self.__recvtask
|
|
325
215
|
|
|
326
|
-
|
|
327
|
-
|
|
216
|
+
# Check that the receiver task stopped due to a
|
|
217
|
+
# (successfully) closed websockets connection.
|
|
218
|
+
try:
|
|
219
|
+
await self.ws.recv()
|
|
220
|
+
except websockets.ConnectionClosedOK:
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
# Terminate client with an error.
|
|
224
|
+
raise ReceiveWorkerExited(
|
|
225
|
+
"Receiver task terminated prematurely due to a closed "
|
|
226
|
+
"websockets connection"
|
|
328
227
|
)
|
|
329
|
-
|
|
330
|
-
raise ReceiveWorkerExited
|
|
331
228
|
else:
|
|
332
229
|
# Break out of for-loop, and start processing message.
|
|
333
230
|
break
|
|
@@ -355,11 +252,7 @@ class ETPClient(ETPConnection):
|
|
|
355
252
|
"Server responded with ETPErrors:", ETPError.from_protos(errors)
|
|
356
253
|
)
|
|
357
254
|
|
|
358
|
-
|
|
359
|
-
logger.warning(f"Recived {len(bodies)} messages, but only expected one")
|
|
360
|
-
|
|
361
|
-
# ok
|
|
362
|
-
return bodies[0]
|
|
255
|
+
return bodies
|
|
363
256
|
|
|
364
257
|
@staticmethod
|
|
365
258
|
def _parse_error_info(bodies: list[ETPModel]) -> list[ErrorInfo]:
|
|
@@ -372,13 +265,15 @@ class ETPClient(ETPConnection):
|
|
|
372
265
|
errors.extend(body.errors.values())
|
|
373
266
|
return errors
|
|
374
267
|
|
|
375
|
-
async def __aexit__(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
268
|
+
async def __aexit__(
|
|
269
|
+
self,
|
|
270
|
+
exc_type: T.Type[BaseException] | None,
|
|
271
|
+
exc_value: BaseException | None,
|
|
272
|
+
traceback: TracebackType | None,
|
|
273
|
+
) -> None:
|
|
379
274
|
close_session_sent = False
|
|
380
275
|
try:
|
|
381
|
-
await self._send(CloseSession(reason=
|
|
276
|
+
await self._send(CloseSession(reason="Client exiting"))
|
|
382
277
|
close_session_sent = True
|
|
383
278
|
except websockets.ConnectionClosed:
|
|
384
279
|
logger.error(
|
|
@@ -419,7 +314,7 @@ class ETPClient(ETPConnection):
|
|
|
419
314
|
# In some cases the server does not drop the connection after we
|
|
420
315
|
# have sent the `CloseSession`-message. We therefore add a timeout
|
|
421
316
|
# to the reading of possibly lost messages.
|
|
422
|
-
async with timeout(self.etp_timeout or 10):
|
|
317
|
+
async with asyncio.timeout(self.etp_timeout or 10):
|
|
423
318
|
async for msg in self.ws:
|
|
424
319
|
counter += 1
|
|
425
320
|
except websockets.ConnectionClosed:
|
|
@@ -428,7 +323,7 @@ class ETPClient(ETPConnection):
|
|
|
428
323
|
pass
|
|
429
324
|
except TimeoutError:
|
|
430
325
|
if close_session_sent:
|
|
431
|
-
logger.
|
|
326
|
+
logger.warning(
|
|
432
327
|
"Websockets connection was not closed within "
|
|
433
328
|
f"{self.etp_timeout or 10} seconds after the "
|
|
434
329
|
"`CloseSession`-message was sent"
|
|
@@ -442,17 +337,30 @@ class ETPClient(ETPConnection):
|
|
|
442
337
|
|
|
443
338
|
logger.debug("Client closed")
|
|
444
339
|
|
|
340
|
+
async def close(self) -> None:
|
|
341
|
+
"""Closing method that tears down the ETP-connection via the
|
|
342
|
+
`ETPClient.__aexit__`-method, and closes the websockets connection.
|
|
343
|
+
This method should _only_ be used if the user has set up a connection
|
|
344
|
+
via `etp_client = await connect(...)` or `etp_client = await
|
|
345
|
+
etp_connect(...)` and will handle the closing of the connection
|
|
346
|
+
manually.
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
await self.__aexit__(None, None, None)
|
|
350
|
+
# The websockets connection should be closed from the ETP-server once
|
|
351
|
+
# it has received a `CloseSession`-message. However, calling close on
|
|
352
|
+
# the websockets connection does not do anything if it is already
|
|
353
|
+
# closed.
|
|
354
|
+
await self.ws.close()
|
|
355
|
+
|
|
445
356
|
async def __recv(self):
|
|
446
357
|
logger.debug("Starting receiver loop")
|
|
447
358
|
|
|
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()
|
|
359
|
+
# Using `async for` makes the receiver task exit without errors on a
|
|
360
|
+
# `websockets.exceptions.ConnectionClosedOK`-exception. This ensures a
|
|
361
|
+
# smoother clean-up in case the main-task errors resulting in a closed
|
|
362
|
+
# websockets connection down the line.
|
|
363
|
+
async for msg_data in self.ws:
|
|
456
364
|
msg = Message.decode_binary_message(
|
|
457
365
|
T.cast(bytes, msg_data), ETPClient.generic_transition_table
|
|
458
366
|
)
|
|
@@ -468,11 +376,9 @@ class ETPClient(ETPConnection):
|
|
|
468
376
|
# set response on send event
|
|
469
377
|
self._recv_events[msg.header.correlation_id].set()
|
|
470
378
|
|
|
471
|
-
|
|
472
|
-
# session related
|
|
473
|
-
#
|
|
379
|
+
logger.info("Websockets connection closed and receiver task stopped")
|
|
474
380
|
|
|
475
|
-
async def __aenter__(self) -> Self:
|
|
381
|
+
async def __aenter__(self) -> T.Self:
|
|
476
382
|
return await self.request_session()
|
|
477
383
|
|
|
478
384
|
async def request_session(self):
|
|
@@ -488,7 +394,7 @@ class ETPClient(ETPConnection):
|
|
|
488
394
|
|
|
489
395
|
return "store"
|
|
490
396
|
|
|
491
|
-
|
|
397
|
+
msgs = await self.send(
|
|
492
398
|
RequestSession(
|
|
493
399
|
applicationName=self.application_name,
|
|
494
400
|
applicationVersion=self.application_version,
|
|
@@ -512,6 +418,10 @@ class ETPClient(ETPConnection):
|
|
|
512
418
|
),
|
|
513
419
|
)
|
|
514
420
|
)
|
|
421
|
+
|
|
422
|
+
assert len(msgs) == 1
|
|
423
|
+
msg = msgs[0]
|
|
424
|
+
|
|
515
425
|
assert msg and isinstance(msg, OpenSession)
|
|
516
426
|
|
|
517
427
|
self.is_connected = True
|
|
@@ -525,12 +435,15 @@ class ETPClient(ETPConnection):
|
|
|
525
435
|
async def authorize(
|
|
526
436
|
self, authorization: str, supplemental_authorization: T.Mapping[str, str] = {}
|
|
527
437
|
):
|
|
528
|
-
|
|
438
|
+
msgs = await self.send(
|
|
529
439
|
Authorize(
|
|
530
440
|
authorization=authorization,
|
|
531
441
|
supplementalAuthorization=supplemental_authorization,
|
|
532
442
|
)
|
|
533
443
|
)
|
|
444
|
+
assert len(msgs) == 1
|
|
445
|
+
msg = msgs[0]
|
|
446
|
+
|
|
534
447
|
assert msg and isinstance(msg, AuthorizeResponse)
|
|
535
448
|
|
|
536
449
|
return msg
|
|
@@ -562,8 +475,10 @@ class ETPClient(ETPConnection):
|
|
|
562
475
|
raise Exception("Max one / in dataspace name")
|
|
563
476
|
return DataspaceURI.from_name(ds)
|
|
564
477
|
|
|
565
|
-
def list_objects(
|
|
566
|
-
|
|
478
|
+
async def list_objects(
|
|
479
|
+
self, dataspace_uri: DataspaceURI | str, depth: int = 1
|
|
480
|
+
) -> GetResourcesResponse:
|
|
481
|
+
responses = await self.send(
|
|
567
482
|
GetResources(
|
|
568
483
|
scope=ContextScopeKind.TARGETS_OR_SELF,
|
|
569
484
|
context=ContextInfo(
|
|
@@ -574,6 +489,8 @@ class ETPClient(ETPConnection):
|
|
|
574
489
|
),
|
|
575
490
|
)
|
|
576
491
|
)
|
|
492
|
+
assert len(responses) == 1
|
|
493
|
+
return responses[0]
|
|
577
494
|
|
|
578
495
|
#
|
|
579
496
|
# dataspace
|
|
@@ -582,10 +499,17 @@ class ETPClient(ETPConnection):
|
|
|
582
499
|
async def get_dataspaces(
|
|
583
500
|
self, store_last_write_filter: int = None
|
|
584
501
|
) -> GetDataspacesResponse:
|
|
585
|
-
|
|
502
|
+
responses = await self.send(
|
|
586
503
|
GetDataspaces(store_last_write_filter=store_last_write_filter)
|
|
587
504
|
)
|
|
588
505
|
|
|
506
|
+
assert all(
|
|
507
|
+
[isinstance(response, GetDataspacesResponse) for response in responses]
|
|
508
|
+
), "Expected GetDataspacesResponse"
|
|
509
|
+
assert len(responses) == 1
|
|
510
|
+
|
|
511
|
+
return responses[0]
|
|
512
|
+
|
|
589
513
|
async def put_dataspaces(
|
|
590
514
|
self,
|
|
591
515
|
legaltags: list[str],
|
|
@@ -593,13 +517,13 @@ class ETPClient(ETPConnection):
|
|
|
593
517
|
owners: list[str],
|
|
594
518
|
viewers: list[str],
|
|
595
519
|
*dataspace_uris: DataspaceURI,
|
|
596
|
-
):
|
|
520
|
+
) -> dict[str, str]:
|
|
597
521
|
_uris = list(map(DataspaceURI.from_any, dataspace_uris))
|
|
598
522
|
for i in _uris:
|
|
599
523
|
if i.raw_uri.count("/") > 4: # includes the 3 eml
|
|
600
524
|
raise Exception("Max one / in dataspace name")
|
|
601
525
|
time = self.timestamp
|
|
602
|
-
|
|
526
|
+
responses = await self.send(
|
|
603
527
|
PutDataspaces(
|
|
604
528
|
dataspaces={
|
|
605
529
|
d.raw_uri: Dataspace(
|
|
@@ -622,15 +546,19 @@ class ETPClient(ETPConnection):
|
|
|
622
546
|
}
|
|
623
547
|
)
|
|
624
548
|
)
|
|
625
|
-
assert
|
|
626
|
-
|
|
627
|
-
)
|
|
549
|
+
assert all(
|
|
550
|
+
[isinstance(response, PutDataspacesResponse) for response in responses]
|
|
551
|
+
), "Expected PutDataspacesResponse"
|
|
552
|
+
|
|
553
|
+
successes = {}
|
|
554
|
+
for response in responses:
|
|
555
|
+
successes = {**successes, **response.success}
|
|
628
556
|
|
|
629
|
-
assert len(
|
|
630
|
-
f"expected {len(dataspace_uris)}
|
|
557
|
+
assert len(successes) == len(dataspace_uris), (
|
|
558
|
+
f"expected {len(dataspace_uris)} successes"
|
|
631
559
|
)
|
|
632
560
|
|
|
633
|
-
return
|
|
561
|
+
return successes
|
|
634
562
|
|
|
635
563
|
async def put_dataspaces_no_raise(
|
|
636
564
|
self,
|
|
@@ -639,22 +567,30 @@ class ETPClient(ETPConnection):
|
|
|
639
567
|
owners: list[str],
|
|
640
568
|
viewers: list[str],
|
|
641
569
|
*dataspace_uris: DataspaceURI,
|
|
642
|
-
):
|
|
570
|
+
) -> dict[str, str]:
|
|
643
571
|
try:
|
|
644
572
|
return await self.put_dataspaces(
|
|
645
573
|
legaltags, otherRelevantDataCountries, owners, viewers, *dataspace_uris
|
|
646
574
|
)
|
|
647
575
|
except ETPError:
|
|
648
|
-
|
|
576
|
+
return {}
|
|
649
577
|
|
|
650
|
-
async def delete_dataspaces(self, *dataspace_uris: DataspaceURI):
|
|
578
|
+
async def delete_dataspaces(self, *dataspace_uris: DataspaceURI) -> dict[str, str]:
|
|
651
579
|
_uris = list(map(str, dataspace_uris))
|
|
652
580
|
|
|
653
|
-
|
|
654
|
-
assert
|
|
655
|
-
|
|
581
|
+
responses = await self.send(DeleteDataspaces(uris=dict(zip(_uris, _uris))))
|
|
582
|
+
assert all(
|
|
583
|
+
[isinstance(response, DeleteDataspacesResponse) for response in responses]
|
|
584
|
+
), "Expected DeleteDataspacesResponse"
|
|
585
|
+
|
|
586
|
+
successes = {}
|
|
587
|
+
for response in responses:
|
|
588
|
+
successes = {**successes, **response.success}
|
|
589
|
+
|
|
590
|
+
assert len(successes) == len(dataspace_uris), (
|
|
591
|
+
f"expected {len(dataspace_uris)} successes"
|
|
656
592
|
)
|
|
657
|
-
return
|
|
593
|
+
return successes
|
|
658
594
|
|
|
659
595
|
async def get_data_objects(self, *uris: T.Union[DataObjectURI, str]):
|
|
660
596
|
tasks = []
|
|
@@ -662,7 +598,8 @@ class ETPClient(ETPConnection):
|
|
|
662
598
|
task = self.send(GetDataObjects(uris={str(uri): str(uri)}))
|
|
663
599
|
tasks.append(task)
|
|
664
600
|
|
|
665
|
-
|
|
601
|
+
task_responses = await asyncio.gather(*tasks)
|
|
602
|
+
responses = [r for tr in task_responses for r in tr]
|
|
666
603
|
assert len(responses) == len(uris)
|
|
667
604
|
|
|
668
605
|
data_objects = []
|
|
@@ -695,7 +632,8 @@ class ETPClient(ETPConnection):
|
|
|
695
632
|
)
|
|
696
633
|
tasks.append(task)
|
|
697
634
|
|
|
698
|
-
|
|
635
|
+
task_responses = await asyncio.gather(*tasks)
|
|
636
|
+
responses = [r for tr in task_responses for r in tr]
|
|
699
637
|
|
|
700
638
|
errors = []
|
|
701
639
|
for response in responses:
|
|
@@ -757,12 +695,16 @@ class ETPClient(ETPConnection):
|
|
|
757
695
|
):
|
|
758
696
|
_uris = list(map(str, uris))
|
|
759
697
|
|
|
760
|
-
|
|
698
|
+
responses = await self.send(
|
|
761
699
|
DeleteDataObjects(
|
|
762
700
|
uris=dict(zip(_uris, _uris)),
|
|
763
701
|
prune_contained_objects=prune_contained_objects,
|
|
764
702
|
)
|
|
765
703
|
)
|
|
704
|
+
|
|
705
|
+
assert len(responses) == 1
|
|
706
|
+
response = responses[0]
|
|
707
|
+
|
|
766
708
|
assert isinstance(response, DeleteDataObjectsResponse), (
|
|
767
709
|
"Expected DeleteDataObjectsResponse"
|
|
768
710
|
)
|
|
@@ -770,23 +712,34 @@ class ETPClient(ETPConnection):
|
|
|
770
712
|
return response.deleted_uris
|
|
771
713
|
|
|
772
714
|
async def start_transaction(
|
|
773
|
-
self, dataspace_uri: DataspaceURI, read_only: bool = True
|
|
715
|
+
self, dataspace_uri: DataspaceURI | str, read_only: bool = True
|
|
774
716
|
) -> Uuid:
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
)
|
|
717
|
+
dataspace_uri = str(DataspaceURI.from_any(dataspace_uri))
|
|
718
|
+
responses = await self.send(
|
|
719
|
+
StartTransaction(read_only=read_only, dataspace_uris=[dataspace_uri])
|
|
779
720
|
)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
721
|
+
assert all(
|
|
722
|
+
[isinstance(response, StartTransactionResponse) for response in responses]
|
|
723
|
+
), "Expected StartTransactionResponse"
|
|
724
|
+
|
|
725
|
+
assert len(responses) == 1
|
|
726
|
+
response = responses[0]
|
|
727
|
+
|
|
728
|
+
if not response.successful:
|
|
729
|
+
raise ETPTransactionFailure(f"Failed starting transaction {dataspace_uri}")
|
|
730
|
+
|
|
731
|
+
return response.transaction_uuid
|
|
784
732
|
|
|
785
733
|
async def commit_transaction(self, transaction_uuid: Uuid):
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
734
|
+
responses = await self.send(
|
|
735
|
+
CommitTransaction(transaction_uuid=transaction_uuid)
|
|
736
|
+
)
|
|
737
|
+
assert len(responses) == 1
|
|
738
|
+
response = responses[0]
|
|
739
|
+
|
|
740
|
+
if response.successful is False:
|
|
741
|
+
raise ETPTransactionFailure(response.failure_reason)
|
|
742
|
+
return response
|
|
790
743
|
|
|
791
744
|
async def rollback_transaction(self, transaction_id: Uuid):
|
|
792
745
|
return await self.send(RollbackTransaction(transactionUuid=transaction_id))
|
|
@@ -811,20 +764,19 @@ class ETPClient(ETPConnection):
|
|
|
811
764
|
"obj must be Grid2DRepresentation"
|
|
812
765
|
)
|
|
813
766
|
sgeo = gri.grid2d_patch.geometry.points.supporting_geometry # type: ignore
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
)
|
|
767
|
+
assert isinstance(gri.grid2d_patch.geometry.points, ro.Point3dZValueArray), (
|
|
768
|
+
"Points must be Point3dZValueArray"
|
|
769
|
+
)
|
|
770
|
+
assert isinstance(
|
|
771
|
+
gri.grid2d_patch.geometry.points.zvalues, ro.DoubleHdf5Array
|
|
772
|
+
), "Values must be DoubleHdf5Array"
|
|
773
|
+
assert isinstance(
|
|
774
|
+
gri.grid2d_patch.geometry.points.supporting_geometry,
|
|
775
|
+
ro.Point3dLatticeArray,
|
|
776
|
+
), "Points support_geo must be Point3dLatticeArray"
|
|
777
|
+
assert isinstance(sgeo, ro.Point3dLatticeArray), (
|
|
778
|
+
"supporting_geometry must be Point3dLatticeArray"
|
|
779
|
+
)
|
|
828
780
|
assert isinstance(
|
|
829
781
|
gri.grid2d_patch.geometry.points.zvalues.values, ro.Hdf5Dataset
|
|
830
782
|
), "Values must be Hdf5Dataset"
|
|
@@ -893,9 +845,12 @@ class ETPClient(ETPConnection):
|
|
|
893
845
|
#
|
|
894
846
|
|
|
895
847
|
async def get_array_metadata(self, *uids: DataArrayIdentifier):
|
|
896
|
-
|
|
848
|
+
responses = await self.send(
|
|
897
849
|
GetDataArrayMetadata(dataArrays={i.path_in_resource: i for i in uids})
|
|
898
850
|
)
|
|
851
|
+
assert len(responses) == 1
|
|
852
|
+
response = responses[0]
|
|
853
|
+
|
|
899
854
|
assert isinstance(response, GetDataArrayMetadataResponse)
|
|
900
855
|
|
|
901
856
|
if len(response.array_metadata) != len(uids):
|
|
@@ -915,9 +870,13 @@ class ETPClient(ETPConnection):
|
|
|
915
870
|
):
|
|
916
871
|
return await self._get_array_chunked(uid)
|
|
917
872
|
|
|
918
|
-
|
|
873
|
+
responses = await self.send(
|
|
919
874
|
GetDataArrays(dataArrays={uid.path_in_resource: uid})
|
|
920
875
|
)
|
|
876
|
+
|
|
877
|
+
assert len(responses) == 1
|
|
878
|
+
response = responses[0]
|
|
879
|
+
|
|
921
880
|
assert isinstance(response, GetDataArraysResponse), (
|
|
922
881
|
"Expected GetDataArraysResponse"
|
|
923
882
|
)
|
|
@@ -936,10 +895,13 @@ class ETPClient(ETPConnection):
|
|
|
936
895
|
path_in_resource=path_in_resource,
|
|
937
896
|
)
|
|
938
897
|
|
|
939
|
-
|
|
898
|
+
responses = await self.send(
|
|
940
899
|
GetDataArrayMetadata(data_arrays={dai.path_in_resource: dai}),
|
|
941
900
|
)
|
|
942
901
|
|
|
902
|
+
assert len(responses) == 1
|
|
903
|
+
response = responses[0]
|
|
904
|
+
|
|
943
905
|
self.assert_response(response, GetDataArrayMetadataResponse)
|
|
944
906
|
assert (
|
|
945
907
|
len(response.array_metadata) == 1
|
|
@@ -1002,7 +964,12 @@ class ETPClient(ETPConnection):
|
|
|
1002
964
|
)
|
|
1003
965
|
tasks.append(task)
|
|
1004
966
|
|
|
1005
|
-
|
|
967
|
+
task_responses = await asyncio.gather(*tasks)
|
|
968
|
+
responses = [
|
|
969
|
+
response
|
|
970
|
+
for task_response in task_responses
|
|
971
|
+
for response in task_response
|
|
972
|
+
]
|
|
1006
973
|
|
|
1007
974
|
data_blocks = []
|
|
1008
975
|
for i, response in enumerate(responses):
|
|
@@ -1037,10 +1004,13 @@ class ETPClient(ETPConnection):
|
|
|
1037
1004
|
return data
|
|
1038
1005
|
|
|
1039
1006
|
# Download the full array in one go.
|
|
1040
|
-
|
|
1007
|
+
responses = await self.send(
|
|
1041
1008
|
GetDataArrays(data_arrays={dai.path_in_resource: dai}),
|
|
1042
1009
|
)
|
|
1043
1010
|
|
|
1011
|
+
assert len(responses) == 1
|
|
1012
|
+
response = responses[0]
|
|
1013
|
+
|
|
1044
1014
|
self.assert_response(response, GetDataArraysResponse)
|
|
1045
1015
|
assert (
|
|
1046
1016
|
len(response.data_arrays) == 1
|
|
@@ -1072,7 +1042,7 @@ class ETPClient(ETPConnection):
|
|
|
1072
1042
|
now = self.timestamp
|
|
1073
1043
|
|
|
1074
1044
|
# Allocate space on server for the array.
|
|
1075
|
-
|
|
1045
|
+
responses = await self.send(
|
|
1076
1046
|
PutUninitializedDataArrays(
|
|
1077
1047
|
data_arrays={
|
|
1078
1048
|
dai.path_in_resource: PutUninitializedDataArrayType(
|
|
@@ -1089,6 +1059,9 @@ class ETPClient(ETPConnection):
|
|
|
1089
1059
|
),
|
|
1090
1060
|
)
|
|
1091
1061
|
|
|
1062
|
+
assert len(responses) == 1
|
|
1063
|
+
response = responses[0]
|
|
1064
|
+
|
|
1092
1065
|
self.assert_response(response, PutUninitializedDataArraysResponse)
|
|
1093
1066
|
assert len(response.success) == 1 and dai.path_in_resource in response.success
|
|
1094
1067
|
|
|
@@ -1138,7 +1111,14 @@ class ETPClient(ETPConnection):
|
|
|
1138
1111
|
tasks.append(task)
|
|
1139
1112
|
|
|
1140
1113
|
# Upload all blocks.
|
|
1141
|
-
|
|
1114
|
+
task_responses = await asyncio.gather(*tasks)
|
|
1115
|
+
|
|
1116
|
+
# Flatten list of responses.
|
|
1117
|
+
responses = [
|
|
1118
|
+
response
|
|
1119
|
+
for task_response in task_responses
|
|
1120
|
+
for response in task_response
|
|
1121
|
+
]
|
|
1142
1122
|
|
|
1143
1123
|
# Check for successful responses.
|
|
1144
1124
|
for response in responses:
|
|
@@ -1155,7 +1135,7 @@ class ETPClient(ETPConnection):
|
|
|
1155
1135
|
etp_array_data = utils_arrays.get_etp_data_array_from_numpy(data)
|
|
1156
1136
|
|
|
1157
1137
|
# Pass entire array in one message.
|
|
1158
|
-
|
|
1138
|
+
responses = await self.send(
|
|
1159
1139
|
PutDataArrays(
|
|
1160
1140
|
data_arrays={
|
|
1161
1141
|
dai.path_in_resource: PutDataArraysType(
|
|
@@ -1166,6 +1146,9 @@ class ETPClient(ETPConnection):
|
|
|
1166
1146
|
)
|
|
1167
1147
|
)
|
|
1168
1148
|
|
|
1149
|
+
assert len(responses) == 1
|
|
1150
|
+
response = responses[0]
|
|
1151
|
+
|
|
1169
1152
|
self.assert_response(response, PutDataArraysResponse)
|
|
1170
1153
|
assert len(response.success) == 1 and dai.path_in_resource in response.success
|
|
1171
1154
|
|
|
@@ -1187,7 +1170,7 @@ class ETPClient(ETPConnection):
|
|
|
1187
1170
|
if data.nbytes > self.max_array_size:
|
|
1188
1171
|
return await self._put_array_chunked(uid, data)
|
|
1189
1172
|
|
|
1190
|
-
|
|
1173
|
+
responses = await self.send(
|
|
1191
1174
|
PutDataArrays(
|
|
1192
1175
|
data_arrays={
|
|
1193
1176
|
uid.path_in_resource: PutDataArraysType(
|
|
@@ -1198,6 +1181,9 @@ class ETPClient(ETPConnection):
|
|
|
1198
1181
|
)
|
|
1199
1182
|
)
|
|
1200
1183
|
|
|
1184
|
+
assert len(responses) == 1
|
|
1185
|
+
response = responses[0]
|
|
1186
|
+
|
|
1201
1187
|
assert isinstance(response, PutDataArraysResponse), (
|
|
1202
1188
|
"Expected PutDataArraysResponse"
|
|
1203
1189
|
)
|
|
@@ -1221,9 +1207,13 @@ class ETPClient(ETPConnection):
|
|
|
1221
1207
|
starts=starts.tolist(),
|
|
1222
1208
|
counts=counts.tolist(),
|
|
1223
1209
|
)
|
|
1224
|
-
|
|
1210
|
+
responses = await self.send(
|
|
1225
1211
|
GetDataSubarrays(dataSubarrays={uid.path_in_resource: payload})
|
|
1226
1212
|
)
|
|
1213
|
+
|
|
1214
|
+
assert len(responses) == 1
|
|
1215
|
+
response = responses[0]
|
|
1216
|
+
|
|
1227
1217
|
assert isinstance(response, GetDataSubarraysResponse), (
|
|
1228
1218
|
"Expected GetDataSubarraysResponse"
|
|
1229
1219
|
)
|
|
@@ -1262,9 +1252,13 @@ class ETPClient(ETPConnection):
|
|
|
1262
1252
|
f"{dataarray.data.item.__class__.__name__}"
|
|
1263
1253
|
)
|
|
1264
1254
|
|
|
1265
|
-
|
|
1255
|
+
responses = await self.send(
|
|
1266
1256
|
PutDataSubarrays(dataSubarrays={uid.path_in_resource: payload})
|
|
1267
1257
|
)
|
|
1258
|
+
|
|
1259
|
+
assert len(responses) == 1
|
|
1260
|
+
response = responses[0]
|
|
1261
|
+
|
|
1268
1262
|
assert isinstance(response, PutDataSubarraysResponse), (
|
|
1269
1263
|
"Expected PutDataSubarraysResponse"
|
|
1270
1264
|
)
|
|
@@ -1386,9 +1380,13 @@ class ETPClient(ETPConnection):
|
|
|
1386
1380
|
)
|
|
1387
1381
|
),
|
|
1388
1382
|
)
|
|
1389
|
-
|
|
1383
|
+
responses = await self.send(
|
|
1390
1384
|
PutUninitializedDataArrays(dataArrays={uid.path_in_resource: payload})
|
|
1391
1385
|
)
|
|
1386
|
+
|
|
1387
|
+
assert len(responses) == 1
|
|
1388
|
+
response = responses[0]
|
|
1389
|
+
|
|
1392
1390
|
assert isinstance(response, PutUninitializedDataArraysResponse), (
|
|
1393
1391
|
"Expected PutUninitializedDataArraysResponse"
|
|
1394
1392
|
)
|
|
@@ -1407,6 +1405,7 @@ class connect:
|
|
|
1407
1405
|
# ... = await connect(...)
|
|
1408
1406
|
|
|
1409
1407
|
def __await__(self):
|
|
1408
|
+
# The caller is response for calling `close()` on the client.
|
|
1410
1409
|
return self.__aenter__().__await__()
|
|
1411
1410
|
|
|
1412
1411
|
# async with connect(...) as ...:
|
|
@@ -1420,7 +1419,7 @@ class connect:
|
|
|
1420
1419
|
if self.data_partition is not None:
|
|
1421
1420
|
headers["data-partition-id"] = self.data_partition
|
|
1422
1421
|
|
|
1423
|
-
|
|
1422
|
+
ws = await websockets.connect(
|
|
1424
1423
|
self.server_url,
|
|
1425
1424
|
subprotocols=[ETPClient.SUB_PROTOCOL], # type: ignore
|
|
1426
1425
|
additional_headers=headers,
|
|
@@ -1430,7 +1429,7 @@ class connect:
|
|
|
1430
1429
|
)
|
|
1431
1430
|
|
|
1432
1431
|
self.client = ETPClient(
|
|
1433
|
-
|
|
1432
|
+
ws=ws,
|
|
1434
1433
|
etp_timeout=self.timeout,
|
|
1435
1434
|
max_message_size=SETTINGS.MaxWebSocketMessagePayloadSize,
|
|
1436
1435
|
application_name=SETTINGS.application_name,
|
|
@@ -1448,67 +1447,188 @@ class connect:
|
|
|
1448
1447
|
|
|
1449
1448
|
# exit the async context manager
|
|
1450
1449
|
async def __aexit__(self, exc_type, exc: Exception, tb: TracebackType):
|
|
1450
|
+
# The `ETPClient.close`-method also closes the websockets connection.
|
|
1451
1451
|
await self.client.close()
|
|
1452
|
-
await self.ws.close()
|
|
1453
1452
|
|
|
1454
1453
|
|
|
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
|
-
|
|
1454
|
+
class etp_connect:
|
|
1455
|
+
"""
|
|
1456
|
+
Connect to an ETP server via websockets.
|
|
1457
|
+
|
|
1458
|
+
This class can act as:
|
|
1459
|
+
|
|
1460
|
+
1. A context manager handling setup and tear-down of the connection.
|
|
1461
|
+
2. An asynchronous iterator which can be used to persistently retry to
|
|
1462
|
+
connect if the websockets connection drops.
|
|
1463
|
+
3. An awaitable connection that must be manually closed by the user.
|
|
1464
|
+
|
|
1465
|
+
See below for examples of all three cases.
|
|
1466
|
+
|
|
1467
|
+
Parameters
|
|
1468
|
+
----------
|
|
1469
|
+
uri: str
|
|
1470
|
+
The uri to the ETP server. This should be the uri to a websockets
|
|
1471
|
+
endpoint.
|
|
1472
|
+
data_partition_id: str | None
|
|
1473
|
+
The data partition id used when connecting to the OSDU open-etp-server
|
|
1474
|
+
in multi-partition mode. Default is `None`.
|
|
1475
|
+
authorization: str | SecretStr | None
|
|
1476
|
+
Bearer token used for authenticating to the ETP server. This token
|
|
1477
|
+
should be on the form `"Bearer 1234..."`. Default is `None`.
|
|
1478
|
+
etp_timeout: float | None
|
|
1479
|
+
The timeout in seconds for when to stop waiting for a message from the
|
|
1480
|
+
ETP server. Setting it to `None` will persist the connection
|
|
1481
|
+
indefinetly. Default is `None`.
|
|
1482
|
+
max_message_size: float
|
|
1483
|
+
The maximum number of bytes for a single websockets message. Default is
|
|
1484
|
+
`2**20` corresponding to `1` MiB.
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
Examples
|
|
1488
|
+
--------
|
|
1489
|
+
An example of connecting to the ETP server using :func:`etp_connect` as a
|
|
1490
|
+
context manager is:
|
|
1491
|
+
|
|
1492
|
+
async with etp_connect(...) as etp_client:
|
|
1493
|
+
...
|
|
1494
|
+
|
|
1495
|
+
In this case the closing message and the websockets connection is closed
|
|
1496
|
+
once the program exits the context manager.
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
To persist a connection if the websockets connection is dropped (for any
|
|
1500
|
+
reason), use :func:`etp_connect` as an asynchronous generator, viz.:
|
|
1501
|
+
|
|
1502
|
+
import websockets
|
|
1503
|
+
|
|
1504
|
+
async for etp_client in etp_connect(...):
|
|
1505
|
+
try:
|
|
1506
|
+
...
|
|
1507
|
+
except websockets.ConnectionClosed:
|
|
1508
|
+
continue
|
|
1509
|
+
|
|
1510
|
+
# Include `break` to avoid re-running the whole block if the
|
|
1511
|
+
# iteration runs without any errors.
|
|
1512
|
+
break
|
|
1513
|
+
|
|
1514
|
+
Note that in this case the whole program under the `try`-block is re-run
|
|
1515
|
+
from the start if the iteration completes normally, or if the websockets
|
|
1516
|
+
connection is dropped. Therefore, make sure to include a `break` at the end
|
|
1517
|
+
of the `try`-block (as in the example above).
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
The third option is to set up a connection via `await` and then manually
|
|
1521
|
+
close the connection once done:
|
|
1522
|
+
|
|
1523
|
+
etp_client = await etp_connect(...)
|
|
1524
|
+
...
|
|
1525
|
+
await etp_client.close()
|
|
1526
|
+
"""
|
|
1527
|
+
|
|
1528
|
+
def __init__(
|
|
1529
|
+
self,
|
|
1530
|
+
uri: str,
|
|
1531
|
+
data_partition_id: str | None = None,
|
|
1532
|
+
authorization: str | SecretStr | None = None,
|
|
1533
|
+
etp_timeout: float | None = None,
|
|
1534
|
+
max_message_size: float = 2**20,
|
|
1535
|
+
) -> None:
|
|
1536
|
+
self.uri = uri
|
|
1537
|
+
self.data_partition_id = data_partition_id
|
|
1538
|
+
|
|
1539
|
+
if isinstance(authorization, SecretStr):
|
|
1540
|
+
self.authorization = authorization
|
|
1541
|
+
else:
|
|
1542
|
+
self.authorization = SecretStr(authorization)
|
|
1543
|
+
|
|
1544
|
+
self.etp_timeout = etp_timeout
|
|
1545
|
+
self.max_message_size = max_message_size
|
|
1546
|
+
self.subprotocols = ["etp12.energistics.org"]
|
|
1547
|
+
|
|
1548
|
+
def __await__(self) -> ETPClient:
|
|
1549
|
+
# The caller is responsible for calling `close()` on the client.
|
|
1550
|
+
return self.__aenter__().__await__()
|
|
1551
|
+
|
|
1552
|
+
def get_additional_headers(self) -> dict[str, str]:
|
|
1553
|
+
additional_headers = {}
|
|
1554
|
+
|
|
1555
|
+
if self.authorization.get_secret_value() is not None:
|
|
1556
|
+
additional_headers["Authorization"] = self.authorization.get_secret_value()
|
|
1557
|
+
|
|
1558
|
+
if self.data_partition_id is not None:
|
|
1559
|
+
additional_headers["data-partition-id"] = self.data_partition_id
|
|
1560
|
+
|
|
1561
|
+
return additional_headers
|
|
1562
|
+
|
|
1563
|
+
async def __aenter__(self) -> ETPClient:
|
|
1564
|
+
self.stack = contextlib.AsyncExitStack()
|
|
1565
|
+
try:
|
|
1566
|
+
ws = await self.stack.enter_async_context(
|
|
1567
|
+
websockets.connect(
|
|
1568
|
+
uri=self.uri,
|
|
1569
|
+
subprotocols=self.subprotocols,
|
|
1570
|
+
max_size=self.max_message_size,
|
|
1571
|
+
additional_headers=self.get_additional_headers(),
|
|
1572
|
+
)
|
|
1573
|
+
)
|
|
1574
|
+
etp_client = await self.stack.enter_async_context(
|
|
1575
|
+
ETPClient(
|
|
1576
|
+
ws=ws,
|
|
1577
|
+
etp_timeout=self.etp_timeout,
|
|
1578
|
+
max_message_size=self.max_message_size,
|
|
1579
|
+
)
|
|
1580
|
+
)
|
|
1581
|
+
except BaseException:
|
|
1582
|
+
await self.stack.aclose()
|
|
1583
|
+
raise
|
|
1584
|
+
|
|
1585
|
+
return etp_client
|
|
1586
|
+
|
|
1587
|
+
async def __aexit__(
|
|
1588
|
+
self,
|
|
1589
|
+
exc_type: T.Type[BaseException] | None,
|
|
1590
|
+
exc_value: BaseException | None,
|
|
1591
|
+
traceback: TracebackType | None,
|
|
1592
|
+
) -> None:
|
|
1593
|
+
return await self.stack.aclose()
|
|
1594
|
+
|
|
1595
|
+
async def __aiter__(self) -> AsyncGenerator[ETPClient]:
|
|
1596
|
+
async for ws in websockets.connect(
|
|
1597
|
+
uri=self.uri,
|
|
1598
|
+
subprotocols=self.subprotocols,
|
|
1599
|
+
max_size=self.max_message_size,
|
|
1600
|
+
additional_headers=self.get_additional_headers(),
|
|
1601
|
+
):
|
|
1602
|
+
async with ETPClient(
|
|
1603
|
+
ws=ws,
|
|
1604
|
+
etp_timeout=self.etp_timeout,
|
|
1605
|
+
max_message_size=self.max_message_size,
|
|
1606
|
+
) as etp_client:
|
|
1607
|
+
yield etp_client
|
|
1608
|
+
|
|
1609
|
+
|
|
1610
|
+
def timeout_intervals(total_timeout: float) -> Generator[float]:
|
|
1611
|
+
# Local function generating progressively longer timeout intervals.
|
|
1612
|
+
|
|
1613
|
+
# Use the timeout-interval generator from the Python websockets
|
|
1614
|
+
# library.
|
|
1615
|
+
backoff_generator = websockets.client.backoff(
|
|
1616
|
+
initial_delay=5.0, min_delay=5.0, max_delay=20.0
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
# Check if we should never time out.
|
|
1620
|
+
if total_timeout is None:
|
|
1621
|
+
# This is an infinite generator, so it should never exit.
|
|
1622
|
+
yield from backoff_generator
|
|
1623
|
+
return
|
|
1624
|
+
|
|
1625
|
+
# Generate timeout intervals until we have reached the
|
|
1626
|
+
# `total_timeout`-threshold.
|
|
1627
|
+
csum = 0.0
|
|
1628
|
+
for d in backoff_generator:
|
|
1629
|
+
yield d
|
|
1630
|
+
|
|
1631
|
+
csum += d
|
|
1632
|
+
|
|
1633
|
+
if csum >= total_timeout:
|
|
1634
|
+
break
|