wherobots-python-dbapi 0.26.1__py3-none-any.whl → 0.28.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 +54 -11
- {wherobots_python_dbapi-0.26.1.dist-info → wherobots_python_dbapi-0.28.0.dist-info}/METADATA +17 -1
- {wherobots_python_dbapi-0.26.1.dist-info → wherobots_python_dbapi-0.28.0.dist-info}/RECORD +5 -5
- {wherobots_python_dbapi-0.26.1.dist-info → wherobots_python_dbapi-0.28.0.dist-info}/WHEEL +0 -0
- {wherobots_python_dbapi-0.26.1.dist-info → wherobots_python_dbapi-0.28.0.dist-info}/licenses/LICENSE +0 -0
wherobots/db/driver.py
CHANGED
|
@@ -11,6 +11,7 @@ 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
|
|
16
17
|
import websockets.exceptions
|
|
@@ -20,8 +21,6 @@ import certifi
|
|
|
20
21
|
from .connection import Connection
|
|
21
22
|
from .constants import (
|
|
22
23
|
DEFAULT_ENDPOINT,
|
|
23
|
-
DEFAULT_REGION,
|
|
24
|
-
DEFAULT_RUNTIME,
|
|
25
24
|
DEFAULT_READ_TIMEOUT_SECONDS,
|
|
26
25
|
DEFAULT_SESSION_TYPE,
|
|
27
26
|
DEFAULT_SESSION_WAIT_TIMEOUT_SECONDS,
|
|
@@ -51,6 +50,9 @@ paramstyle: Final[str] = PARAM_STYLE
|
|
|
51
50
|
# This follows the industry-standard set used by urllib3.util.Retry's status_forcelist.
|
|
52
51
|
TRANSIENT_HTTP_STATUS_CODES = {429, 502, 503, 504}
|
|
53
52
|
|
|
53
|
+
# Default timeout for individual HTTP requests (connect + read), in seconds.
|
|
54
|
+
DEFAULT_HTTP_TIMEOUT = 30
|
|
55
|
+
|
|
54
56
|
|
|
55
57
|
def gen_user_agent_header():
|
|
56
58
|
try:
|
|
@@ -68,8 +70,8 @@ def connect(
|
|
|
68
70
|
host: str = DEFAULT_ENDPOINT,
|
|
69
71
|
token: Union[str, None] = None,
|
|
70
72
|
api_key: Union[str, None] = None,
|
|
71
|
-
runtime: Union[Runtime, None] = None,
|
|
72
|
-
region: Union[Region, None] = None,
|
|
73
|
+
runtime: Union[str, Runtime, None] = None,
|
|
74
|
+
region: Union[str, Region, None] = None,
|
|
73
75
|
version: Union[str, None] = None,
|
|
74
76
|
wait_timeout: float = DEFAULT_SESSION_WAIT_TIMEOUT_SECONDS,
|
|
75
77
|
read_timeout: float = DEFAULT_READ_TIMEOUT_SECONDS,
|
|
@@ -79,7 +81,22 @@ def connect(
|
|
|
79
81
|
results_format: Union[ResultsFormat, None] = None,
|
|
80
82
|
data_compression: Union[DataCompression, None] = None,
|
|
81
83
|
geometry_representation: Union[GeometryRepresentation, None] = None,
|
|
84
|
+
cancel_event: Union[threading.Event, None] = None,
|
|
82
85
|
) -> Connection:
|
|
86
|
+
"""Create a connection to a Wherobots SQL session.
|
|
87
|
+
|
|
88
|
+
:param runtime: The compute runtime to use. Accepts a ``Runtime`` enum value
|
|
89
|
+
or a raw string; strings are passed to the API as-is. Override the
|
|
90
|
+
default runtime set for your organization — only set this if you need a
|
|
91
|
+
specific runtime instead of the one your administrator has configured.
|
|
92
|
+
When omitted, your organization's default runtime is used.
|
|
93
|
+
:param region: The compute region to run in. Accepts a ``Region`` enum value
|
|
94
|
+
or a raw string (e.g. a BYOC region such as ``byoc-acme-us-east-1``);
|
|
95
|
+
strings are passed to the API as-is. Override the default region set for
|
|
96
|
+
your organization — only set this if you intend to use a specific region
|
|
97
|
+
instead of the one your administrator has configured. When omitted, your
|
|
98
|
+
organization's default region is used.
|
|
99
|
+
"""
|
|
83
100
|
if not token and not api_key:
|
|
84
101
|
raise ValueError("At least one of `token` or `api_key` is required")
|
|
85
102
|
if token and api_key:
|
|
@@ -92,16 +109,20 @@ def connect(
|
|
|
92
109
|
headers["X-API-Key"] = api_key
|
|
93
110
|
|
|
94
111
|
host = host or DEFAULT_ENDPOINT
|
|
95
|
-
runtime = runtime or DEFAULT_RUNTIME
|
|
96
|
-
region = region or DEFAULT_REGION
|
|
97
112
|
session_type = session_type or DEFAULT_SESSION_TYPE
|
|
98
113
|
|
|
114
|
+
# Normalize enum values to their string form and pass raw strings through
|
|
115
|
+
# untouched. When omitted (None) the field is dropped from the request so
|
|
116
|
+
# the API applies the organization's configured default.
|
|
117
|
+
runtime_id = runtime.value if isinstance(runtime, Runtime) else runtime
|
|
118
|
+
region_name = region.value if isinstance(region, Region) else region
|
|
119
|
+
|
|
99
120
|
logging.info(
|
|
100
121
|
"Requesting %s%s runtime %sin %s from %s ...",
|
|
101
122
|
"new " if force_new else "",
|
|
102
|
-
|
|
123
|
+
runtime_id or "org-default",
|
|
103
124
|
f"running {version} " if version else "",
|
|
104
|
-
|
|
125
|
+
region_name or "org-default",
|
|
105
126
|
host,
|
|
106
127
|
)
|
|
107
128
|
|
|
@@ -109,17 +130,22 @@ def connect(
|
|
|
109
130
|
if not host.startswith("http:"):
|
|
110
131
|
host = f"https://{host}"
|
|
111
132
|
|
|
133
|
+
_check_cancelled(cancel_event)
|
|
134
|
+
|
|
112
135
|
try:
|
|
113
136
|
resp = requests.post(
|
|
114
137
|
url=f"{host}/sql/session",
|
|
115
|
-
params
|
|
138
|
+
# `requests` omits query params whose value is None, so an omitted
|
|
139
|
+
# region is simply not sent and the API applies the org default.
|
|
140
|
+
params={"region": region_name, "force_new": force_new},
|
|
116
141
|
json={
|
|
117
|
-
"runtimeId":
|
|
142
|
+
"runtimeId": runtime_id,
|
|
118
143
|
"shutdownAfterInactiveSeconds": shutdown_after_inactive_seconds,
|
|
119
144
|
"version": version,
|
|
120
145
|
"sessionType": session_type.value,
|
|
121
146
|
},
|
|
122
147
|
headers=headers,
|
|
148
|
+
timeout=DEFAULT_HTTP_TIMEOUT,
|
|
123
149
|
)
|
|
124
150
|
resp.raise_for_status()
|
|
125
151
|
except requests.HTTPError as e:
|
|
@@ -149,10 +175,12 @@ def connect(
|
|
|
149
175
|
)
|
|
150
176
|
| tenacity.retry_if_exception_type(tenacity.TryAgain)
|
|
151
177
|
),
|
|
178
|
+
before_sleep=lambda _: _check_cancelled(cancel_event),
|
|
152
179
|
reraise=True,
|
|
153
180
|
)
|
|
154
181
|
def get_session_uri() -> str:
|
|
155
|
-
|
|
182
|
+
_check_cancelled(cancel_event)
|
|
183
|
+
r = requests.get(session_id_url, headers=headers, timeout=DEFAULT_HTTP_TIMEOUT)
|
|
156
184
|
r.raise_for_status()
|
|
157
185
|
payload = r.json()
|
|
158
186
|
status = AppStatus(payload.get("status"))
|
|
@@ -169,6 +197,8 @@ def connect(
|
|
|
169
197
|
logging.info("Getting SQL session status from %s ...", session_id_url)
|
|
170
198
|
session_uri = get_session_uri()
|
|
171
199
|
logging.debug("SQL session URI from app status: %s", session_uri)
|
|
200
|
+
except InterfaceError:
|
|
201
|
+
raise
|
|
172
202
|
except Exception as e:
|
|
173
203
|
raise InterfaceError("Could not acquire SQL session!", e)
|
|
174
204
|
|
|
@@ -179,9 +209,16 @@ def connect(
|
|
|
179
209
|
results_format=results_format,
|
|
180
210
|
data_compression=data_compression,
|
|
181
211
|
geometry_representation=geometry_representation,
|
|
212
|
+
cancel_event=cancel_event,
|
|
182
213
|
)
|
|
183
214
|
|
|
184
215
|
|
|
216
|
+
def _check_cancelled(cancel_event: Union[threading.Event, None]) -> None:
|
|
217
|
+
"""Raise InterfaceError if the cancel event is set."""
|
|
218
|
+
if cancel_event is not None and cancel_event.is_set():
|
|
219
|
+
raise InterfaceError("Connection cancelled by caller")
|
|
220
|
+
|
|
221
|
+
|
|
185
222
|
def http_to_ws(uri: str) -> str:
|
|
186
223
|
"""Converts an HTTP URI to a WebSocket URI."""
|
|
187
224
|
parsed = urllib.parse.urlparse(uri)
|
|
@@ -199,6 +236,7 @@ def connect_direct(
|
|
|
199
236
|
results_format: Union[ResultsFormat, None] = None,
|
|
200
237
|
data_compression: Union[DataCompression, None] = None,
|
|
201
238
|
geometry_representation: Union[GeometryRepresentation, None] = None,
|
|
239
|
+
cancel_event: Union[threading.Event, None] = None,
|
|
202
240
|
) -> Connection:
|
|
203
241
|
uri_with_protocol = f"{uri}/{protocol}"
|
|
204
242
|
ssl_context = ssl.create_default_context()
|
|
@@ -215,19 +253,24 @@ def connect_direct(
|
|
|
215
253
|
websockets.exceptions.InvalidHandshake,
|
|
216
254
|
)
|
|
217
255
|
),
|
|
256
|
+
before_sleep=lambda _: _check_cancelled(cancel_event),
|
|
218
257
|
reraise=True,
|
|
219
258
|
)
|
|
220
259
|
def ws_connect() -> websockets.sync.client.ClientConnection:
|
|
260
|
+
_check_cancelled(cancel_event)
|
|
221
261
|
logging.info("Connecting to SQL session at %s ...", uri_with_protocol)
|
|
222
262
|
return websockets.sync.client.connect(
|
|
223
263
|
uri=uri_with_protocol,
|
|
224
264
|
additional_headers=headers,
|
|
225
265
|
max_size=MAX_MESSAGE_SIZE,
|
|
266
|
+
open_timeout=DEFAULT_HTTP_TIMEOUT,
|
|
226
267
|
ssl=ssl_context,
|
|
227
268
|
)
|
|
228
269
|
|
|
229
270
|
try:
|
|
230
271
|
ws = ws_connect()
|
|
272
|
+
except InterfaceError:
|
|
273
|
+
raise
|
|
231
274
|
except Exception as e:
|
|
232
275
|
raise InterfaceError("Failed to connect to SQL session!") from e
|
|
233
276
|
|
{wherobots_python_dbapi-0.26.1.dist-info → wherobots_python_dbapi-0.28.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.28.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
|
|
@@ -107,6 +107,16 @@ with connect(
|
|
|
107
107
|
print(results)
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
`runtime` and `region` are optional and accept either the provided enums (handy
|
|
111
|
+
for autocomplete) or a plain string — strings are passed to the API as-is, so
|
|
112
|
+
new or BYOC regions (e.g. `region="byoc-acme-us-east-1"`) work without an SDK
|
|
113
|
+
upgrade. When omitted, your organization's configured defaults are used:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
with connect(api_key='...') as conn: # uses your org's default runtime + region
|
|
117
|
+
...
|
|
118
|
+
```
|
|
119
|
+
|
|
110
120
|
The `Cursor` supports the context manager protocol, so you can use it
|
|
111
121
|
within a `with` statement when needed:
|
|
112
122
|
|
|
@@ -285,3 +295,9 @@ users may find useful:
|
|
|
285
295
|
your expected time between queries and effectively get a continuously
|
|
286
296
|
running SQL session runtime without any complex connection management
|
|
287
297
|
in your application.
|
|
298
|
+
* `cancel_event`: a `threading.Event` that, when set, causes the
|
|
299
|
+
connection attempt to abort promptly with an `InterfaceError`. This
|
|
300
|
+
is useful when `connect()` is running in a background thread and the
|
|
301
|
+
caller needs to interrupt it (e.g. on client disconnect or timeout).
|
|
302
|
+
The event is checked before each HTTP request, between retry
|
|
303
|
+
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=fHfYtujvafmAnJh9uf_0oZUNVtyf8XLsgs9sqfiDMk4,10304
|
|
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.28.0.dist-info/METADATA,sha256=qUqbExsrE8Q6GwBE8f_mWL8-yNnGAp4vY1qgc_2OU0c,11142
|
|
14
|
+
wherobots_python_dbapi-0.28.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
15
|
+
wherobots_python_dbapi-0.28.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
wherobots_python_dbapi-0.28.0.dist-info/RECORD,,
|
|
File without changes
|
{wherobots_python_dbapi-0.26.1.dist-info → wherobots_python_dbapi-0.28.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|