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 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
- runtime.value,
123
+ runtime_id or "org-default",
103
124
  f"running {version} " if version else "",
104
- region.value,
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={"region": region.value, "force_new": force_new},
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": runtime.value,
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
- r = requests.get(session_id_url, headers=headers)
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wherobots-python-dbapi
3
- Version: 0.26.1
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=2DAkWSQQOijDY3Zbufbk7j8X0yYkrqhLU_kvrNa7P2s,8002
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.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.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,,