wherobots-python-dbapi 0.22.0__tar.gz → 0.23.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.22.0 → wherobots_python_dbapi-0.23.0}/PKG-INFO +42 -2
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/README.md +40 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/pyproject.toml +2 -2
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/__init__.py +5 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/connection.py +48 -13
- wherobots_python_dbapi-0.23.0/wherobots/db/constants.py +22 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/cursor.py +51 -18
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/driver.py +7 -5
- wherobots_python_dbapi-0.23.0/wherobots/db/models.py +80 -0
- wherobots_python_dbapi-0.22.0/wherobots/db/constants.py → wherobots_python_dbapi-0.23.0/wherobots/db/types.py +6 -20
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/.gitignore +0 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/LICENSE +0 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/__init__.py +0 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/errors.py +0 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/region.py +0 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/runtime.py +0 -0
- {wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/session_type.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wherobots-python-dbapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.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
|
|
7
7
|
Author-email: Maxime Petazzoni <max@wherobots.com>
|
|
8
8
|
License-Expression: Apache-2.0
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Python: <4,>=3.
|
|
10
|
+
Requires-Python: <4,>=3.10
|
|
11
11
|
Requires-Dist: cbor2>=5.6.3
|
|
12
12
|
Requires-Dist: packaging
|
|
13
13
|
Requires-Dist: pandas
|
|
@@ -83,6 +83,46 @@ It also implements the `close()` method, as suggested by the PEP-2049
|
|
|
83
83
|
specification, to support situations where the cursor is wrapped in a
|
|
84
84
|
`contextmanager.closing()`.
|
|
85
85
|
|
|
86
|
+
### Storing results in cloud storage
|
|
87
|
+
|
|
88
|
+
For large query results, you can store them directly in cloud storage
|
|
89
|
+
instead of retrieving them over the connection. This is useful when
|
|
90
|
+
results are too large to transfer efficiently, or when you want to
|
|
91
|
+
process them later with other tools.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from wherobots.db import connect, Store, StorageFormat
|
|
95
|
+
from wherobots.db.region import Region
|
|
96
|
+
from wherobots.db.runtime import Runtime
|
|
97
|
+
|
|
98
|
+
with connect(
|
|
99
|
+
api_key='...',
|
|
100
|
+
runtime=Runtime.TINY,
|
|
101
|
+
region=Region.AWS_US_WEST_2) as conn:
|
|
102
|
+
curr = conn.cursor()
|
|
103
|
+
|
|
104
|
+
# Store results with a presigned URL for easy download
|
|
105
|
+
curr.execute(
|
|
106
|
+
"SELECT * FROM wherobots_open_data.overture.places LIMIT 1000",
|
|
107
|
+
store=Store.for_download()
|
|
108
|
+
)
|
|
109
|
+
store_result = curr.get_store_result()
|
|
110
|
+
print(f"Results stored at: {store_result.result_uri}")
|
|
111
|
+
print(f"Size: {store_result.size} bytes")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `Store` class supports the following options:
|
|
115
|
+
|
|
116
|
+
* `format`: output format - `StorageFormat.PARQUET` (default),
|
|
117
|
+
`StorageFormat.CSV`, or `StorageFormat.GEOJSON`
|
|
118
|
+
* `single`: if `True`, write results to a single file instead of
|
|
119
|
+
multiple partitioned files (default: `True`)
|
|
120
|
+
* `generate_presigned_url`: if `True`, generate a presigned URL for
|
|
121
|
+
downloading results (default: `False`)
|
|
122
|
+
|
|
123
|
+
Use `Store.for_download()` as a convenient shorthand for storing results
|
|
124
|
+
as a single Parquet file with a presigned URL.
|
|
125
|
+
|
|
86
126
|
### Runtime and region selection
|
|
87
127
|
|
|
88
128
|
You can chose the Wherobots runtime you want to use using the `runtime`
|
|
@@ -59,6 +59,46 @@ It also implements the `close()` method, as suggested by the PEP-2049
|
|
|
59
59
|
specification, to support situations where the cursor is wrapped in a
|
|
60
60
|
`contextmanager.closing()`.
|
|
61
61
|
|
|
62
|
+
### Storing results in cloud storage
|
|
63
|
+
|
|
64
|
+
For large query results, you can store them directly in cloud storage
|
|
65
|
+
instead of retrieving them over the connection. This is useful when
|
|
66
|
+
results are too large to transfer efficiently, or when you want to
|
|
67
|
+
process them later with other tools.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from wherobots.db import connect, Store, StorageFormat
|
|
71
|
+
from wherobots.db.region import Region
|
|
72
|
+
from wherobots.db.runtime import Runtime
|
|
73
|
+
|
|
74
|
+
with connect(
|
|
75
|
+
api_key='...',
|
|
76
|
+
runtime=Runtime.TINY,
|
|
77
|
+
region=Region.AWS_US_WEST_2) as conn:
|
|
78
|
+
curr = conn.cursor()
|
|
79
|
+
|
|
80
|
+
# Store results with a presigned URL for easy download
|
|
81
|
+
curr.execute(
|
|
82
|
+
"SELECT * FROM wherobots_open_data.overture.places LIMIT 1000",
|
|
83
|
+
store=Store.for_download()
|
|
84
|
+
)
|
|
85
|
+
store_result = curr.get_store_result()
|
|
86
|
+
print(f"Results stored at: {store_result.result_uri}")
|
|
87
|
+
print(f"Size: {store_result.size} bytes")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The `Store` class supports the following options:
|
|
91
|
+
|
|
92
|
+
* `format`: output format - `StorageFormat.PARQUET` (default),
|
|
93
|
+
`StorageFormat.CSV`, or `StorageFormat.GEOJSON`
|
|
94
|
+
* `single`: if `True`, write results to a single file instead of
|
|
95
|
+
multiple partitioned files (default: `True`)
|
|
96
|
+
* `generate_presigned_url`: if `True`, generate a presigned URL for
|
|
97
|
+
downloading results (default: `False`)
|
|
98
|
+
|
|
99
|
+
Use `Store.for_download()` as a convenient shorthand for storing results
|
|
100
|
+
as a single Parquet file with a presigned URL.
|
|
101
|
+
|
|
62
102
|
### Runtime and region selection
|
|
63
103
|
|
|
64
104
|
You can chose the Wherobots runtime you want to use using the `runtime`
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "wherobots-python-dbapi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.23.0"
|
|
4
4
|
description = "Python DB-API driver for Wherobots DB"
|
|
5
5
|
authors = [{ name = "Maxime Petazzoni", email = "max@wherobots.com" }]
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.10, <4"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
license = "Apache-2.0"
|
|
9
9
|
dependencies = [
|
|
@@ -10,8 +10,10 @@ from .errors import (
|
|
|
10
10
|
ProgrammingError,
|
|
11
11
|
NotSupportedError,
|
|
12
12
|
)
|
|
13
|
+
from .models import Store, StoreResult
|
|
13
14
|
from .region import Region
|
|
14
15
|
from .runtime import Runtime
|
|
16
|
+
from .types import StorageFormat
|
|
15
17
|
|
|
16
18
|
__all__ = [
|
|
17
19
|
"Connection",
|
|
@@ -27,4 +29,7 @@ __all__ = [
|
|
|
27
29
|
"NotSupportedError",
|
|
28
30
|
"Region",
|
|
29
31
|
"Runtime",
|
|
32
|
+
"Store",
|
|
33
|
+
"StorageFormat",
|
|
34
|
+
"StoreResult",
|
|
30
35
|
]
|
|
@@ -4,7 +4,7 @@ import textwrap
|
|
|
4
4
|
import threading
|
|
5
5
|
import uuid
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import Any, Callable,
|
|
7
|
+
from typing import Any, Callable, Dict
|
|
8
8
|
|
|
9
9
|
import pandas
|
|
10
10
|
import pyarrow
|
|
@@ -13,8 +13,11 @@ import websockets.exceptions
|
|
|
13
13
|
import websockets.protocol
|
|
14
14
|
import websockets.sync.client
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
|
|
16
|
+
from .constants import DEFAULT_READ_TIMEOUT_SECONDS
|
|
17
|
+
from .cursor import Cursor
|
|
18
|
+
from .errors import NotSupportedError, OperationalError
|
|
19
|
+
from .models import ExecutionResult, Store, StoreResult
|
|
20
|
+
from .types import (
|
|
18
21
|
RequestKind,
|
|
19
22
|
EventKind,
|
|
20
23
|
ExecutionState,
|
|
@@ -22,8 +25,6 @@ from wherobots.db.constants import (
|
|
|
22
25
|
DataCompression,
|
|
23
26
|
GeometryRepresentation,
|
|
24
27
|
)
|
|
25
|
-
from wherobots.db.cursor import Cursor
|
|
26
|
-
from wherobots.db.errors import NotSupportedError, OperationalError
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
@dataclass
|
|
@@ -32,6 +33,7 @@ class Query:
|
|
|
32
33
|
execution_id: str
|
|
33
34
|
state: ExecutionState
|
|
34
35
|
handler: Callable[[Any], None]
|
|
36
|
+
store: Store | None = None
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
class Connection:
|
|
@@ -53,9 +55,9 @@ class Connection:
|
|
|
53
55
|
self,
|
|
54
56
|
ws: websockets.sync.client.ClientConnection,
|
|
55
57
|
read_timeout: float = DEFAULT_READ_TIMEOUT_SECONDS,
|
|
56
|
-
results_format:
|
|
57
|
-
data_compression:
|
|
58
|
-
geometry_representation:
|
|
58
|
+
results_format: ResultsFormat | None = None,
|
|
59
|
+
data_compression: DataCompression | None = None,
|
|
60
|
+
geometry_representation: GeometryRepresentation | None = None,
|
|
59
61
|
):
|
|
60
62
|
self.__ws = ws
|
|
61
63
|
self.__read_timeout = read_timeout
|
|
@@ -132,8 +134,26 @@ class Connection:
|
|
|
132
134
|
|
|
133
135
|
if query.state == ExecutionState.SUCCEEDED:
|
|
134
136
|
# On a state_updated event telling us the query succeeded,
|
|
135
|
-
#
|
|
137
|
+
# check if results are stored in cloud storage or need to be fetched.
|
|
136
138
|
if kind == EventKind.STATE_UPDATED:
|
|
139
|
+
result_uri = message.get("result_uri")
|
|
140
|
+
if result_uri:
|
|
141
|
+
# Results are stored in cloud storage
|
|
142
|
+
store_result = StoreResult(
|
|
143
|
+
result_uri=result_uri,
|
|
144
|
+
size=message.get("size"),
|
|
145
|
+
)
|
|
146
|
+
logging.info(
|
|
147
|
+
"Query %s results stored at: %s (size: %s)",
|
|
148
|
+
execution_id,
|
|
149
|
+
result_uri,
|
|
150
|
+
store_result.size,
|
|
151
|
+
)
|
|
152
|
+
query.state = ExecutionState.COMPLETED
|
|
153
|
+
query.handler(ExecutionResult(store_result=store_result))
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# No store configured, request results normally
|
|
137
157
|
self.__request_results(execution_id)
|
|
138
158
|
return
|
|
139
159
|
|
|
@@ -144,13 +164,15 @@ class Connection:
|
|
|
144
164
|
return
|
|
145
165
|
|
|
146
166
|
query.state = ExecutionState.COMPLETED
|
|
147
|
-
query.handler(
|
|
167
|
+
query.handler(
|
|
168
|
+
ExecutionResult(results=self._handle_results(execution_id, results))
|
|
169
|
+
)
|
|
148
170
|
elif query.state == ExecutionState.CANCELLED:
|
|
149
171
|
logging.info(
|
|
150
172
|
"Query %s has been cancelled; returning empty results.",
|
|
151
173
|
execution_id,
|
|
152
174
|
)
|
|
153
|
-
query.handler(pandas.DataFrame())
|
|
175
|
+
query.handler(ExecutionResult(results=pandas.DataFrame()))
|
|
154
176
|
self.__queries.pop(execution_id)
|
|
155
177
|
elif query.state == ExecutionState.FAILED:
|
|
156
178
|
# Don't do anything here; the ERROR event is coming with more
|
|
@@ -159,7 +181,7 @@ class Connection:
|
|
|
159
181
|
elif kind == EventKind.ERROR:
|
|
160
182
|
query.state = ExecutionState.FAILED
|
|
161
183
|
error = message.get("message")
|
|
162
|
-
query.handler(OperationalError(error))
|
|
184
|
+
query.handler(ExecutionResult(error=OperationalError(error)))
|
|
163
185
|
else:
|
|
164
186
|
logging.warning("Received unknown %s event!", kind)
|
|
165
187
|
|
|
@@ -200,7 +222,12 @@ class Connection:
|
|
|
200
222
|
raise ValueError("Unexpected frame type received")
|
|
201
223
|
return message
|
|
202
224
|
|
|
203
|
-
def __execute_sql(
|
|
225
|
+
def __execute_sql(
|
|
226
|
+
self,
|
|
227
|
+
sql: str,
|
|
228
|
+
handler: Callable[[Any], None],
|
|
229
|
+
store: Store | None = None,
|
|
230
|
+
) -> str:
|
|
204
231
|
"""Triggers the execution of the given SQL query."""
|
|
205
232
|
execution_id = str(uuid.uuid4())
|
|
206
233
|
request = {
|
|
@@ -209,11 +236,19 @@ class Connection:
|
|
|
209
236
|
"statement": sql,
|
|
210
237
|
}
|
|
211
238
|
|
|
239
|
+
if store:
|
|
240
|
+
request["store"] = {
|
|
241
|
+
"format": store.format.value,
|
|
242
|
+
"single": str(store.single).lower(),
|
|
243
|
+
"generate_presigned_url": str(store.generate_presigned_url).lower(),
|
|
244
|
+
}
|
|
245
|
+
|
|
212
246
|
self.__queries[execution_id] = Query(
|
|
213
247
|
sql=sql,
|
|
214
248
|
execution_id=execution_id,
|
|
215
249
|
state=ExecutionState.EXECUTION_REQUESTED,
|
|
216
250
|
handler=handler,
|
|
251
|
+
store=store,
|
|
217
252
|
)
|
|
218
253
|
|
|
219
254
|
logging.info(
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from packaging.version import Version
|
|
2
|
+
|
|
3
|
+
from .region import Region
|
|
4
|
+
from .runtime import Runtime
|
|
5
|
+
from .session_type import SessionType
|
|
6
|
+
from .types import StorageFormat
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DEFAULT_ENDPOINT: str = "api.cloud.wherobots.com" # "api.cloud.wherobots.com"
|
|
10
|
+
STAGING_ENDPOINT: str = "api.staging.wherobots.com" # "api.staging.wherobots.com"
|
|
11
|
+
|
|
12
|
+
DEFAULT_RUNTIME: Runtime = Runtime.TINY
|
|
13
|
+
DEFAULT_REGION: Region = Region.AWS_US_WEST_2
|
|
14
|
+
DEFAULT_SESSION_TYPE: SessionType = SessionType.MULTI
|
|
15
|
+
DEFAULT_STORAGE_FORMAT: StorageFormat = StorageFormat.PARQUET
|
|
16
|
+
DEFAULT_READ_TIMEOUT_SECONDS: float = 0.25
|
|
17
|
+
DEFAULT_SESSION_WAIT_TIMEOUT_SECONDS: float = 900
|
|
18
|
+
|
|
19
|
+
MAX_MESSAGE_SIZE: int = 100 * 2**20 # 100MiB
|
|
20
|
+
PROTOCOL_VERSION: Version = Version("1.0.0")
|
|
21
|
+
|
|
22
|
+
PARAM_STYLE = "pyformat"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import queue
|
|
2
|
-
from typing import Any,
|
|
2
|
+
from typing import Any, List, Tuple, Dict
|
|
3
3
|
|
|
4
|
-
from .errors import
|
|
4
|
+
from .errors import ProgrammingError
|
|
5
|
+
from .models import ExecutionResult, Store, StoreResult
|
|
5
6
|
|
|
6
7
|
_TYPE_MAP = {
|
|
7
8
|
"object": "STRING",
|
|
@@ -20,20 +21,21 @@ class Cursor:
|
|
|
20
21
|
self.__cancel_fn = cancel_fn
|
|
21
22
|
|
|
22
23
|
self.__queue: queue.Queue = queue.Queue()
|
|
23
|
-
self.__results:
|
|
24
|
-
self.
|
|
24
|
+
self.__results: list[Any] | None = None
|
|
25
|
+
self.__store_result: StoreResult | None = None
|
|
26
|
+
self.__current_execution_id: str | None = None
|
|
25
27
|
self.__current_row: int = 0
|
|
26
28
|
|
|
27
29
|
# Description and row count are set by the last executed operation.
|
|
28
30
|
# Their default values are defined by PEP-0249.
|
|
29
|
-
self.__description:
|
|
31
|
+
self.__description: List[Tuple] | None = None
|
|
30
32
|
self.__rowcount: int = -1
|
|
31
33
|
|
|
32
34
|
# Array-size is also defined by PEP-0249 and is expected to be read/writable.
|
|
33
35
|
self.arraysize: int = 1
|
|
34
36
|
|
|
35
37
|
@property
|
|
36
|
-
def description(self) ->
|
|
38
|
+
def description(self) -> List[Tuple] | None:
|
|
37
39
|
return self.__description
|
|
38
40
|
|
|
39
41
|
@property
|
|
@@ -43,47 +45,78 @@ class Cursor:
|
|
|
43
45
|
def __on_execution_result(self, result) -> None:
|
|
44
46
|
self.__queue.put(result)
|
|
45
47
|
|
|
46
|
-
def __get_results(self) ->
|
|
48
|
+
def __get_results(self) -> List[Tuple[Any, ...]] | None:
|
|
47
49
|
if not self.__current_execution_id:
|
|
48
50
|
raise ProgrammingError("No query has been executed yet")
|
|
49
51
|
if self.__results is not None:
|
|
50
52
|
return self.__results
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
if isinstance(
|
|
54
|
-
raise result
|
|
54
|
+
execution_result = self.__queue.get()
|
|
55
|
+
if not isinstance(execution_result, ExecutionResult):
|
|
56
|
+
raise ProgrammingError("Unexpected result type")
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if execution_result.error:
|
|
59
|
+
raise execution_result.error
|
|
60
|
+
|
|
61
|
+
self.__store_result = execution_result.store_result
|
|
62
|
+
results = execution_result.results
|
|
63
|
+
|
|
64
|
+
# Results is None when results are stored in cloud storage
|
|
65
|
+
if results is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
self.__rowcount = len(results)
|
|
69
|
+
self.__results = results
|
|
70
|
+
if not results.empty:
|
|
59
71
|
self.__description = [
|
|
60
72
|
(
|
|
61
73
|
col_name, # name
|
|
62
|
-
_TYPE_MAP.get(str(
|
|
74
|
+
_TYPE_MAP.get(str(results[col_name].dtype), "STRING"), # type_code
|
|
63
75
|
None, # display_size
|
|
64
|
-
|
|
76
|
+
results[col_name].memory_usage(), # internal_size
|
|
65
77
|
None, # precision
|
|
66
78
|
None, # scale
|
|
67
79
|
True, # null_ok; Assuming all columns can accept NULL values
|
|
68
80
|
)
|
|
69
|
-
for col_name in
|
|
81
|
+
for col_name in results.columns
|
|
70
82
|
]
|
|
71
83
|
|
|
72
84
|
return self.__results
|
|
73
85
|
|
|
74
|
-
def execute(
|
|
86
|
+
def execute(
|
|
87
|
+
self,
|
|
88
|
+
operation: str,
|
|
89
|
+
parameters: Dict[str, Any] | None = None,
|
|
90
|
+
store: Store | None = None,
|
|
91
|
+
) -> None:
|
|
75
92
|
if self.__current_execution_id:
|
|
76
93
|
self.__cancel_fn(self.__current_execution_id)
|
|
77
94
|
|
|
78
95
|
self.__results = None
|
|
96
|
+
self.__store_result = None
|
|
79
97
|
self.__current_row = 0
|
|
80
98
|
self.__rowcount = -1
|
|
81
99
|
self.__description = None
|
|
82
100
|
|
|
83
101
|
self.__current_execution_id = self.__exec_fn(
|
|
84
|
-
operation % (parameters or {}), self.__on_execution_result
|
|
102
|
+
operation % (parameters or {}), self.__on_execution_result, store
|
|
85
103
|
)
|
|
86
104
|
|
|
105
|
+
def get_store_result(self) -> StoreResult | None:
|
|
106
|
+
"""Get the store result for the last executed query.
|
|
107
|
+
|
|
108
|
+
Returns the StoreResult containing the URI and size of the stored
|
|
109
|
+
results, or None if the query was not configured to store results.
|
|
110
|
+
|
|
111
|
+
This method blocks until the query completes.
|
|
112
|
+
"""
|
|
113
|
+
if not self.__current_execution_id:
|
|
114
|
+
raise ProgrammingError("No query has been executed yet")
|
|
115
|
+
|
|
116
|
+
# Ensure we've waited for the result
|
|
117
|
+
self.__get_results()
|
|
118
|
+
return self.__store_result
|
|
119
|
+
|
|
87
120
|
def executemany(
|
|
88
121
|
self, operation: str, seq_of_parameters: List[Dict[str, Any]]
|
|
89
122
|
) -> None:
|
|
@@ -27,11 +27,6 @@ from .constants import (
|
|
|
27
27
|
MAX_MESSAGE_SIZE,
|
|
28
28
|
PARAM_STYLE,
|
|
29
29
|
PROTOCOL_VERSION,
|
|
30
|
-
AppStatus,
|
|
31
|
-
DataCompression,
|
|
32
|
-
GeometryRepresentation,
|
|
33
|
-
ResultsFormat,
|
|
34
|
-
SessionType,
|
|
35
30
|
)
|
|
36
31
|
from .errors import (
|
|
37
32
|
InterfaceError,
|
|
@@ -39,6 +34,13 @@ from .errors import (
|
|
|
39
34
|
)
|
|
40
35
|
from .region import Region
|
|
41
36
|
from .runtime import Runtime
|
|
37
|
+
from .session_type import SessionType
|
|
38
|
+
from .types import (
|
|
39
|
+
AppStatus,
|
|
40
|
+
DataCompression,
|
|
41
|
+
GeometryRepresentation,
|
|
42
|
+
ResultsFormat,
|
|
43
|
+
)
|
|
42
44
|
|
|
43
45
|
apilevel = "2.0"
|
|
44
46
|
threadsafety = 1
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
import pandas
|
|
4
|
+
|
|
5
|
+
from .constants import DEFAULT_STORAGE_FORMAT
|
|
6
|
+
from .types import StorageFormat
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class StoreResult:
|
|
11
|
+
"""Result information when a query's results are stored to cloud storage.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
result_uri: The URI or presigned URL of the stored result.
|
|
15
|
+
size: The size of the stored result in bytes, or None if not available.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
result_uri: str
|
|
19
|
+
size: int | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Store:
|
|
24
|
+
"""Configuration for storing query results to cloud storage.
|
|
25
|
+
|
|
26
|
+
When passed to cursor.execute(), query results will be written to cloud
|
|
27
|
+
storage instead of being returned directly over the WebSocket connection.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
format: The storage format (parquet, csv, or geojson). Defaults to parquet.
|
|
31
|
+
single: If True, store as a single file. If False, store as multiple files.
|
|
32
|
+
generate_presigned_url: If True, generate a presigned URL for the result.
|
|
33
|
+
Requires single=True.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
format: StorageFormat
|
|
37
|
+
single: bool = False
|
|
38
|
+
generate_presigned_url: bool = False
|
|
39
|
+
|
|
40
|
+
def __post_init__(self) -> None:
|
|
41
|
+
if self.generate_presigned_url and not self.single:
|
|
42
|
+
raise ValueError("Presigned URL can only be generated when single=True")
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def for_download(cls, format: StorageFormat | None = None) -> "Store":
|
|
46
|
+
"""Create a configuration for downloading results via a presigned URL.
|
|
47
|
+
|
|
48
|
+
This is a convenience method that creates a configuration with
|
|
49
|
+
single file mode and presigned URL generation enabled.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
format: The storage format.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A Store configured for single-file download with presigned URL.
|
|
56
|
+
"""
|
|
57
|
+
return cls(
|
|
58
|
+
format=format or DEFAULT_STORAGE_FORMAT,
|
|
59
|
+
single=True,
|
|
60
|
+
generate_presigned_url=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ExecutionResult:
|
|
66
|
+
"""Result of a query execution.
|
|
67
|
+
|
|
68
|
+
This class encapsulates all possible outcomes of a query execution:
|
|
69
|
+
a DataFrame result, an error, or a store result (when results are
|
|
70
|
+
written to cloud storage).
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
results: The query results as a pandas DataFrame, or None if an error occurred.
|
|
74
|
+
error: The error that occurred during execution, or None if successful.
|
|
75
|
+
store_result: The store result if results were written to cloud storage.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
results: pandas.DataFrame | None = None
|
|
79
|
+
error: Exception | None = None
|
|
80
|
+
store_result: StoreResult | None = None
|
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
from enum import auto
|
|
2
|
-
from packaging.version import Version
|
|
3
2
|
from strenum import LowercaseStrEnum, StrEnum
|
|
4
3
|
|
|
5
|
-
from .region import Region
|
|
6
|
-
from .runtime import Runtime
|
|
7
|
-
from .session_type import SessionType
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
DEFAULT_ENDPOINT: str = "api.cloud.wherobots.com" # "api.cloud.wherobots.com"
|
|
11
|
-
STAGING_ENDPOINT: str = "api.staging.wherobots.com" # "api.staging.wherobots.com"
|
|
12
|
-
|
|
13
|
-
DEFAULT_RUNTIME: Runtime = Runtime.TINY
|
|
14
|
-
DEFAULT_REGION: Region = Region.AWS_US_WEST_2
|
|
15
|
-
DEFAULT_SESSION_TYPE: SessionType = SessionType.MULTI
|
|
16
|
-
DEFAULT_READ_TIMEOUT_SECONDS: float = 0.25
|
|
17
|
-
DEFAULT_SESSION_WAIT_TIMEOUT_SECONDS: float = 900
|
|
18
|
-
|
|
19
|
-
MAX_MESSAGE_SIZE: int = 100 * 2**20 # 100MiB
|
|
20
|
-
PROTOCOL_VERSION: Version = Version("1.0.0")
|
|
21
|
-
|
|
22
|
-
PARAM_STYLE = "pyformat"
|
|
23
|
-
|
|
24
4
|
|
|
25
5
|
class ExecutionState(LowercaseStrEnum):
|
|
26
6
|
IDLE = auto()
|
|
@@ -84,6 +64,12 @@ class GeometryRepresentation(LowercaseStrEnum):
|
|
|
84
64
|
GEOJSON = auto()
|
|
85
65
|
|
|
86
66
|
|
|
67
|
+
class StorageFormat(LowercaseStrEnum):
|
|
68
|
+
PARQUET = auto()
|
|
69
|
+
CSV = auto()
|
|
70
|
+
GEOJSON = auto()
|
|
71
|
+
|
|
72
|
+
|
|
87
73
|
class AppStatus(StrEnum):
|
|
88
74
|
PENDING = auto()
|
|
89
75
|
PREPARING = auto()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wherobots_python_dbapi-0.22.0 → wherobots_python_dbapi-0.23.0}/wherobots/db/session_type.py
RENAMED
|
File without changes
|