wherobots-python-dbapi 0.26.1__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 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
@@ -51,6 +52,9 @@ paramstyle: Final[str] = PARAM_STYLE
51
52
  # This follows the industry-standard set used by urllib3.util.Retry's status_forcelist.
52
53
  TRANSIENT_HTTP_STATUS_CODES = {429, 502, 503, 504}
53
54
 
55
+ # Default timeout for individual HTTP requests (connect + read), in seconds.
56
+ DEFAULT_HTTP_TIMEOUT = 30
57
+
54
58
 
55
59
  def gen_user_agent_header():
56
60
  try:
@@ -79,6 +83,7 @@ def connect(
79
83
  results_format: Union[ResultsFormat, None] = None,
80
84
  data_compression: Union[DataCompression, None] = None,
81
85
  geometry_representation: Union[GeometryRepresentation, None] = None,
86
+ cancel_event: Union[threading.Event, None] = None,
82
87
  ) -> Connection:
83
88
  if not token and not api_key:
84
89
  raise ValueError("At least one of `token` or `api_key` is required")
@@ -109,6 +114,8 @@ def connect(
109
114
  if not host.startswith("http:"):
110
115
  host = f"https://{host}"
111
116
 
117
+ _check_cancelled(cancel_event)
118
+
112
119
  try:
113
120
  resp = requests.post(
114
121
  url=f"{host}/sql/session",
@@ -120,6 +127,7 @@ def connect(
120
127
  "sessionType": session_type.value,
121
128
  },
122
129
  headers=headers,
130
+ timeout=DEFAULT_HTTP_TIMEOUT,
123
131
  )
124
132
  resp.raise_for_status()
125
133
  except requests.HTTPError as e:
@@ -149,10 +157,12 @@ def connect(
149
157
  )
150
158
  | tenacity.retry_if_exception_type(tenacity.TryAgain)
151
159
  ),
160
+ before_sleep=lambda _: _check_cancelled(cancel_event),
152
161
  reraise=True,
153
162
  )
154
163
  def get_session_uri() -> str:
155
- r = requests.get(session_id_url, headers=headers)
164
+ _check_cancelled(cancel_event)
165
+ r = requests.get(session_id_url, headers=headers, timeout=DEFAULT_HTTP_TIMEOUT)
156
166
  r.raise_for_status()
157
167
  payload = r.json()
158
168
  status = AppStatus(payload.get("status"))
@@ -169,6 +179,8 @@ def connect(
169
179
  logging.info("Getting SQL session status from %s ...", session_id_url)
170
180
  session_uri = get_session_uri()
171
181
  logging.debug("SQL session URI from app status: %s", session_uri)
182
+ except InterfaceError:
183
+ raise
172
184
  except Exception as e:
173
185
  raise InterfaceError("Could not acquire SQL session!", e)
174
186
 
@@ -179,9 +191,16 @@ def connect(
179
191
  results_format=results_format,
180
192
  data_compression=data_compression,
181
193
  geometry_representation=geometry_representation,
194
+ cancel_event=cancel_event,
182
195
  )
183
196
 
184
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
+
185
204
  def http_to_ws(uri: str) -> str:
186
205
  """Converts an HTTP URI to a WebSocket URI."""
187
206
  parsed = urllib.parse.urlparse(uri)
@@ -199,6 +218,7 @@ def connect_direct(
199
218
  results_format: Union[ResultsFormat, None] = None,
200
219
  data_compression: Union[DataCompression, None] = None,
201
220
  geometry_representation: Union[GeometryRepresentation, None] = None,
221
+ cancel_event: Union[threading.Event, None] = None,
202
222
  ) -> Connection:
203
223
  uri_with_protocol = f"{uri}/{protocol}"
204
224
  ssl_context = ssl.create_default_context()
@@ -215,19 +235,24 @@ def connect_direct(
215
235
  websockets.exceptions.InvalidHandshake,
216
236
  )
217
237
  ),
238
+ before_sleep=lambda _: _check_cancelled(cancel_event),
218
239
  reraise=True,
219
240
  )
220
241
  def ws_connect() -> websockets.sync.client.ClientConnection:
242
+ _check_cancelled(cancel_event)
221
243
  logging.info("Connecting to SQL session at %s ...", uri_with_protocol)
222
244
  return websockets.sync.client.connect(
223
245
  uri=uri_with_protocol,
224
246
  additional_headers=headers,
225
247
  max_size=MAX_MESSAGE_SIZE,
248
+ open_timeout=DEFAULT_HTTP_TIMEOUT,
226
249
  ssl=ssl_context,
227
250
  )
228
251
 
229
252
  try:
230
253
  ws = ws_connect()
254
+ except InterfaceError:
255
+ raise
231
256
  except Exception as e:
232
257
  raise InterfaceError("Failed to connect to SQL session!") from e
233
258
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wherobots-python-dbapi
3
- Version: 0.26.1
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=2DAkWSQQOijDY3Zbufbk7j8X0yYkrqhLU_kvrNa7P2s,8002
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.26.1.dist-info/METADATA,sha256=ILRnl_hGC_RlRgzuttJxSw9ubkJMAjVMVmMEb8G8V-M,10328
14
- wherobots_python_dbapi-0.26.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
15
- wherobots_python_dbapi-0.26.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- wherobots_python_dbapi-0.26.1.dist-info/RECORD,,
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,,