wherobots-python-dbapi 0.26.0__py3-none-any.whl → 0.27.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.
- wherobots/db/driver.py +47 -5
- {wherobots_python_dbapi-0.26.0.dist-info → wherobots_python_dbapi-0.27.0.dist-info}/METADATA +7 -1
- {wherobots_python_dbapi-0.26.0.dist-info → wherobots_python_dbapi-0.27.0.dist-info}/RECORD +5 -5
- {wherobots_python_dbapi-0.26.0.dist-info → wherobots_python_dbapi-0.27.0.dist-info}/WHEEL +0 -0
- {wherobots_python_dbapi-0.26.0.dist-info → wherobots_python_dbapi-0.27.0.dist-info}/licenses/LICENSE +0 -0
wherobots/db/driver.py
CHANGED
|
@@ -11,8 +11,10 @@ from packaging.version import Version
|
|
|
11
11
|
import platform
|
|
12
12
|
import requests
|
|
13
13
|
import tenacity
|
|
14
|
+
import threading
|
|
14
15
|
from typing import Final, Union, Dict
|
|
15
16
|
import urllib.parse
|
|
17
|
+
import websockets.exceptions
|
|
16
18
|
import websockets.sync.client
|
|
17
19
|
import certifi
|
|
18
20
|
|
|
@@ -50,6 +52,9 @@ paramstyle: Final[str] = PARAM_STYLE
|
|
|
50
52
|
# This follows the industry-standard set used by urllib3.util.Retry's status_forcelist.
|
|
51
53
|
TRANSIENT_HTTP_STATUS_CODES = {429, 502, 503, 504}
|
|
52
54
|
|
|
55
|
+
# Default timeout for individual HTTP requests (connect + read), in seconds.
|
|
56
|
+
DEFAULT_HTTP_TIMEOUT = 30
|
|
57
|
+
|
|
53
58
|
|
|
54
59
|
def gen_user_agent_header():
|
|
55
60
|
try:
|
|
@@ -78,6 +83,7 @@ def connect(
|
|
|
78
83
|
results_format: Union[ResultsFormat, None] = None,
|
|
79
84
|
data_compression: Union[DataCompression, None] = None,
|
|
80
85
|
geometry_representation: Union[GeometryRepresentation, None] = None,
|
|
86
|
+
cancel_event: Union[threading.Event, None] = None,
|
|
81
87
|
) -> Connection:
|
|
82
88
|
if not token and not api_key:
|
|
83
89
|
raise ValueError("At least one of `token` or `api_key` is required")
|
|
@@ -108,6 +114,8 @@ def connect(
|
|
|
108
114
|
if not host.startswith("http:"):
|
|
109
115
|
host = f"https://{host}"
|
|
110
116
|
|
|
117
|
+
_check_cancelled(cancel_event)
|
|
118
|
+
|
|
111
119
|
try:
|
|
112
120
|
resp = requests.post(
|
|
113
121
|
url=f"{host}/sql/session",
|
|
@@ -119,6 +127,7 @@ def connect(
|
|
|
119
127
|
"sessionType": session_type.value,
|
|
120
128
|
},
|
|
121
129
|
headers=headers,
|
|
130
|
+
timeout=DEFAULT_HTTP_TIMEOUT,
|
|
122
131
|
)
|
|
123
132
|
resp.raise_for_status()
|
|
124
133
|
except requests.HTTPError as e:
|
|
@@ -148,10 +157,12 @@ def connect(
|
|
|
148
157
|
)
|
|
149
158
|
| tenacity.retry_if_exception_type(tenacity.TryAgain)
|
|
150
159
|
),
|
|
160
|
+
before_sleep=lambda _: _check_cancelled(cancel_event),
|
|
151
161
|
reraise=True,
|
|
152
162
|
)
|
|
153
163
|
def get_session_uri() -> str:
|
|
154
|
-
|
|
164
|
+
_check_cancelled(cancel_event)
|
|
165
|
+
r = requests.get(session_id_url, headers=headers, timeout=DEFAULT_HTTP_TIMEOUT)
|
|
155
166
|
r.raise_for_status()
|
|
156
167
|
payload = r.json()
|
|
157
168
|
status = AppStatus(payload.get("status"))
|
|
@@ -168,6 +179,8 @@ def connect(
|
|
|
168
179
|
logging.info("Getting SQL session status from %s ...", session_id_url)
|
|
169
180
|
session_uri = get_session_uri()
|
|
170
181
|
logging.debug("SQL session URI from app status: %s", session_uri)
|
|
182
|
+
except InterfaceError:
|
|
183
|
+
raise
|
|
171
184
|
except Exception as e:
|
|
172
185
|
raise InterfaceError("Could not acquire SQL session!", e)
|
|
173
186
|
|
|
@@ -178,9 +191,16 @@ def connect(
|
|
|
178
191
|
results_format=results_format,
|
|
179
192
|
data_compression=data_compression,
|
|
180
193
|
geometry_representation=geometry_representation,
|
|
194
|
+
cancel_event=cancel_event,
|
|
181
195
|
)
|
|
182
196
|
|
|
183
197
|
|
|
198
|
+
def _check_cancelled(cancel_event: Union[threading.Event, None]) -> None:
|
|
199
|
+
"""Raise InterfaceError if the cancel event is set."""
|
|
200
|
+
if cancel_event is not None and cancel_event.is_set():
|
|
201
|
+
raise InterfaceError("Connection cancelled by caller")
|
|
202
|
+
|
|
203
|
+
|
|
184
204
|
def http_to_ws(uri: str) -> str:
|
|
185
205
|
"""Converts an HTTP URI to a WebSocket URI."""
|
|
186
206
|
parsed = urllib.parse.urlparse(uri)
|
|
@@ -198,19 +218,41 @@ def connect_direct(
|
|
|
198
218
|
results_format: Union[ResultsFormat, None] = None,
|
|
199
219
|
data_compression: Union[DataCompression, None] = None,
|
|
200
220
|
geometry_representation: Union[GeometryRepresentation, None] = None,
|
|
221
|
+
cancel_event: Union[threading.Event, None] = None,
|
|
201
222
|
) -> Connection:
|
|
202
223
|
uri_with_protocol = f"{uri}/{protocol}"
|
|
224
|
+
ssl_context = ssl.create_default_context()
|
|
225
|
+
ssl_context.load_verify_locations(certifi.where())
|
|
203
226
|
|
|
204
|
-
|
|
227
|
+
@tenacity.retry(
|
|
228
|
+
stop=tenacity.stop_after_attempt(5),
|
|
229
|
+
wait=tenacity.wait_exponential(multiplier=1, min=1, max=5),
|
|
230
|
+
retry=tenacity.retry_if_exception_type(
|
|
231
|
+
(
|
|
232
|
+
ConnectionRefusedError,
|
|
233
|
+
ConnectionResetError,
|
|
234
|
+
TimeoutError,
|
|
235
|
+
websockets.exceptions.InvalidHandshake,
|
|
236
|
+
)
|
|
237
|
+
),
|
|
238
|
+
before_sleep=lambda _: _check_cancelled(cancel_event),
|
|
239
|
+
reraise=True,
|
|
240
|
+
)
|
|
241
|
+
def ws_connect() -> websockets.sync.client.ClientConnection:
|
|
242
|
+
_check_cancelled(cancel_event)
|
|
205
243
|
logging.info("Connecting to SQL session at %s ...", uri_with_protocol)
|
|
206
|
-
|
|
207
|
-
ssl_context.load_verify_locations(certifi.where())
|
|
208
|
-
ws = websockets.sync.client.connect(
|
|
244
|
+
return websockets.sync.client.connect(
|
|
209
245
|
uri=uri_with_protocol,
|
|
210
246
|
additional_headers=headers,
|
|
211
247
|
max_size=MAX_MESSAGE_SIZE,
|
|
248
|
+
open_timeout=DEFAULT_HTTP_TIMEOUT,
|
|
212
249
|
ssl=ssl_context,
|
|
213
250
|
)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
ws = ws_connect()
|
|
254
|
+
except InterfaceError:
|
|
255
|
+
raise
|
|
214
256
|
except Exception as e:
|
|
215
257
|
raise InterfaceError("Failed to connect to SQL session!") from e
|
|
216
258
|
|
{wherobots_python_dbapi-0.26.0.dist-info → wherobots_python_dbapi-0.27.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wherobots-python-dbapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: Python DB-API driver for Wherobots DB
|
|
5
5
|
Project-URL: Homepage, https://github.com/wherobots/wherobots-python-dbapi-driver
|
|
6
6
|
Project-URL: Tracker, https://github.com/wherobots/wherobots-python-dbapi-driver/issues
|
|
@@ -285,3 +285,9 @@ users may find useful:
|
|
|
285
285
|
your expected time between queries and effectively get a continuously
|
|
286
286
|
running SQL session runtime without any complex connection management
|
|
287
287
|
in your application.
|
|
288
|
+
* `cancel_event`: a `threading.Event` that, when set, causes the
|
|
289
|
+
connection attempt to abort promptly with an `InterfaceError`. This
|
|
290
|
+
is useful when `connect()` is running in a background thread and the
|
|
291
|
+
caller needs to interrupt it (e.g. on client disconnect or timeout).
|
|
292
|
+
The event is checked before each HTTP request, between retry
|
|
293
|
+
attempts, and before the WebSocket handshake.
|
|
@@ -3,14 +3,14 @@ wherobots/db/__init__.py,sha256=HN_uGOUMor80GB3rZIqnPcYWDAia7QAC-y9HfGKnegs,744
|
|
|
3
3
|
wherobots/db/connection.py,sha256=BWQwVzFgujEwPKvhDePM9q45M2opCk6Jq998L0ekMyY,12795
|
|
4
4
|
wherobots/db/constants.py,sha256=qV-6CmOya7-f-DoRMzcDzlhcmV5PkLCcqG5TFIsxQ7o,744
|
|
5
5
|
wherobots/db/cursor.py,sha256=Z0rNdhJ985IP9XnZpjrCzvM0OqDFVgCNfJn8lrbijAo,6809
|
|
6
|
-
wherobots/db/driver.py,sha256=
|
|
6
|
+
wherobots/db/driver.py,sha256=Hvldyos2yCMoshq80PFI-4rYhVGPV2SL5OioTuY7PmE,8966
|
|
7
7
|
wherobots/db/errors.py,sha256=G8a-DB6pgB0pUld59Wyq8PuywL-JWxGJfvrr-g8BPuU,310
|
|
8
8
|
wherobots/db/models.py,sha256=uk0EqZMH_RFkQusSw2AvyFivGvGJBu_WYP8FHa6JJGM,4178
|
|
9
9
|
wherobots/db/region.py,sha256=GoFzazvgOjE1kVVT1QKAsRK88c_1XSAebR3ME4foSlQ,400
|
|
10
10
|
wherobots/db/runtime.py,sha256=eAR1oJEtssgkp-IRPTLOrlPI-_5SM_ASw6T6hQ_qF4I,985
|
|
11
11
|
wherobots/db/session_type.py,sha256=jZNwCl0nxvY-0reIn8RR_-fIYSSvIcaGx_4pRc71Lww,137
|
|
12
12
|
wherobots/db/types.py,sha256=WXKT_gbdQCH2_yXD_FECk0BpB7yfNWOLewCfax0U_Lk,2516
|
|
13
|
-
wherobots_python_dbapi-0.
|
|
14
|
-
wherobots_python_dbapi-0.
|
|
15
|
-
wherobots_python_dbapi-0.
|
|
16
|
-
wherobots_python_dbapi-0.
|
|
13
|
+
wherobots_python_dbapi-0.27.0.dist-info/METADATA,sha256=FMCBp3yiCPb_d0AqxVkMmRqtDG5akWaktCfcJ5Uh4W0,10726
|
|
14
|
+
wherobots_python_dbapi-0.27.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
15
|
+
wherobots_python_dbapi-0.27.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
wherobots_python_dbapi-0.27.0.dist-info/RECORD,,
|
|
File without changes
|
{wherobots_python_dbapi-0.26.0.dist-info → wherobots_python_dbapi-0.27.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|