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 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
- 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)
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
- try:
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
- ssl_context = ssl.create_default_context()
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wherobots-python-dbapi
3
- Version: 0.26.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=J-ESQYLTOL0NYbOVVwsARXwECjVgVohg-aUA7Lv_ZrQ,7474
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.0.dist-info/METADATA,sha256=iBhvIP-JgBLJgV4Fd1HCcAJjd2jbKIeXRA0439Tg-38,10328
14
- wherobots_python_dbapi-0.26.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
15
- wherobots_python_dbapi-0.26.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- wherobots_python_dbapi-0.26.0.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,,