wherobots-python-dbapi 0.28.2__tar.gz → 0.30.0__tar.gz
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_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/PKG-INFO +62 -1
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/README.md +61 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/pyproject.toml +1 -1
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/__init__.py +6 -0
- wherobots_python_dbapi-0.30.0/wherobots/db/_transport.py +57 -0
- wherobots_python_dbapi-0.30.0/wherobots/db/connection.py +1369 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/constants.py +10 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/cursor.py +10 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/driver.py +104 -17
- wherobots_python_dbapi-0.30.0/wherobots/db/errors.py +103 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/types.py +3 -0
- wherobots_python_dbapi-0.28.2/wherobots/db/_transport.py +0 -24
- wherobots_python_dbapi-0.28.2/wherobots/db/connection.py +0 -593
- wherobots_python_dbapi-0.28.2/wherobots/db/errors.py +0 -26
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/.gitignore +0 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/LICENSE +0 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/models.py +0 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/redaction.py +0 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/region.py +0 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/runtime.py +0 -0
- {wherobots_python_dbapi-0.28.2 → wherobots_python_dbapi-0.30.0}/wherobots/db/session_type.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: wherobots-python-dbapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.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
|
|
@@ -225,6 +225,49 @@ Progress events are best-effort and may not be available for all query
|
|
|
225
225
|
types or server versions. The handler is simply not invoked when no
|
|
226
226
|
progress information is available.
|
|
227
227
|
|
|
228
|
+
### Recovering from a lost connection
|
|
229
|
+
|
|
230
|
+
If the WebSocket to the SQL session drops while queries are running (a
|
|
231
|
+
network path failure, a keepalive timeout), the connection reconnects to
|
|
232
|
+
the same SQL session by itself and re-attaches every pending query, so
|
|
233
|
+
their results still arrive: there's nothing for your code to do. It keeps
|
|
234
|
+
trying, with backoff, for up to `reconnect_timeout_seconds` (60 seconds by
|
|
235
|
+
default; see below). It never starts a new session, and gives up at once
|
|
236
|
+
if the session refuses the connection, for example because it's gone.
|
|
237
|
+
While it reconnects, `execute()` and cancelling (closing a cursor, or
|
|
238
|
+
executing on it) wait for it to finish.
|
|
239
|
+
|
|
240
|
+
A re-attached query can still fail:
|
|
241
|
+
|
|
242
|
+
* If the SQL session doesn't know the execution (the statement didn't
|
|
243
|
+
reach it before the connection dropped, or the session restarted),
|
|
244
|
+
fetching raises `ExecutionNotFoundError`, a subclass of
|
|
245
|
+
`OperationalError`.
|
|
246
|
+
* If the SQL session cancelled the execution while the connection was
|
|
247
|
+
down (for example, its grace period for a lost connection ran out, or
|
|
248
|
+
another client cancelled it), fetching raises
|
|
249
|
+
`ExecutionCancelledError`, also an `OperationalError` subclass: its
|
|
250
|
+
outcome, including whether a write committed, is unknown. An execution
|
|
251
|
+
that you cancel yourself, by closing the cursor or executing on it,
|
|
252
|
+
completes with an empty result, as usual.
|
|
253
|
+
* An older SQL session that doesn't support attaching still answers with
|
|
254
|
+
the execution's state, and the driver polls it for the outcome (see
|
|
255
|
+
`stale_query_probe_seconds` below). If such a session reports no
|
|
256
|
+
location for a stored result, because it predates reporting it or
|
|
257
|
+
because the stored result is empty, the driver can't tell which, and
|
|
258
|
+
fails the query with an `OperationalError`. With probes disabled, a
|
|
259
|
+
running execution on such a session fails right away.
|
|
260
|
+
|
|
261
|
+
If the connection can't reconnect in time, or reconnecting is turned off
|
|
262
|
+
(`reconnect_timeout_seconds=None`), fetching a pending query's outcome
|
|
263
|
+
raises `ConnectionLostError`, another `OperationalError` subclass. The
|
|
264
|
+
query may still have run, or be running, on the SQL session: its outcome,
|
|
265
|
+
including whether a write committed, is unknown. The error's
|
|
266
|
+
`execution_id` and `session_id` attributes identify it, and a cursor's
|
|
267
|
+
`execution_id` property holds the ID of its current or last execution.
|
|
268
|
+
(When `execute()` itself raises `ConnectionLostError`, the connection was
|
|
269
|
+
already lost and the statement was never sent.)
|
|
270
|
+
|
|
228
271
|
### Runtime and region selection
|
|
229
272
|
|
|
230
273
|
You can chose the Wherobots runtime you want to use using the `runtime`
|
|
@@ -302,3 +345,21 @@ users may find useful:
|
|
|
302
345
|
caller needs to interrupt it (e.g. on client disconnect or timeout).
|
|
303
346
|
The event is checked before each HTTP request, between retry
|
|
304
347
|
attempts, and before the WebSocket handshake.
|
|
348
|
+
* `session_key`: keeps this connection on SQL sessions of its own. The
|
|
349
|
+
API reuses a session only for connections that ask with the same key,
|
|
350
|
+
and never one opened without a key, so a heavy query elsewhere never
|
|
351
|
+
holds the session your queries run on. When a new session would exceed
|
|
352
|
+
your organization's quota, the API shares another of your sessions
|
|
353
|
+
under another key instead, with or without `session_key`, and the
|
|
354
|
+
connection's `session_fallback` is `"quota"` (it is `None` otherwise).
|
|
355
|
+
`connection.session_id` names the session.
|
|
356
|
+
* `stale_query_probe_seconds`: how long a running query may go without
|
|
357
|
+
any message from the SQL session before the driver asks the session
|
|
358
|
+
for its state again, in case the query's completion event was lost.
|
|
359
|
+
Repeat probes back off to at most 8 times this interval. Defaults to
|
|
360
|
+
30 seconds; pass `None` to disable probing.
|
|
361
|
+
* `reconnect_timeout_seconds`: how long the connection keeps trying to
|
|
362
|
+
reconnect to the same SQL session after losing its WebSocket (see
|
|
363
|
+
"Recovering from a lost connection" above). Defaults to 60 seconds,
|
|
364
|
+
which fits in the SQL session's 120-second grace period for the
|
|
365
|
+
queries of a lost connection; pass `None` to turn reconnecting off.
|
|
@@ -200,6 +200,49 @@ Progress events are best-effort and may not be available for all query
|
|
|
200
200
|
types or server versions. The handler is simply not invoked when no
|
|
201
201
|
progress information is available.
|
|
202
202
|
|
|
203
|
+
### Recovering from a lost connection
|
|
204
|
+
|
|
205
|
+
If the WebSocket to the SQL session drops while queries are running (a
|
|
206
|
+
network path failure, a keepalive timeout), the connection reconnects to
|
|
207
|
+
the same SQL session by itself and re-attaches every pending query, so
|
|
208
|
+
their results still arrive: there's nothing for your code to do. It keeps
|
|
209
|
+
trying, with backoff, for up to `reconnect_timeout_seconds` (60 seconds by
|
|
210
|
+
default; see below). It never starts a new session, and gives up at once
|
|
211
|
+
if the session refuses the connection, for example because it's gone.
|
|
212
|
+
While it reconnects, `execute()` and cancelling (closing a cursor, or
|
|
213
|
+
executing on it) wait for it to finish.
|
|
214
|
+
|
|
215
|
+
A re-attached query can still fail:
|
|
216
|
+
|
|
217
|
+
* If the SQL session doesn't know the execution (the statement didn't
|
|
218
|
+
reach it before the connection dropped, or the session restarted),
|
|
219
|
+
fetching raises `ExecutionNotFoundError`, a subclass of
|
|
220
|
+
`OperationalError`.
|
|
221
|
+
* If the SQL session cancelled the execution while the connection was
|
|
222
|
+
down (for example, its grace period for a lost connection ran out, or
|
|
223
|
+
another client cancelled it), fetching raises
|
|
224
|
+
`ExecutionCancelledError`, also an `OperationalError` subclass: its
|
|
225
|
+
outcome, including whether a write committed, is unknown. An execution
|
|
226
|
+
that you cancel yourself, by closing the cursor or executing on it,
|
|
227
|
+
completes with an empty result, as usual.
|
|
228
|
+
* An older SQL session that doesn't support attaching still answers with
|
|
229
|
+
the execution's state, and the driver polls it for the outcome (see
|
|
230
|
+
`stale_query_probe_seconds` below). If such a session reports no
|
|
231
|
+
location for a stored result, because it predates reporting it or
|
|
232
|
+
because the stored result is empty, the driver can't tell which, and
|
|
233
|
+
fails the query with an `OperationalError`. With probes disabled, a
|
|
234
|
+
running execution on such a session fails right away.
|
|
235
|
+
|
|
236
|
+
If the connection can't reconnect in time, or reconnecting is turned off
|
|
237
|
+
(`reconnect_timeout_seconds=None`), fetching a pending query's outcome
|
|
238
|
+
raises `ConnectionLostError`, another `OperationalError` subclass. The
|
|
239
|
+
query may still have run, or be running, on the SQL session: its outcome,
|
|
240
|
+
including whether a write committed, is unknown. The error's
|
|
241
|
+
`execution_id` and `session_id` attributes identify it, and a cursor's
|
|
242
|
+
`execution_id` property holds the ID of its current or last execution.
|
|
243
|
+
(When `execute()` itself raises `ConnectionLostError`, the connection was
|
|
244
|
+
already lost and the statement was never sent.)
|
|
245
|
+
|
|
203
246
|
### Runtime and region selection
|
|
204
247
|
|
|
205
248
|
You can chose the Wherobots runtime you want to use using the `runtime`
|
|
@@ -277,3 +320,21 @@ users may find useful:
|
|
|
277
320
|
caller needs to interrupt it (e.g. on client disconnect or timeout).
|
|
278
321
|
The event is checked before each HTTP request, between retry
|
|
279
322
|
attempts, and before the WebSocket handshake.
|
|
323
|
+
* `session_key`: keeps this connection on SQL sessions of its own. The
|
|
324
|
+
API reuses a session only for connections that ask with the same key,
|
|
325
|
+
and never one opened without a key, so a heavy query elsewhere never
|
|
326
|
+
holds the session your queries run on. When a new session would exceed
|
|
327
|
+
your organization's quota, the API shares another of your sessions
|
|
328
|
+
under another key instead, with or without `session_key`, and the
|
|
329
|
+
connection's `session_fallback` is `"quota"` (it is `None` otherwise).
|
|
330
|
+
`connection.session_id` names the session.
|
|
331
|
+
* `stale_query_probe_seconds`: how long a running query may go without
|
|
332
|
+
any message from the SQL session before the driver asks the session
|
|
333
|
+
for its state again, in case the query's completion event was lost.
|
|
334
|
+
Repeat probes back off to at most 8 times this interval. Defaults to
|
|
335
|
+
30 seconds; pass `None` to disable probing.
|
|
336
|
+
* `reconnect_timeout_seconds`: how long the connection keeps trying to
|
|
337
|
+
reconnect to the same SQL session after losing its WebSocket (see
|
|
338
|
+
"Recovering from a lost connection" above). Defaults to 60 seconds,
|
|
339
|
+
which fits in the SQL session's 120-second grace period for the
|
|
340
|
+
queries of a lost connection; pass `None` to turn reconnecting off.
|
|
@@ -3,7 +3,10 @@ from .cursor import Cursor
|
|
|
3
3
|
from .driver import connect, connect_direct
|
|
4
4
|
from .errors import (
|
|
5
5
|
Error,
|
|
6
|
+
ConnectionLostError,
|
|
6
7
|
DatabaseError,
|
|
8
|
+
ExecutionCancelledError,
|
|
9
|
+
ExecutionNotFoundError,
|
|
7
10
|
InternalError,
|
|
8
11
|
InterfaceError,
|
|
9
12
|
OperationalError,
|
|
@@ -26,6 +29,9 @@ __all__ = [
|
|
|
26
29
|
"InternalError",
|
|
27
30
|
"InterfaceError",
|
|
28
31
|
"OperationalError",
|
|
32
|
+
"ConnectionLostError",
|
|
33
|
+
"ExecutionCancelledError",
|
|
34
|
+
"ExecutionNotFoundError",
|
|
29
35
|
"ProgrammingError",
|
|
30
36
|
"NotSupportedError",
|
|
31
37
|
"Region",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Transport termination independent of the WebSocket protocol's send lock."""
|
|
2
|
+
|
|
3
|
+
import errno
|
|
4
|
+
import select
|
|
5
|
+
import selectors
|
|
6
|
+
import socket
|
|
7
|
+
|
|
8
|
+
from websockets.sync.client import ClientConnection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def abort_connection(ws: ClientConnection) -> None:
|
|
12
|
+
"""Disable further writes and wake blocked I/O before publishing failures.
|
|
13
|
+
|
|
14
|
+
ClientConnection.close() takes the library's protocol mutex, which a
|
|
15
|
+
stalled sendall() may hold. Shutdown the owned socket instead; the library's
|
|
16
|
+
receive thread will observe EOF/error and finish protocol cleanup itself.
|
|
17
|
+
Already transmitted bytes may still execute remotely.
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
ws.socket.shutdown(socket.SHUT_RDWR)
|
|
21
|
+
except OSError as exc:
|
|
22
|
+
if exc.errno not in (errno.ENOTCONN, errno.EBADF, errno.ECONNRESET):
|
|
23
|
+
raise
|
|
24
|
+
finally:
|
|
25
|
+
# Even if shutdown fails, prevent any later send on this socket object.
|
|
26
|
+
ws.socket.close()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def socket_writable(ws: ClientConnection) -> bool:
|
|
30
|
+
"""Whether a small write to the transport's socket won't block right now.
|
|
31
|
+
|
|
32
|
+
For a send that must not stall the reader thread: a full send buffer
|
|
33
|
+
(a peer that keeps sending but stops reading) would park sendall(), which
|
|
34
|
+
also holds the library's protocol mutex, so even keepalive pings stall.
|
|
35
|
+
|
|
36
|
+
Never raises. Returns ``True`` (proceed, as without this check) on any
|
|
37
|
+
failure or for a transport without a real socket, such as a test fake:
|
|
38
|
+
``False`` would silently disable the caller's sends. Uses ``poll`` (or a
|
|
39
|
+
selector), not ``select.select``, which fails for fds above FD_SETSIZE.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
sock = getattr(ws, "socket", None)
|
|
43
|
+
if not isinstance(sock, socket.socket):
|
|
44
|
+
return True
|
|
45
|
+
fd = sock.fileno()
|
|
46
|
+
if fd < 0:
|
|
47
|
+
return True # Closed: let the send itself report it.
|
|
48
|
+
if hasattr(select, "poll"):
|
|
49
|
+
poller = select.poll()
|
|
50
|
+
poller.register(fd, select.POLLOUT)
|
|
51
|
+
# POLLERR/POLLHUP count as writable: the send then fails fast.
|
|
52
|
+
return bool(poller.poll(0))
|
|
53
|
+
with selectors.DefaultSelector() as selector:
|
|
54
|
+
selector.register(fd, selectors.EVENT_WRITE)
|
|
55
|
+
return bool(selector.select(0))
|
|
56
|
+
except Exception:
|
|
57
|
+
return True
|