duckstring 0.2.0__tar.gz → 0.3.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.
- duckstring-0.3.0/MANIFEST.in +8 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/PKG-INFO +6 -5
- {duckstring-0.2.0 → duckstring-0.3.0}/README.md +1 -4
- {duckstring-0.2.0 → duckstring-0.3.0}/pyproject.toml +14 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/__init__.py +0 -2
- duckstring-0.3.0/src/duckstring/acc.py +13 -0
- duckstring-0.3.0/src/duckstring/agg.py +12 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/app.py +20 -7
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/db.py +18 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/driver.py +687 -25
- duckstring-0.3.0/src/duckstring/catchment/poller.py +238 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/__init__.py +6 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/catchment.py +8 -0
- duckstring-0.3.0/src/duckstring/catchment/routes/data.py +532 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/deploy.py +56 -1
- duckstring-0.3.0/src/duckstring/catchment/routes/draw.py +110 -0
- duckstring-0.3.0/src/duckstring/catchment/routes/duct.py +98 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/orchestrate.py +100 -7
- duckstring-0.3.0/src/duckstring/catchment/routes/view.py +97 -0
- duckstring-0.3.0/src/duckstring/catchment/schema/002_ducts.sql +41 -0
- duckstring-0.3.0/src/duckstring/catchment/schema/003_pull_m.sql +5 -0
- duckstring-0.3.0/src/duckstring/catchment/schema/004_identity.sql +15 -0
- duckstring-0.3.0/src/duckstring/catchment/schema/005_pond_version_schema.sql +12 -0
- duckstring-0.3.0/src/duckstring/catchment/schema/006_refresh.sql +7 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/404.html +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next.__PAGE__.txt +2 -2
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._full.txt +3 -3
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._head.txt +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._index.txt +2 -2
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._tree.txt +2 -2
- duckstring-0.3.0/src/duckstring/catchment/static/_next/static/chunks/0d-69yy-ja-ef.css +1 -0
- duckstring-0.3.0/src/duckstring/catchment/static/_next/static/chunks/0l6vjq3ae5vov.js +2 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._full.txt +2 -2
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._head.txt +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._index.txt +2 -2
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._not-found.txt +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._tree.txt +2 -2
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found.html +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found.txt +2 -2
- duckstring-0.3.0/src/duckstring/catchment/static/index.html +1 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/index.txt +3 -3
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/__init__.py +8 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/_http.py +4 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/catchment.py +1 -1
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/control.py +38 -0
- duckstring-0.3.0/src/duckstring/cli/duct.py +190 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/pond.py +33 -11
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/core.py +176 -19
- duckstring-0.3.0/src/duckstring/dataplane.py +518 -0
- duckstring-0.3.0/src/duckstring/demo/catalog/README.md +12 -0
- duckstring-0.3.0/src/duckstring/demo/catalog/pond.toml +4 -0
- duckstring-0.3.0/src/duckstring/demo/catalog/src/pond.py +61 -0
- duckstring-0.3.0/src/duckstring/demo/orders/README.md +11 -0
- duckstring-0.3.0/src/duckstring/demo/orders/pond.toml +4 -0
- duckstring-0.3.0/src/duckstring/demo/orders/src/pond.py +52 -0
- duckstring-0.3.0/src/duckstring/demo/priced/README.md +20 -0
- duckstring-0.3.0/src/duckstring/demo/priced/pond.toml +7 -0
- duckstring-0.3.0/src/duckstring/demo/priced/src/pond.py +23 -0
- duckstring-0.3.0/src/duckstring/demo/priced/src/puddles.py +59 -0
- duckstring-0.3.0/src/duckstring/demo/reports/.gitignore +8 -0
- duckstring-0.3.0/src/duckstring/demo/revenue/.gitignore +8 -0
- duckstring-0.3.0/src/duckstring/demo/revenue/README.md +12 -0
- duckstring-0.3.0/src/duckstring/demo/revenue/pond.toml +7 -0
- duckstring-0.3.0/src/duckstring/demo/revenue/src/pond.py +25 -0
- duckstring-0.3.0/src/duckstring/demo/revenue/src/puddles.py +35 -0
- duckstring-0.3.0/src/duckstring/demo/sales/.gitignore +8 -0
- duckstring-0.3.0/src/duckstring/demo/transactions/.gitignore +8 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/__main__.py +9 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/core.py +33 -4
- duckstring-0.3.0/src/duckstring/duck/executor.py +187 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/__init__.py +4 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/catchment.py +81 -19
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/core.py +17 -0
- duckstring-0.3.0/src/duckstring/iceberg_catalog.py +204 -0
- duckstring-0.3.0/src/duckstring/iceberg_plane.py +288 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/keys.py +6 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/runner.py +47 -13
- duckstring-0.3.0/src/duckstring/schema_contract.py +67 -0
- duckstring-0.3.0/src/duckstring/trickle/__init__.py +71 -0
- duckstring-0.3.0/src/duckstring/trickle/acc.py +139 -0
- duckstring-0.3.0/src/duckstring/trickle/agg.py +209 -0
- duckstring-0.3.0/src/duckstring/trickle/builder.py +1069 -0
- duckstring-0.3.0/src/duckstring/trickle/context.py +53 -0
- duckstring-0.3.0/src/duckstring/trickle/io.py +2283 -0
- duckstring-0.3.0/src/duckstring/trickle_builder.py +13 -0
- duckstring-0.3.0/src/duckstring/trickle_io.py +15 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/PKG-INFO +6 -5
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/SOURCES.txt +48 -27
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/requires.txt +5 -0
- duckstring-0.2.0/src/duckstring/catchment/routes/data.py +0 -143
- duckstring-0.2.0/src/duckstring/catchment/static/_next/static/chunks/0pgf-_4l3d1gl.css +0 -1
- duckstring-0.2.0/src/duckstring/catchment/static/_next/static/chunks/0qh0m742pxib~.js +0 -2
- duckstring-0.2.0/src/duckstring/catchment/static/index.html +0 -1
- duckstring-0.2.0/src/duckstring/duck/executor.py +0 -142
- duckstring-0.2.0/tests/test_auth.py +0 -258
- duckstring-0.2.0/tests/test_catchment.py +0 -289
- duckstring-0.2.0/tests/test_catchment_app.py +0 -674
- duckstring-0.2.0/tests/test_config.py +0 -70
- duckstring-0.2.0/tests/test_core.py +0 -94
- duckstring-0.2.0/tests/test_data.py +0 -148
- duckstring-0.2.0/tests/test_deploy.py +0 -146
- duckstring-0.2.0/tests/test_download.py +0 -77
- duckstring-0.2.0/tests/test_duck.py +0 -175
- duckstring-0.2.0/tests/test_engine.py +0 -591
- duckstring-0.2.0/tests/test_engine_split.py +0 -228
- duckstring-0.2.0/tests/test_execute.py +0 -149
- duckstring-0.2.0/tests/test_multimajor.py +0 -188
- duckstring-0.2.0/tests/test_platform.py +0 -155
- duckstring-0.2.0/tests/test_pond.py +0 -156
- duckstring-0.2.0/tests/test_puddle.py +0 -392
- duckstring-0.2.0/tests/test_restart.py +0 -98
- duckstring-0.2.0/tests/test_runtime.py +0 -233
- duckstring-0.2.0/tests/test_static.py +0 -23
- duckstring-0.2.0/tests/test_status.py +0 -198
- duckstring-0.2.0/tests/test_status_api.py +0 -233
- duckstring-0.2.0/tests/test_window.py +0 -193
- {duckstring-0.2.0 → duckstring-0.3.0}/LICENSE +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/setup.cfg +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/__main__.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/__init__.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/asgi.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/dag.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/launcher.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/registry.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/duck.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/schema/001_init.sql +0 -0
- {duckstring-0.2.0/src/duckstring/catchment/static/_next/static/uFzbxKIUibAOY83mw7FJ7 → duckstring-0.3.0/src/duckstring/catchment/static/_next/static/Z066xVKlXhWUYRNRq4sUP}/_buildManifest.js +0 -0
- {duckstring-0.2.0/src/duckstring/catchment/static/_next/static/uFzbxKIUibAOY83mw7FJ7 → duckstring-0.3.0/src/duckstring/catchment/static/_next/static/Z066xVKlXhWUYRNRq4sUP}/_clientMiddlewareManifest.js +0 -0
- {duckstring-0.2.0/src/duckstring/catchment/static/_next/static/uFzbxKIUibAOY83mw7FJ7 → duckstring-0.3.0/src/duckstring/catchment/static/_next/static/Z066xVKlXhWUYRNRq4sUP}/_ssgManifest.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/03~yq9q893hmn.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/07lhk_q6pmm3r.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/0dbhjjzl8qfwv.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/0jvmviuftg5e2.css +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/0kczw6usu-y-f.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/11kjahy2ntf0n.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/turbopack-03~mbvk_uplk_.js +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/favicon.svg +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/logo-mark.svg +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/logo.svg +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/config.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/data.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/deploy.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/puddle.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/status.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/trigger.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/window.py +0 -0
- {duckstring-0.2.0/src/duckstring/demo/products → duckstring-0.3.0/src/duckstring/demo/catalog}/.gitignore +0 -0
- {duckstring-0.2.0/src/duckstring/demo/reports → duckstring-0.3.0/src/duckstring/demo/orders}/.gitignore +0 -0
- {duckstring-0.2.0/src/duckstring/demo/sales → duckstring-0.3.0/src/duckstring/demo/priced}/.gitignore +0 -0
- {duckstring-0.2.0/src/duckstring/demo/transactions → duckstring-0.3.0/src/duckstring/demo/products}/.gitignore +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/products/README.md +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/products/pond.toml +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/products/src/pond.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/reports/README.md +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/reports/pond.toml +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/reports/src/pond.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/README.md +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/pond.toml +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/src/pond.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/src/puddles.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/transactions/README.md +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/transactions/pond.toml +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/transactions/src/pond.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/__init__.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/client.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/pond.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/worker.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/__init__.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/hydrate.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/project.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/utils.py +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/dependency_links.txt +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/entry_points.txt +0 -0
- {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# The sdist ships the installable package only — keep internal design notes and the test suite
|
|
2
|
+
# out of the PyPI source tarball (they're in the git repo for contributors).
|
|
3
|
+
prune tests
|
|
4
|
+
prune plans
|
|
5
|
+
prune experiment
|
|
6
|
+
prune docs
|
|
7
|
+
prune frontend
|
|
8
|
+
prune playground
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: duckstring
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Build data pipelines the way you build software: version each transform, declare its dependencies, and Duckstring resolves the execution DAG automatically.
|
|
5
5
|
Author-email: Duckstring <dev@duckstring.com>
|
|
6
6
|
License: Apache License
|
|
@@ -221,6 +221,10 @@ Requires-Dist: uvicorn[standard]>=0.29
|
|
|
221
221
|
Requires-Dist: httpx>=0.25
|
|
222
222
|
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
223
223
|
Requires-Dist: tomli-w>=1.0
|
|
224
|
+
Requires-Dist: pyiceberg>=0.7
|
|
225
|
+
Requires-Dist: pyarrow>=14
|
|
226
|
+
Requires-Dist: pytz
|
|
227
|
+
Provides-Extra: iceberg
|
|
224
228
|
Provides-Extra: dev
|
|
225
229
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
226
230
|
Requires-Dist: pytest-timeout>=0.5; extra == "dev"
|
|
@@ -244,6 +248,7 @@ The main elements:
|
|
|
244
248
|
- **Catchment**: Control environment (FastAPI + UI + CLI)
|
|
245
249
|
- **Pond**: Versioned container with declared upstream dependencies
|
|
246
250
|
- **Ripple**: Unit operation within a Pond (e.g. a single transformation producing a table)
|
|
251
|
+
- **Trickle**: DBSP-based incremental engine operating within a Ripple
|
|
247
252
|
|
|
248
253
|
Ponds are typed or referred to in context:
|
|
249
254
|
|
|
@@ -292,10 +297,6 @@ Ponds execute on demand signals sent to an Outlet, in two flavours — **push**
|
|
|
292
297
|
|
|
293
298
|
A Tide keeps an Outlet no staler than a bound (`duckstring trigger tide reports 1d`); a Wave keeps it as fresh as the pipeline can supply. See [Triggers](https://docs.duckstring.com/guides/triggers) for the full semantics.
|
|
294
299
|
|
|
295
|
-
## Scope & maturity
|
|
296
|
-
|
|
297
|
-
Duckstring targets single-node workloads (DuckDB under the hood) — pipelines in the tens of millions of rows, not big-data scale. Incremental transforms (the **Trickle** concept) are not yet implemented: transforms recompute their tables each run, with incremental state via self-reads.
|
|
298
|
-
|
|
299
300
|
## Going further
|
|
300
301
|
|
|
301
302
|
Full documentation lives at **[docs.duckstring.com](https://docs.duckstring.com)**:
|
|
@@ -14,6 +14,7 @@ The main elements:
|
|
|
14
14
|
- **Catchment**: Control environment (FastAPI + UI + CLI)
|
|
15
15
|
- **Pond**: Versioned container with declared upstream dependencies
|
|
16
16
|
- **Ripple**: Unit operation within a Pond (e.g. a single transformation producing a table)
|
|
17
|
+
- **Trickle**: DBSP-based incremental engine operating within a Ripple
|
|
17
18
|
|
|
18
19
|
Ponds are typed or referred to in context:
|
|
19
20
|
|
|
@@ -62,10 +63,6 @@ Ponds execute on demand signals sent to an Outlet, in two flavours — **push**
|
|
|
62
63
|
|
|
63
64
|
A Tide keeps an Outlet no staler than a bound (`duckstring trigger tide reports 1d`); a Wave keeps it as fresh as the pipeline can supply. See [Triggers](https://docs.duckstring.com/guides/triggers) for the full semantics.
|
|
64
65
|
|
|
65
|
-
## Scope & maturity
|
|
66
|
-
|
|
67
|
-
Duckstring targets single-node workloads (DuckDB under the hood) — pipelines in the tens of millions of rows, not big-data scale. Incremental transforms (the **Trickle** concept) are not yet implemented: transforms recompute their tables each run, with incremental state via self-reads.
|
|
68
|
-
|
|
69
66
|
## Going further
|
|
70
67
|
|
|
71
68
|
Full documentation lives at **[docs.duckstring.com](https://docs.duckstring.com)**:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "duckstring"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Build data pipelines the way you build software: version each transform, declare its dependencies, and Duckstring resolves the execution DAG automatically."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -19,9 +19,22 @@ dependencies = [
|
|
|
19
19
|
"httpx>=0.25",
|
|
20
20
|
"tomli>=2.0; python_version < '3.11'",
|
|
21
21
|
"tomli-w>=1.0",
|
|
22
|
+
# The default data plane: an Apache Iceberg base layer (snapshots + schema metadata over the
|
|
23
|
+
# Parquet files we already write). Core deps because Iceberg is default-on; set
|
|
24
|
+
# DUCKSTRING_DATA_PLANE=parquet to opt out (the lighter fallback, also for offline Catchments that
|
|
25
|
+
# can't fetch DuckDB's iceberg extension). NB: we use our own file-backed catalog
|
|
26
|
+
# (duckstring.iceberg_catalog) rather than pyiceberg's SqlCatalog, so SQLAlchemy is NOT pulled in.
|
|
27
|
+
"pyiceberg>=0.7",
|
|
28
|
+
"pyarrow>=14",
|
|
29
|
+
# DuckDB requires pytz to convert TIMESTAMPTZ columns to Python datetimes on fetch
|
|
30
|
+
# (our _duckstring_f freshness column is TIMESTAMPTZ, read back in dataplane/trickle export).
|
|
31
|
+
# It is otherwise only present transitively (pandas), so a clean install lacks it.
|
|
32
|
+
"pytz",
|
|
22
33
|
]
|
|
23
34
|
|
|
24
35
|
[project.optional-dependencies]
|
|
36
|
+
# Retained for back-compat: the Iceberg deps are in core now (Iceberg is the default plane).
|
|
37
|
+
iceberg = []
|
|
25
38
|
dev = [
|
|
26
39
|
"pytest>=8.0",
|
|
27
40
|
"pytest-timeout>=0.5",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Backwards-compatible alias — scan (order-dependent) metric specs live in :mod:`duckstring.trickle.acc`.
|
|
2
|
+
|
|
3
|
+
Kept so ``from duckstring import acc`` works alongside ``from duckstring import agg``. See
|
|
4
|
+
:mod:`duckstring.trickle_io` for the rationale.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from .trickle import acc as _acc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def __getattr__(name): # PEP 562: forward all names to the relocated module
|
|
13
|
+
return getattr(_acc, name)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Backwards-compatible alias — aggregate metric specs now live in :mod:`duckstring.trickle.agg`.
|
|
2
|
+
|
|
3
|
+
Kept so ``from duckstring import agg`` continues to work. See :mod:`duckstring.trickle_io` for the rationale.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .trickle import agg as _agg
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def __getattr__(name): # PEP 562: forward all names to the relocated module
|
|
12
|
+
return getattr(_agg, name)
|
|
@@ -11,7 +11,7 @@ from fastapi import FastAPI
|
|
|
11
11
|
from fastapi.responses import JSONResponse
|
|
12
12
|
from fastapi.staticfiles import StaticFiles
|
|
13
13
|
|
|
14
|
-
from .db import connect, migrate
|
|
14
|
+
from .db import connect, ensure_identity, migrate
|
|
15
15
|
from .driver import Driver
|
|
16
16
|
from .launcher import NoopLauncher, SubprocessLauncher
|
|
17
17
|
from .routes import router
|
|
@@ -49,22 +49,35 @@ async def _lifespan(app: FastAPI):
|
|
|
49
49
|
# Restore: resume any Pond Runs that were in flight when the Catchment last stopped.
|
|
50
50
|
driver.resume_incomplete()
|
|
51
51
|
|
|
52
|
+
from .poller import run_poller
|
|
53
|
+
|
|
54
|
+
# The poller wakes immediately when a Draw acquires demand (so it solicits its upstream at once),
|
|
55
|
+
# instead of waiting for its next cycle. The driver signals across threads via the running loop.
|
|
56
|
+
wake = asyncio.Event()
|
|
57
|
+
loop = asyncio.get_running_loop()
|
|
58
|
+
driver.set_notify(lambda: loop.call_soon_threadsafe(wake.set))
|
|
59
|
+
|
|
52
60
|
scheduler = asyncio.create_task(_scheduler(driver))
|
|
61
|
+
poller = asyncio.create_task(run_poller(driver, app.state.root, wake))
|
|
53
62
|
try:
|
|
54
63
|
yield
|
|
55
64
|
finally:
|
|
56
|
-
scheduler
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
for task in (scheduler, poller):
|
|
66
|
+
task.cancel()
|
|
67
|
+
try:
|
|
68
|
+
await task
|
|
69
|
+
except asyncio.CancelledError:
|
|
70
|
+
pass
|
|
61
71
|
launcher.shutdown_all()
|
|
62
72
|
|
|
63
73
|
|
|
64
|
-
def create_app(
|
|
74
|
+
def create_app(
|
|
75
|
+
root: Path, api_key: str | None = None, base_url: str | None = None, name: str | None = None
|
|
76
|
+
) -> FastAPI:
|
|
65
77
|
root.mkdir(parents=True, exist_ok=True)
|
|
66
78
|
con = connect(root / "duck.db")
|
|
67
79
|
migrate(con)
|
|
80
|
+
ensure_identity(con, name or os.environ.get("DUCKSTRING_CATCHMENT_NAME"))
|
|
68
81
|
|
|
69
82
|
app = FastAPI(title="Duckstring Catchment", lifespan=_lifespan)
|
|
70
83
|
app.state.root = root
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import sqlite3
|
|
2
|
+
import uuid
|
|
2
3
|
from datetime import datetime, timezone
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
_SCHEMA_DIR = Path(__file__).parent / "schema"
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
def ensure_identity(con: sqlite3.Connection, name: str | None = None) -> None:
|
|
10
|
+
"""Mint this Catchment's stable id on first start (never changes), and set/refresh its optional
|
|
11
|
+
display name. Call after migrate()."""
|
|
12
|
+
row = con.execute("SELECT value FROM catchment_meta WHERE key = 'id'").fetchone()
|
|
13
|
+
if row is None:
|
|
14
|
+
con.execute("INSERT INTO catchment_meta (key, value) VALUES ('id', ?)", (str(uuid.uuid4()),))
|
|
15
|
+
if name:
|
|
16
|
+
con.execute(
|
|
17
|
+
"INSERT INTO catchment_meta (key, value) VALUES ('name', ?) "
|
|
18
|
+
"ON CONFLICT(key) DO UPDATE SET value = excluded.value",
|
|
19
|
+
(name,),
|
|
20
|
+
)
|
|
21
|
+
con.commit()
|
|
22
|
+
|
|
23
|
+
|
|
8
24
|
def connect(path: Path) -> sqlite3.Connection:
|
|
9
25
|
con = sqlite3.connect(path, check_same_thread=False)
|
|
26
|
+
if path != Path(":memory:") and path.exists():
|
|
27
|
+
path.chmod(0o600) # may hold duct credentials (auth headers for upstream Catchments)
|
|
10
28
|
con.execute("PRAGMA foreign_keys = ON")
|
|
11
29
|
con.execute("PRAGMA journal_mode = WAL")
|
|
12
30
|
con.execute("PRAGMA busy_timeout = 5000") # queue on a locked DB (up to 5 s) instead of erroring
|