alloy-sdk 0.1.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.
- alloy_sdk-0.1.0/LICENSE +7 -0
- alloy_sdk-0.1.0/PKG-INFO +502 -0
- alloy_sdk-0.1.0/README.md +456 -0
- alloy_sdk-0.1.0/alloy/__init__.py +55 -0
- alloy_sdk-0.1.0/alloy/_version.py +4 -0
- alloy_sdk-0.1.0/alloy/py.typed +1 -0
- alloy_sdk-0.1.0/alloy/sql.py +1786 -0
- alloy_sdk-0.1.0/alloy/storage.py +1132 -0
- alloy_sdk-0.1.0/pyproject.toml +165 -0
alloy_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Alloy. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files are proprietary to Alloy.
|
|
6
|
+
Use, copying, modification, distribution, or sublicensing is permitted only
|
|
7
|
+
under a separate written agreement with Alloy.
|
alloy_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alloy-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Alloy hosted SQL and Mesh Storage APIs
|
|
5
|
+
Project-URL: Homepage, https://www.usealloy.ai/
|
|
6
|
+
Project-URL: Documentation, https://docs.usealloy.ai/
|
|
7
|
+
Project-URL: Repository, https://github.com/alloyrobotics/alloy-web
|
|
8
|
+
Author: Alloy
|
|
9
|
+
License: Proprietary License
|
|
10
|
+
|
|
11
|
+
Copyright (c) Alloy. All rights reserved.
|
|
12
|
+
|
|
13
|
+
This software and associated documentation files are proprietary to Alloy.
|
|
14
|
+
Use, copying, modification, distribution, or sublicensing is permitted only
|
|
15
|
+
under a separate written agreement with Alloy.
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Keywords: alloy,autonomy,data-lake,mcap,mesh-storage,robotics,sql
|
|
18
|
+
Classifier: Development Status :: 3 - Alpha
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: License :: Other/Proprietary License
|
|
21
|
+
Classifier: Operating System :: MacOS
|
|
22
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Topic :: Database
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: <3.13,>=3.12
|
|
30
|
+
Requires-Dist: aioboto3>=15.5.0
|
|
31
|
+
Requires-Dist: boto3>=1.40.61
|
|
32
|
+
Requires-Dist: httpx>=0.27.0
|
|
33
|
+
Requires-Dist: pyarrow>=15.0.0
|
|
34
|
+
Requires-Dist: requests>=2.33.0
|
|
35
|
+
Provides-Extra: dataframes
|
|
36
|
+
Requires-Dist: duckdb>=1.0.0; extra == 'dataframes'
|
|
37
|
+
Requires-Dist: pandas<3,>=2.0.0; extra == 'dataframes'
|
|
38
|
+
Requires-Dist: polars>=1.0.0; extra == 'dataframes'
|
|
39
|
+
Provides-Extra: duckdb
|
|
40
|
+
Requires-Dist: duckdb>=1.0.0; extra == 'duckdb'
|
|
41
|
+
Provides-Extra: pandas
|
|
42
|
+
Requires-Dist: pandas<3,>=2.0.0; extra == 'pandas'
|
|
43
|
+
Provides-Extra: polars
|
|
44
|
+
Requires-Dist: polars>=1.0.0; extra == 'polars'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# Alloy Python SDK
|
|
48
|
+
|
|
49
|
+
Public-ready Python SDK package for hosted Alloy APIs.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
Requires Python 3.12. The first public SDK release intentionally supports
|
|
54
|
+
Python 3.12 only; the SDK quality workflow tests Linux and macOS on Python 3.12.
|
|
55
|
+
|
|
56
|
+
After the PyPI release is live:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install alloy-sdk
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Before the PyPI release, install from this repository checkout for validation:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv pip install ./packages/python/alloy-sdk
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Storage
|
|
69
|
+
|
|
70
|
+
Use `alloy.storage` to upload local files into Mesh Storage, list uploaded
|
|
71
|
+
files, and download files by key. The SDK handles the transfer details.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from alloy import storage
|
|
75
|
+
|
|
76
|
+
with storage.connect() as store:
|
|
77
|
+
upload = store.upload_folder(
|
|
78
|
+
"local/run-001",
|
|
79
|
+
path="flights/run-001",
|
|
80
|
+
overwrite=False,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
print(upload.prefix)
|
|
84
|
+
# uploads/sdk-uploads/flights/run-001/
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Single-file uploads use the same folder-style `path`:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from alloy import storage
|
|
91
|
+
|
|
92
|
+
with storage.connect() as store:
|
|
93
|
+
store.upload_file("local/run-001/run.mcap", path="flights/run-001", overwrite=False)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`path` is a Mesh folder under `uploads/sdk-uploads/`; do not include
|
|
97
|
+
`uploads/`, `sdk-uploads/`, a bucket name, or a trailing slash. If `path` is
|
|
98
|
+
omitted, Alloy generates a dated folder such as `2026-06-24/<uuid>`.
|
|
99
|
+
|
|
100
|
+
Read helpers accept either an SDK upload `path` or an existing Mesh prefix.
|
|
101
|
+
Downloads use exact Mesh keys:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from alloy import storage
|
|
105
|
+
|
|
106
|
+
with storage.connect() as store:
|
|
107
|
+
listing = store.list_files(path="flights/run-001")
|
|
108
|
+
for file in listing:
|
|
109
|
+
print(file.key, file.size)
|
|
110
|
+
|
|
111
|
+
store.download_file(
|
|
112
|
+
"uploads/sdk-uploads/flights/run-001/run.mcap",
|
|
113
|
+
"run.mcap",
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Async clients mirror the sync methods:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from alloy import storage
|
|
121
|
+
|
|
122
|
+
async with storage.async_connect() as store:
|
|
123
|
+
await store.upload_folder(
|
|
124
|
+
"local/run-001",
|
|
125
|
+
path="flights/run-001",
|
|
126
|
+
overwrite=False,
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The SDK does not expose delete, move, rename, or overwrite operations in v1.
|
|
131
|
+
`overwrite=True` is rejected; delete/replace workflows must go through Mesh
|
|
132
|
+
deletion semantics.
|
|
133
|
+
|
|
134
|
+
### Storage Security Model
|
|
135
|
+
|
|
136
|
+
Storage helpers request short-lived, path-scoped R2 credentials from the Alloy
|
|
137
|
+
data API and use them only for the transfer. Do not log `UploadSession`,
|
|
138
|
+
`ReadSession`, `UploadResult`, or raw credential values, and do not hand
|
|
139
|
+
temporary credentials to untrusted code. Upload sessions are scoped to
|
|
140
|
+
`uploads/sdk-uploads/<path>/`; raw object-store credentials can still perform
|
|
141
|
+
S3-compatible writes inside that temporary scope until expiry, so the SDK keeps
|
|
142
|
+
`overwrite=False` fixed and does not expose delete, move, rename, or overwrite
|
|
143
|
+
helpers in v1.
|
|
144
|
+
|
|
145
|
+
## Hosted SQL
|
|
146
|
+
|
|
147
|
+
Hosted SQL v1 has one public transport: HTTPS `POST /mesh/query` returning Apache
|
|
148
|
+
Arrow IPC stream bytes. The SDK keeps the public API small:
|
|
149
|
+
|
|
150
|
+
- `fetch*` for small row-oriented results.
|
|
151
|
+
- `fetch_rows` for capped Python rowsets with columns, CSV output, and
|
|
152
|
+
truncation metadata.
|
|
153
|
+
- `query` for an Arrow-backed result object.
|
|
154
|
+
- `query_arrow`, `query_pandas`, `query_polars`, and `query_df` for direct formats.
|
|
155
|
+
- `stream` for batch iteration.
|
|
156
|
+
|
|
157
|
+
### Connect
|
|
158
|
+
|
|
159
|
+
The SDK reads `ALLOY_DATA_URL` and `ALLOY_API_KEY`.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
export ALLOY_DATA_URL="https://data.usealloy.ai"
|
|
163
|
+
export ALLOY_API_KEY="ak_..."
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from alloy import sql
|
|
168
|
+
|
|
169
|
+
with sql.connect() as db:
|
|
170
|
+
count = db.fetchval("SELECT count(*) FROM alloy.fleet.diagnostics")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Pass explicit values when you do not want environment-based config:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from alloy import sql
|
|
177
|
+
|
|
178
|
+
with sql.connect(
|
|
179
|
+
base_url="https://data.usealloy.ai",
|
|
180
|
+
api_key="ak_...",
|
|
181
|
+
) as db:
|
|
182
|
+
rows = db.fetch(
|
|
183
|
+
"""
|
|
184
|
+
SELECT topic, count(*) AS n
|
|
185
|
+
FROM alloy.fleet.diagnostics
|
|
186
|
+
GROUP BY topic
|
|
187
|
+
"""
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use the data API host provided by Alloy for your environment. Common hosts are
|
|
192
|
+
`https://data.usealloy.ai` for production, `https://data-adnav.usealloy.ai` for
|
|
193
|
+
Adnav production, and `https://data-dev.usealloy.ai` for development testing.
|
|
194
|
+
|
|
195
|
+
### Small Row Results
|
|
196
|
+
|
|
197
|
+
Use `fetch`, `fetchrow`, and `fetchval` when you want ordinary Python values for
|
|
198
|
+
a bounded result set. This is the most compact shape for scripts, dashboards,
|
|
199
|
+
backend routes, and MCP tools.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from alloy import sql
|
|
203
|
+
|
|
204
|
+
p = sql.param
|
|
205
|
+
|
|
206
|
+
with sql.connect() as db:
|
|
207
|
+
count = db.fetchval("SELECT count(*) FROM alloy.fleet.diagnostics")
|
|
208
|
+
|
|
209
|
+
latest = db.fetchrow(
|
|
210
|
+
"""
|
|
211
|
+
SELECT d.topic, d.created_at
|
|
212
|
+
FROM alloy.fleet.diagnostics AS d
|
|
213
|
+
JOIN alloy.mesh.file_meta AS fm
|
|
214
|
+
ON fm.file_id = d.file_id
|
|
215
|
+
AND fm.key = 'alloy.mission_id'
|
|
216
|
+
WHERE fm.value = $mission_id
|
|
217
|
+
ORDER BY d.created_at DESC
|
|
218
|
+
LIMIT 1
|
|
219
|
+
""",
|
|
220
|
+
params={"mission_id": p.utf8("2U8NUGFHGifdCt7tdcnwTd")},
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
rows = db.fetch(
|
|
224
|
+
"""
|
|
225
|
+
SELECT d.topic, count(*) AS n
|
|
226
|
+
FROM alloy.fleet.diagnostics AS d
|
|
227
|
+
JOIN alloy.mesh.file_meta AS fm
|
|
228
|
+
ON fm.file_id = d.file_id
|
|
229
|
+
AND fm.key = 'alloy.mission_id'
|
|
230
|
+
WHERE fm.value = $mission_id
|
|
231
|
+
GROUP BY d.topic
|
|
232
|
+
ORDER BY n DESC
|
|
233
|
+
LIMIT 100
|
|
234
|
+
""",
|
|
235
|
+
params={"mission_id": p.utf8("2U8NUGFHGifdCt7tdcnwTd")},
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Use `fetch_rows` when the caller needs result metadata, CSV output, or an
|
|
240
|
+
explicit client-side cap in one object:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from alloy import sql
|
|
244
|
+
|
|
245
|
+
with sql.connect() as db:
|
|
246
|
+
rowset = db.fetch_rows(
|
|
247
|
+
"""
|
|
248
|
+
SELECT topic, created_at, payload
|
|
249
|
+
FROM alloy.fleet.diagnostics
|
|
250
|
+
ORDER BY created_at DESC
|
|
251
|
+
LIMIT 1000
|
|
252
|
+
""",
|
|
253
|
+
max_rows=1000,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
print(rowset.columns)
|
|
257
|
+
print(rowset.row_count)
|
|
258
|
+
print(rowset.to_csv())
|
|
259
|
+
|
|
260
|
+
if rowset.truncated:
|
|
261
|
+
print("Result was capped locally; add or tighten LIMIT in SQL.")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`max_rows` is a client materialization cap. It avoids building unbounded Python
|
|
265
|
+
row lists, but it does not rewrite the SQL, reduce hosted SQL work, or reduce
|
|
266
|
+
the bytes the v1 endpoint sends. Use SQL `LIMIT` for performance and `max_rows`
|
|
267
|
+
as a final guardrail at the SDK boundary.
|
|
268
|
+
|
|
269
|
+
### Arrow-Backed Results
|
|
270
|
+
|
|
271
|
+
Use `query` when you want metadata plus flexible conversion. The sync result is
|
|
272
|
+
script-friendly and exposes ClickHouse-style row helpers.
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from alloy import sql
|
|
276
|
+
|
|
277
|
+
with sql.connect() as db:
|
|
278
|
+
result = db.query("SELECT * FROM alloy.fleet.diagnostics LIMIT 100")
|
|
279
|
+
|
|
280
|
+
result.column_names
|
|
281
|
+
result.column_types
|
|
282
|
+
result.row_count
|
|
283
|
+
result.result_rows
|
|
284
|
+
result.first_row
|
|
285
|
+
result.first_item
|
|
286
|
+
|
|
287
|
+
for row in result.named_results():
|
|
288
|
+
print(row["topic"])
|
|
289
|
+
|
|
290
|
+
arrow_table = result.to_arrow()
|
|
291
|
+
pandas_df = result.to_pandas()
|
|
292
|
+
polars_df = result.to_polars()
|
|
293
|
+
duckdb_conn = result.to_duckdb(table_name="diagnostics")
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Direct format helpers are shortcuts over the same `query` path:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
with sql.connect() as db:
|
|
300
|
+
table = db.query_arrow("SELECT * FROM alloy.fleet.diagnostics LIMIT 100")
|
|
301
|
+
df = db.query_df("SELECT * FROM alloy.fleet.diagnostics LIMIT 100")
|
|
302
|
+
pandas_df = db.query_pandas("SELECT * FROM alloy.fleet.diagnostics LIMIT 100")
|
|
303
|
+
polars_df = db.query_polars("SELECT * FROM alloy.fleet.diagnostics LIMIT 100")
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Async Usage
|
|
307
|
+
|
|
308
|
+
The async client mirrors the sync client. Use the same method names with
|
|
309
|
+
`await`. Async HTTP is native, Arrow IPC decode runs off the event loop, and
|
|
310
|
+
expensive result materialization is async-only.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from alloy import sql
|
|
314
|
+
|
|
315
|
+
async with sql.async_connect() as db:
|
|
316
|
+
count = await db.fetchval("SELECT count(*) FROM alloy.fleet.diagnostics")
|
|
317
|
+
|
|
318
|
+
latest = await db.fetchrow(
|
|
319
|
+
"""
|
|
320
|
+
SELECT fm.value AS mission_id, d.topic, d.created_at
|
|
321
|
+
FROM alloy.fleet.diagnostics AS d
|
|
322
|
+
LEFT JOIN alloy.mesh.file_meta AS fm
|
|
323
|
+
ON fm.file_id = d.file_id
|
|
324
|
+
AND fm.key = 'alloy.mission_id'
|
|
325
|
+
ORDER BY d.created_at DESC
|
|
326
|
+
LIMIT 1
|
|
327
|
+
"""
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
rows = await db.fetch("SELECT topic FROM alloy.fleet.diagnostics LIMIT 100")
|
|
331
|
+
|
|
332
|
+
rowset = await db.fetch_rows(
|
|
333
|
+
"SELECT topic, created_at FROM alloy.fleet.diagnostics LIMIT 1000",
|
|
334
|
+
max_rows=1000,
|
|
335
|
+
)
|
|
336
|
+
csv_text = await rowset.to_csv()
|
|
337
|
+
if rowset.truncated:
|
|
338
|
+
raise RuntimeError("Hosted SQL result exceeded the local row cap")
|
|
339
|
+
|
|
340
|
+
result = await db.query("SELECT * FROM alloy.fleet.diagnostics LIMIT 100")
|
|
341
|
+
table = result.to_arrow()
|
|
342
|
+
df = await result.to_pandas()
|
|
343
|
+
polars_df = await result.to_polars()
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Streaming Batches
|
|
347
|
+
|
|
348
|
+
Use `stream` for batch-oriented reads. The sync stream decodes directly from the
|
|
349
|
+
HTTP response. The async stream uses async HTTP and decodes Arrow IPC batches in
|
|
350
|
+
a worker thread; v1 still reads the response body before decoding batches.
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from alloy import sql
|
|
354
|
+
|
|
355
|
+
with sql.connect() as db:
|
|
356
|
+
with db.stream("SELECT * FROM alloy.fleet.diagnostics") as stream:
|
|
357
|
+
for batch in stream:
|
|
358
|
+
print(batch.num_rows)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from alloy import sql
|
|
363
|
+
|
|
364
|
+
async with sql.async_connect() as db:
|
|
365
|
+
async with db.stream("SELECT * FROM alloy.fleet.diagnostics") as stream:
|
|
366
|
+
async with stream.reader() as reader:
|
|
367
|
+
print(reader.schema.names)
|
|
368
|
+
async for batch in reader:
|
|
369
|
+
print(batch.num_rows)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Params
|
|
373
|
+
|
|
374
|
+
Use named typed params instead of formatting values into SQL strings. Params are
|
|
375
|
+
values only: not table names, column names, identifiers, clauses, or SQL
|
|
376
|
+
fragments. The SDK never interpolates params into SQL client-side.
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from alloy import sql
|
|
380
|
+
|
|
381
|
+
p = sql.param
|
|
382
|
+
|
|
383
|
+
with sql.connect() as db:
|
|
384
|
+
rows = db.fetch(
|
|
385
|
+
"""
|
|
386
|
+
SELECT d.created_at, d.payload
|
|
387
|
+
FROM alloy.fleet.diagnostics AS d
|
|
388
|
+
JOIN alloy.mesh.file_meta AS fm
|
|
389
|
+
ON fm.file_id = d.file_id
|
|
390
|
+
AND fm.key = 'alloy.mission_id'
|
|
391
|
+
WHERE fm.value = $mission_id
|
|
392
|
+
AND d.created_at >= $since
|
|
393
|
+
AND d.score >= $min_score
|
|
394
|
+
ORDER BY d.created_at DESC
|
|
395
|
+
LIMIT 100
|
|
396
|
+
""",
|
|
397
|
+
params={
|
|
398
|
+
"mission_id": p.utf8("2U8NUGFHGifdCt7tdcnwTd"),
|
|
399
|
+
"since": p.timestamp_us("2026-06-01T00:00:00Z"),
|
|
400
|
+
"min_score": p.float64(0.8),
|
|
401
|
+
},
|
|
402
|
+
)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Params are named only: SQL uses `$mission_id`, and the params key is
|
|
406
|
+
`"mission_id"`. Positional placeholders like `$1` are not supported in v1.
|
|
407
|
+
Supported types are `utf8`, `bool`, `int64`, `float64`, `date32`,
|
|
408
|
+
`timestamp_us`, and `binary`. `int64` is encoded as a decimal string on the
|
|
409
|
+
wire so values are not rounded by JavaScript gateways; use `sql.param.int64(...)`
|
|
410
|
+
instead of hand-writing that payload.
|
|
411
|
+
|
|
412
|
+
### Local DuckDB
|
|
413
|
+
|
|
414
|
+
Local DuckDB helpers run over the downloaded Arrow result. They do not push work
|
|
415
|
+
back to hosted SQL.
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
with sql.connect() as db:
|
|
419
|
+
result = db.query(
|
|
420
|
+
"""
|
|
421
|
+
SELECT topic, count(*) AS n
|
|
422
|
+
FROM alloy.fleet.diagnostics
|
|
423
|
+
GROUP BY topic
|
|
424
|
+
"""
|
|
425
|
+
)
|
|
426
|
+
summary = result.local_sql("SELECT sum(n) FROM result")
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
async with sql.async_connect() as db:
|
|
431
|
+
result = await db.query(
|
|
432
|
+
"""
|
|
433
|
+
SELECT topic, count(*) AS n
|
|
434
|
+
FROM alloy.fleet.diagnostics
|
|
435
|
+
GROUP BY topic
|
|
436
|
+
"""
|
|
437
|
+
)
|
|
438
|
+
summary = await result.local_sql("SELECT sum(n) FROM result")
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## V1 Contract
|
|
442
|
+
|
|
443
|
+
- Supported: HTTPS `POST /mesh/query`, bearer API-key auth, Arrow IPC stream output.
|
|
444
|
+
- Supported: PyArrow-first results, with Pandas, Polars, and DuckDB helpers.
|
|
445
|
+
- Supported: named typed SQL value params via `params={...}`.
|
|
446
|
+
- Not supported in v1: public Flight SQL/gRPC, Postgres wire protocol, ODBC/JDBC
|
|
447
|
+
BI plug-and-play, cursors, sessions, or stream resume.
|
|
448
|
+
- If a stream fails partway through, rerun the query.
|
|
449
|
+
- Pointer-follow is for projected payload fields. Filtering on pointered payload
|
|
450
|
+
fields is not a performance contract.
|
|
451
|
+
- Nested filters are evaluated correctly, but arbitrary nested predicate
|
|
452
|
+
pushdown is separate performance hardening work.
|
|
453
|
+
|
|
454
|
+
## Release Validation
|
|
455
|
+
|
|
456
|
+
The production release flow is documented in [RELEASE.md](RELEASE.md). Public
|
|
457
|
+
SDK releases are shipped by merging the bot-created `release/sdk-vX.Y.Z` ->
|
|
458
|
+
`release/sdk` release PR. That merge publishes through PyPI Trusted Publishing
|
|
459
|
+
after the protected `pypi` environment is approved.
|
|
460
|
+
|
|
461
|
+
Humans set only the release line in `alloy/_version.py`, such as `0.1`. The
|
|
462
|
+
release PR workflow stamps the generated branch with the next available patch
|
|
463
|
+
version, such as `0.1.0` or `0.1.1`.
|
|
464
|
+
|
|
465
|
+
Before marking a release candidate ready, run the local SDK quality gate from
|
|
466
|
+
`packages/python/alloy-sdk`:
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
uv sync --all-extras --frozen
|
|
470
|
+
uv run ruff format --check .
|
|
471
|
+
uv run ruff check .
|
|
472
|
+
uv run ty check
|
|
473
|
+
uv run pip-audit --local --progress-spinner off
|
|
474
|
+
uv run pytest -q
|
|
475
|
+
rm -rf dist
|
|
476
|
+
uv build --out-dir dist
|
|
477
|
+
uv run python scripts/validate_release.py
|
|
478
|
+
uv run python scripts/validate_release_scope.py --base origin/main --head HEAD
|
|
479
|
+
uv run python scripts/validate_distribution.py dist
|
|
480
|
+
uv run twine check dist/*
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Then smoke-test the built wheel in a clean environment:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
smoke_venv=$(mktemp -d)
|
|
487
|
+
uv venv "$smoke_venv" --python 3.12
|
|
488
|
+
uv pip install --python "$smoke_venv/bin/python" dist/*.whl
|
|
489
|
+
"$smoke_venv/bin/python" scripts/smoke_installed_wheel.py
|
|
490
|
+
rm -rf "$smoke_venv"
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
For live release validation, set `ALLOY_DATA_URL` and `ALLOY_API_KEY`, then run:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
uv run python scripts/live_smoke.py
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The live smoke runs SQL plus sync and async Storage upload/list/download,
|
|
500
|
+
exact-key conflict, and cross-prefix denial checks. It writes tiny objects under
|
|
501
|
+
`uploads/sdk-uploads/sdk-live-smoke/` and leaves them in place because SDK v1
|
|
502
|
+
intentionally has no delete helper.
|