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.
Files changed (175) hide show
  1. duckstring-0.3.0/MANIFEST.in +8 -0
  2. {duckstring-0.2.0 → duckstring-0.3.0}/PKG-INFO +6 -5
  3. {duckstring-0.2.0 → duckstring-0.3.0}/README.md +1 -4
  4. {duckstring-0.2.0 → duckstring-0.3.0}/pyproject.toml +14 -1
  5. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/__init__.py +0 -2
  6. duckstring-0.3.0/src/duckstring/acc.py +13 -0
  7. duckstring-0.3.0/src/duckstring/agg.py +12 -0
  8. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/app.py +20 -7
  9. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/db.py +18 -0
  10. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/driver.py +687 -25
  11. duckstring-0.3.0/src/duckstring/catchment/poller.py +238 -0
  12. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/__init__.py +6 -0
  13. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/catchment.py +8 -0
  14. duckstring-0.3.0/src/duckstring/catchment/routes/data.py +532 -0
  15. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/deploy.py +56 -1
  16. duckstring-0.3.0/src/duckstring/catchment/routes/draw.py +110 -0
  17. duckstring-0.3.0/src/duckstring/catchment/routes/duct.py +98 -0
  18. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/orchestrate.py +100 -7
  19. duckstring-0.3.0/src/duckstring/catchment/routes/view.py +97 -0
  20. duckstring-0.3.0/src/duckstring/catchment/schema/002_ducts.sql +41 -0
  21. duckstring-0.3.0/src/duckstring/catchment/schema/003_pull_m.sql +5 -0
  22. duckstring-0.3.0/src/duckstring/catchment/schema/004_identity.sql +15 -0
  23. duckstring-0.3.0/src/duckstring/catchment/schema/005_pond_version_schema.sql +12 -0
  24. duckstring-0.3.0/src/duckstring/catchment/schema/006_refresh.sql +7 -0
  25. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/404.html +1 -1
  26. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next.__PAGE__.txt +2 -2
  27. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._full.txt +3 -3
  28. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._head.txt +1 -1
  29. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._index.txt +2 -2
  30. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/__next._tree.txt +2 -2
  31. duckstring-0.3.0/src/duckstring/catchment/static/_next/static/chunks/0d-69yy-ja-ef.css +1 -0
  32. duckstring-0.3.0/src/duckstring/catchment/static/_next/static/chunks/0l6vjq3ae5vov.js +2 -0
  33. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._full.txt +2 -2
  34. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._head.txt +1 -1
  35. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._index.txt +2 -2
  36. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
  37. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._not-found.txt +1 -1
  38. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found/__next._tree.txt +2 -2
  39. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found.html +1 -1
  40. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_not-found.txt +2 -2
  41. duckstring-0.3.0/src/duckstring/catchment/static/index.html +1 -0
  42. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/index.txt +3 -3
  43. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/__init__.py +8 -1
  44. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/_http.py +4 -0
  45. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/catchment.py +1 -1
  46. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/control.py +38 -0
  47. duckstring-0.3.0/src/duckstring/cli/duct.py +190 -0
  48. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/pond.py +33 -11
  49. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/core.py +176 -19
  50. duckstring-0.3.0/src/duckstring/dataplane.py +518 -0
  51. duckstring-0.3.0/src/duckstring/demo/catalog/README.md +12 -0
  52. duckstring-0.3.0/src/duckstring/demo/catalog/pond.toml +4 -0
  53. duckstring-0.3.0/src/duckstring/demo/catalog/src/pond.py +61 -0
  54. duckstring-0.3.0/src/duckstring/demo/orders/README.md +11 -0
  55. duckstring-0.3.0/src/duckstring/demo/orders/pond.toml +4 -0
  56. duckstring-0.3.0/src/duckstring/demo/orders/src/pond.py +52 -0
  57. duckstring-0.3.0/src/duckstring/demo/priced/README.md +20 -0
  58. duckstring-0.3.0/src/duckstring/demo/priced/pond.toml +7 -0
  59. duckstring-0.3.0/src/duckstring/demo/priced/src/pond.py +23 -0
  60. duckstring-0.3.0/src/duckstring/demo/priced/src/puddles.py +59 -0
  61. duckstring-0.3.0/src/duckstring/demo/reports/.gitignore +8 -0
  62. duckstring-0.3.0/src/duckstring/demo/revenue/.gitignore +8 -0
  63. duckstring-0.3.0/src/duckstring/demo/revenue/README.md +12 -0
  64. duckstring-0.3.0/src/duckstring/demo/revenue/pond.toml +7 -0
  65. duckstring-0.3.0/src/duckstring/demo/revenue/src/pond.py +25 -0
  66. duckstring-0.3.0/src/duckstring/demo/revenue/src/puddles.py +35 -0
  67. duckstring-0.3.0/src/duckstring/demo/sales/.gitignore +8 -0
  68. duckstring-0.3.0/src/duckstring/demo/transactions/.gitignore +8 -0
  69. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/__main__.py +9 -0
  70. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/core.py +33 -4
  71. duckstring-0.3.0/src/duckstring/duck/executor.py +187 -0
  72. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/__init__.py +4 -0
  73. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/catchment.py +81 -19
  74. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/core.py +17 -0
  75. duckstring-0.3.0/src/duckstring/iceberg_catalog.py +204 -0
  76. duckstring-0.3.0/src/duckstring/iceberg_plane.py +288 -0
  77. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/keys.py +6 -0
  78. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/runner.py +47 -13
  79. duckstring-0.3.0/src/duckstring/schema_contract.py +67 -0
  80. duckstring-0.3.0/src/duckstring/trickle/__init__.py +71 -0
  81. duckstring-0.3.0/src/duckstring/trickle/acc.py +139 -0
  82. duckstring-0.3.0/src/duckstring/trickle/agg.py +209 -0
  83. duckstring-0.3.0/src/duckstring/trickle/builder.py +1069 -0
  84. duckstring-0.3.0/src/duckstring/trickle/context.py +53 -0
  85. duckstring-0.3.0/src/duckstring/trickle/io.py +2283 -0
  86. duckstring-0.3.0/src/duckstring/trickle_builder.py +13 -0
  87. duckstring-0.3.0/src/duckstring/trickle_io.py +15 -0
  88. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/PKG-INFO +6 -5
  89. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/SOURCES.txt +48 -27
  90. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/requires.txt +5 -0
  91. duckstring-0.2.0/src/duckstring/catchment/routes/data.py +0 -143
  92. duckstring-0.2.0/src/duckstring/catchment/static/_next/static/chunks/0pgf-_4l3d1gl.css +0 -1
  93. duckstring-0.2.0/src/duckstring/catchment/static/_next/static/chunks/0qh0m742pxib~.js +0 -2
  94. duckstring-0.2.0/src/duckstring/catchment/static/index.html +0 -1
  95. duckstring-0.2.0/src/duckstring/duck/executor.py +0 -142
  96. duckstring-0.2.0/tests/test_auth.py +0 -258
  97. duckstring-0.2.0/tests/test_catchment.py +0 -289
  98. duckstring-0.2.0/tests/test_catchment_app.py +0 -674
  99. duckstring-0.2.0/tests/test_config.py +0 -70
  100. duckstring-0.2.0/tests/test_core.py +0 -94
  101. duckstring-0.2.0/tests/test_data.py +0 -148
  102. duckstring-0.2.0/tests/test_deploy.py +0 -146
  103. duckstring-0.2.0/tests/test_download.py +0 -77
  104. duckstring-0.2.0/tests/test_duck.py +0 -175
  105. duckstring-0.2.0/tests/test_engine.py +0 -591
  106. duckstring-0.2.0/tests/test_engine_split.py +0 -228
  107. duckstring-0.2.0/tests/test_execute.py +0 -149
  108. duckstring-0.2.0/tests/test_multimajor.py +0 -188
  109. duckstring-0.2.0/tests/test_platform.py +0 -155
  110. duckstring-0.2.0/tests/test_pond.py +0 -156
  111. duckstring-0.2.0/tests/test_puddle.py +0 -392
  112. duckstring-0.2.0/tests/test_restart.py +0 -98
  113. duckstring-0.2.0/tests/test_runtime.py +0 -233
  114. duckstring-0.2.0/tests/test_static.py +0 -23
  115. duckstring-0.2.0/tests/test_status.py +0 -198
  116. duckstring-0.2.0/tests/test_status_api.py +0 -233
  117. duckstring-0.2.0/tests/test_window.py +0 -193
  118. {duckstring-0.2.0 → duckstring-0.3.0}/LICENSE +0 -0
  119. {duckstring-0.2.0 → duckstring-0.3.0}/setup.cfg +0 -0
  120. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/__main__.py +0 -0
  121. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/__init__.py +0 -0
  122. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/asgi.py +0 -0
  123. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/dag.py +0 -0
  124. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/launcher.py +0 -0
  125. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/registry.py +0 -0
  126. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/routes/duck.py +0 -0
  127. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/schema/001_init.sql +0 -0
  128. {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
  129. {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
  130. {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
  131. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/03~yq9q893hmn.js +0 -0
  132. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/07lhk_q6pmm3r.js +0 -0
  133. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/0dbhjjzl8qfwv.js +0 -0
  134. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/0jvmviuftg5e2.css +0 -0
  135. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/0kczw6usu-y-f.js +0 -0
  136. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/11kjahy2ntf0n.js +0 -0
  137. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/_next/static/chunks/turbopack-03~mbvk_uplk_.js +0 -0
  138. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/favicon.svg +0 -0
  139. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/logo-mark.svg +0 -0
  140. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/catchment/static/logo.svg +0 -0
  141. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/config.py +0 -0
  142. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/data.py +0 -0
  143. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/deploy.py +0 -0
  144. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/puddle.py +0 -0
  145. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/status.py +0 -0
  146. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/trigger.py +0 -0
  147. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/cli/window.py +0 -0
  148. {duckstring-0.2.0/src/duckstring/demo/products → duckstring-0.3.0/src/duckstring/demo/catalog}/.gitignore +0 -0
  149. {duckstring-0.2.0/src/duckstring/demo/reports → duckstring-0.3.0/src/duckstring/demo/orders}/.gitignore +0 -0
  150. {duckstring-0.2.0/src/duckstring/demo/sales → duckstring-0.3.0/src/duckstring/demo/priced}/.gitignore +0 -0
  151. {duckstring-0.2.0/src/duckstring/demo/transactions → duckstring-0.3.0/src/duckstring/demo/products}/.gitignore +0 -0
  152. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/products/README.md +0 -0
  153. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/products/pond.toml +0 -0
  154. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/products/src/pond.py +0 -0
  155. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/reports/README.md +0 -0
  156. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/reports/pond.toml +0 -0
  157. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/reports/src/pond.py +0 -0
  158. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/README.md +0 -0
  159. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/pond.toml +0 -0
  160. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/src/pond.py +0 -0
  161. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/sales/src/puddles.py +0 -0
  162. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/transactions/README.md +0 -0
  163. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/transactions/pond.toml +0 -0
  164. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/demo/transactions/src/pond.py +0 -0
  165. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/__init__.py +0 -0
  166. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/duck/client.py +0 -0
  167. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/pond.py +0 -0
  168. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/engine/worker.py +0 -0
  169. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/__init__.py +0 -0
  170. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/hydrate.py +0 -0
  171. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/local/project.py +0 -0
  172. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring/utils.py +0 -0
  173. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/dependency_links.txt +0 -0
  174. {duckstring-0.2.0 → duckstring-0.3.0}/src/duckstring.egg-info/entry_points.txt +0 -0
  175. {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.2.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.2.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",
@@ -6,7 +6,6 @@ from .core import (
6
6
  Pond,
7
7
  Puddle,
8
8
  Ripple,
9
- Trickle,
10
9
  puddle,
11
10
  ripple,
12
11
  )
@@ -21,7 +20,6 @@ __all__ = [
21
20
  "Pond",
22
21
  "Puddle",
23
22
  "Ripple",
24
- "Trickle",
25
23
  "puddle",
26
24
  "ripple",
27
25
  "__version__",
@@ -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.cancel()
57
- try:
58
- await scheduler
59
- except asyncio.CancelledError:
60
- pass
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(root: Path, api_key: str | None = None, base_url: str | None = None) -> FastAPI:
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