interlaced 2.0.0a3__tar.gz → 2.0.2__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 (158) hide show
  1. interlaced-2.0.2/MANIFEST.in +2 -0
  2. {interlaced-2.0.0a3 → interlaced-2.0.2}/PKG-INFO +84 -37
  3. {interlaced-2.0.0a3 → interlaced-2.0.2}/README.md +70 -28
  4. {interlaced-2.0.0a3 → interlaced-2.0.2}/pyproject.toml +18 -12
  5. interlaced-2.0.2/src/interlace/__init__.py +26 -0
  6. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/cli/main.py +319 -40
  7. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/config/config.py +27 -1
  8. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/dsl/decorators.py +81 -16
  9. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/dsl/discovery.py +25 -8
  10. interlaced-2.0.0a3/src/interlace/engines/postgres.py → interlaced-2.0.2/src/interlace/engines/adbc.py +63 -61
  11. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/engines/base.py +10 -1
  12. interlaced-2.0.2/src/interlace/engines/bigquery.py +45 -0
  13. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/engines/duckdb.py +106 -38
  14. interlaced-2.0.2/src/interlace/engines/postgres.py +73 -0
  15. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/engines/quack.py +4 -1
  16. interlaced-2.0.2/src/interlace/engines/redshift.py +29 -0
  17. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/engines/registry.py +1 -4
  18. interlaced-2.0.2/src/interlace/engines/snowflake.py +41 -0
  19. interlaced-2.0.2/src/interlace/engines/spark.py +160 -0
  20. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/exceptions.py +0 -12
  21. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/graph/column_lineage.py +43 -0
  22. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/graph/dag.py +0 -3
  23. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/graph/project.py +52 -17
  24. interlaced-2.0.2/src/interlace/graph/selectors.py +87 -0
  25. interlaced-2.0.2/src/interlace/ir/__init__.py +5 -0
  26. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/ir/canonicalize.py +11 -5
  27. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/ir/fingerprint.py +2 -2
  28. interlaced-2.0.2/src/interlace/ir/relation.py +43 -0
  29. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/plan/apply.py +250 -98
  30. interlaced-2.0.2/src/interlace/plan/differ.py +386 -0
  31. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/plan/plan.py +38 -21
  32. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/plan/resolve.py +4 -0
  33. interlaced-2.0.2/src/interlace/plan/run.py +125 -0
  34. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/project.py +46 -22
  35. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/runtime/python_model.py +9 -4
  36. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/scheduler/worker.py +54 -1
  37. interlaced-2.0.2/src/interlace/service/app.py +1565 -0
  38. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/service/auth.py +3 -2
  39. interlaced-2.0.2/src/interlace/service/ui/app.css +356 -0
  40. interlaced-2.0.2/src/interlace/service/ui/favicon.svg +1 -0
  41. interlaced-2.0.2/src/interlace/service/ui/index.html +65 -0
  42. interlaced-2.0.2/src/interlace/service/ui/js/api.js +113 -0
  43. interlaced-2.0.2/src/interlace/service/ui/js/app.js +274 -0
  44. interlaced-2.0.2/src/interlace/service/ui/js/dag.js +433 -0
  45. interlaced-2.0.2/src/interlace/service/ui/js/ui.js +209 -0
  46. interlaced-2.0.2/src/interlace/service/ui/js/views/checks.js +0 -0
  47. interlaced-2.0.2/src/interlace/service/ui/js/views/environments.js +231 -0
  48. interlaced-2.0.2/src/interlace/service/ui/js/views/lineage.js +118 -0
  49. interlaced-2.0.2/src/interlace/service/ui/js/views/models.js +313 -0
  50. interlaced-2.0.2/src/interlace/service/ui/js/views/overview.js +93 -0
  51. interlaced-2.0.2/src/interlace/service/ui/js/views/plan.js +181 -0
  52. interlaced-2.0.2/src/interlace/service/ui/js/views/query.js +242 -0
  53. interlaced-2.0.2/src/interlace/service/ui/js/views/runs.js +264 -0
  54. interlaced-2.0.2/src/interlace/service/ui/js/views/streams.js +202 -0
  55. interlaced-2.0.2/src/interlace/service/ui/js/views/system.js +294 -0
  56. interlaced-2.0.2/src/interlace/sinks.py +49 -0
  57. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/state/interval.py +14 -0
  58. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/state/janitor.py +77 -0
  59. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/state/snapshot.py +0 -4
  60. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/state/store.py +215 -19
  61. interlaced-2.0.2/src/interlace/strategies/__init__.py +79 -0
  62. interlaced-2.0.2/src/interlace/strategies/append.py +46 -0
  63. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/strategies/base.py +7 -2
  64. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/strategies/full_merge.py +3 -4
  65. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/strategies/incremental_by_time.py +2 -3
  66. interlaced-2.0.2/src/interlace/strategies/merge.py +115 -0
  67. interlaced-2.0.0a3/src/interlace/strategies/full.py → interlaced-2.0.2/src/interlace/strategies/replace.py +9 -5
  68. interlaced-2.0.2/src/interlace/strategies/replace_in_place.py +48 -0
  69. interlaced-2.0.2/src/interlace/strategies/scd.py +203 -0
  70. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/strategies/view.py +2 -3
  71. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/streaming/log.py +84 -57
  72. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/streaming/materializer.py +43 -13
  73. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/streaming/schema.py +15 -4
  74. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlaced.egg-info/PKG-INFO +84 -37
  75. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlaced.egg-info/SOURCES.txt +30 -49
  76. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlaced.egg-info/requires.txt +15 -7
  77. interlaced-2.0.0a3/src/interlace/__init__.py +0 -26
  78. interlaced-2.0.0a3/src/interlace/exports.py +0 -145
  79. interlaced-2.0.0a3/src/interlace/graph/selectors.py +0 -45
  80. interlaced-2.0.0a3/src/interlace/ir/__init__.py +0 -24
  81. interlaced-2.0.0a3/src/interlace/ir/relation.py +0 -71
  82. interlaced-2.0.0a3/src/interlace/ir/schema.py +0 -23
  83. interlaced-2.0.0a3/src/interlace/plan/differ.py +0 -211
  84. interlaced-2.0.0a3/src/interlace/plan/run.py +0 -79
  85. interlaced-2.0.0a3/src/interlace/service/app.py +0 -836
  86. interlaced-2.0.0a3/src/interlace/strategies/__init__.py +0 -64
  87. interlaced-2.0.0a3/src/interlace/strategies/merge_by_key.py +0 -69
  88. interlaced-2.0.0a3/src/interlace/strategies/scd_type_2.py +0 -122
  89. interlaced-2.0.0a3/tests/test_apply.py +0 -199
  90. interlaced-2.0.0a3/tests/test_checks.py +0 -189
  91. interlaced-2.0.0a3/tests/test_cli.py +0 -102
  92. interlaced-2.0.0a3/tests/test_cli_inspect.py +0 -163
  93. interlaced-2.0.0a3/tests/test_column_lineage.py +0 -73
  94. interlaced-2.0.0a3/tests/test_compile.py +0 -74
  95. interlaced-2.0.0a3/tests/test_contracts.py +0 -35
  96. interlaced-2.0.0a3/tests/test_dag.py +0 -48
  97. interlaced-2.0.0a3/tests/test_differ.py +0 -112
  98. interlaced-2.0.0a3/tests/test_discovery.py +0 -63
  99. interlaced-2.0.0a3/tests/test_dsl.py +0 -73
  100. interlaced-2.0.0a3/tests/test_duckdb_adapter.py +0 -119
  101. interlaced-2.0.0a3/tests/test_ephemeral.py +0 -81
  102. interlaced-2.0.0a3/tests/test_example.py +0 -46
  103. interlaced-2.0.0a3/tests/test_example_benchmark.py +0 -44
  104. interlaced-2.0.0a3/tests/test_example_platform_tour.py +0 -64
  105. interlaced-2.0.0a3/tests/test_exports.py +0 -91
  106. interlaced-2.0.0a3/tests/test_fingerprint.py +0 -42
  107. interlaced-2.0.0a3/tests/test_forward_only.py +0 -207
  108. interlaced-2.0.0a3/tests/test_incremental.py +0 -170
  109. interlaced-2.0.0a3/tests/test_incremental_python.py +0 -225
  110. interlaced-2.0.0a3/tests/test_init.py +0 -52
  111. interlaced-2.0.0a3/tests/test_interval.py +0 -74
  112. interlaced-2.0.0a3/tests/test_janitor.py +0 -154
  113. interlaced-2.0.0a3/tests/test_multi_engine.py +0 -203
  114. interlaced-2.0.0a3/tests/test_postgres_engine.py +0 -198
  115. interlaced-2.0.0a3/tests/test_python_models.py +0 -197
  116. interlaced-2.0.0a3/tests/test_rebuild_skip.py +0 -160
  117. interlaced-2.0.0a3/tests/test_run.py +0 -108
  118. interlaced-2.0.0a3/tests/test_scd2.py +0 -109
  119. interlaced-2.0.0a3/tests/test_scheduler.py +0 -120
  120. interlaced-2.0.0a3/tests/test_schema_evolution.py +0 -122
  121. interlaced-2.0.0a3/tests/test_selectors.py +0 -53
  122. interlaced-2.0.0a3/tests/test_service.py +0 -346
  123. interlaced-2.0.0a3/tests/test_sql_config.py +0 -66
  124. interlaced-2.0.0a3/tests/test_state_store.py +0 -97
  125. interlaced-2.0.0a3/tests/test_storage.py +0 -145
  126. interlaced-2.0.0a3/tests/test_strategies.py +0 -107
  127. interlaced-2.0.0a3/tests/test_streaming.py +0 -230
  128. interlaced-2.0.0a3/tests/test_table_sinks.py +0 -169
  129. interlaced-2.0.0a3/tests/test_transfers.py +0 -201
  130. interlaced-2.0.0a3/tests/test_warehouse_config.py +0 -259
  131. interlaced-2.0.0a3/tests/test_worker_leases.py +0 -175
  132. {interlaced-2.0.0a3 → interlaced-2.0.2}/LICENSE +0 -0
  133. {interlaced-2.0.0a3 → interlaced-2.0.2}/setup.cfg +0 -0
  134. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/checks/__init__.py +0 -0
  135. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/checks/builtin.py +0 -0
  136. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/checks/runner.py +0 -0
  137. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/checks/spec.py +0 -0
  138. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/cli/__init__.py +0 -0
  139. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/config/__init__.py +0 -0
  140. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/contracts.py +0 -0
  141. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/dsl/__init__.py +0 -0
  142. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/dsl/sql_config.py +0 -0
  143. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/engines/__init__.py +0 -0
  144. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/graph/__init__.py +0 -0
  145. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/plan/__init__.py +0 -0
  146. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/py.typed +0 -0
  147. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/runtime/__init__.py +0 -0
  148. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/runtime/handles.py +0 -0
  149. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/scaffold.py +0 -0
  150. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/scheduler/__init__.py +0 -0
  151. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/scheduler/engine.py +0 -0
  152. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/scheduler/triggers.py +0 -0
  153. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/service/__init__.py +0 -0
  154. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/state/__init__.py +0 -0
  155. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlace/streaming/__init__.py +0 -0
  156. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlaced.egg-info/dependency_links.txt +0 -0
  157. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlaced.egg-info/entry_points.txt +0 -0
  158. {interlaced-2.0.0a3 → interlaced-2.0.2}/src/interlaced.egg-info/top_level.txt +0 -0
@@ -0,0 +1,2 @@
1
+ prune tests
2
+ prune tasks
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: interlaced
3
- Version: 2.0.0a3
3
+ Version: 2.0.2
4
4
  Summary: Python/SQL-first data platform: transformation, built-in orchestration, and durable streaming ingestion
5
5
  Author-email: Mark <mark@interlace.sh>
6
6
  License-Expression: MIT
7
7
  Keywords: data,pipeline,orchestration,transformation,etl,streaming,dbt,sqlmesh,duckdb
8
- Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: Topic :: Software Development :: Build Tools
11
11
  Classifier: Programming Language :: Python :: 3.12
@@ -13,27 +13,31 @@ Classifier: Programming Language :: SQL
13
13
  Requires-Python: >=3.12
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: sqlglot<31.0,>=25.0
16
+ Requires-Dist: sqlglot<29.0,>=25.0
17
17
  Requires-Dist: duckdb>=1.5.3
18
18
  Requires-Dist: pyarrow>=17.0
19
- Requires-Dist: msgspec<1.0,>=0.18
20
19
  Requires-Dist: pydantic<3.0,>=2.5
21
20
  Requires-Dist: typer<1.0,>=0.12
22
21
  Requires-Dist: rich<15.0,>=13.0
23
22
  Requires-Dist: cronsim<3.0,>=2.5
24
23
  Requires-Dist: tenacity<10.0,>=8.2
25
- Requires-Dist: structlog<26.0,>=24.0
26
24
  Requires-Dist: pyyaml<7.0,>=6.0
27
25
  Provides-Extra: service
28
26
  Requires-Dist: litestar<3.0,>=2.12; extra == "service"
29
27
  Requires-Dist: uvicorn<1.0,>=0.30; extra == "service"
30
- Requires-Dist: httpx<1.0,>=0.27; extra == "service"
31
- Requires-Dist: argon2-cffi<26.0,>=23.1; extra == "service"
32
- Requires-Dist: joserfc<2.0,>=1.0; extra == "service"
33
- Requires-Dist: watchfiles<2.0,>=0.22; extra == "service"
28
+ Requires-Dist: msgspec<1.0,>=0.18; extra == "service"
34
29
  Provides-Extra: adbc
35
30
  Requires-Dist: adbc-driver-manager<2.0,>=1.2; extra == "adbc"
36
31
  Requires-Dist: adbc-driver-postgresql<2.0,>=1.2; extra == "adbc"
32
+ Provides-Extra: adbc-snowflake
33
+ Requires-Dist: interlaced[adbc]; extra == "adbc-snowflake"
34
+ Requires-Dist: adbc-driver-snowflake<2.0,>=1.2; extra == "adbc-snowflake"
35
+ Provides-Extra: adbc-bigquery
36
+ Requires-Dist: interlaced[adbc]; extra == "adbc-bigquery"
37
+ Requires-Dist: adbc-driver-bigquery<2.0,>=1.2; extra == "adbc-bigquery"
38
+ Provides-Extra: spark
39
+ Requires-Dist: pyspark<4.1,>=4.0; extra == "spark"
40
+ Requires-Dist: delta-spark<4.1,>=4.0; extra == "spark"
37
41
  Provides-Extra: postgres
38
42
  Requires-Dist: psycopg[binary]<4.0,>=3.1; extra == "postgres"
39
43
  Provides-Extra: polars
@@ -44,6 +48,7 @@ Provides-Extra: all
44
48
  Requires-Dist: interlaced[adbc,polars,postgres,service]; extra == "all"
45
49
  Provides-Extra: dev
46
50
  Requires-Dist: pytest<10.0,>=8.0; extra == "dev"
51
+ Requires-Dist: httpx<1.0,>=0.27; extra == "dev"
47
52
  Requires-Dist: pytest-asyncio<2.0,>=1.0; extra == "dev"
48
53
  Requires-Dist: ruff<1.0,>=0.6; extra == "dev"
49
54
  Requires-Dist: black<27.0,>=24.0; extra == "dev"
@@ -60,11 +65,12 @@ Models are `.sql` files or Python functions; state is versioned snapshots with v
60
65
  environments and a terraform-style plan/apply; everything runs in a single daemon on
61
66
  DuckDB + DuckLake by default.
62
67
 
63
- > **Status: v2 pre-release.** This branch is a ground-up rebuild (the `interlace` package on
64
- > PyPI is the older 0.x line). APIs may still move. Requires Python 3.12+.
68
+ > **Status: 1.0.** Requires Python 3.12+.
69
+ > The package is published to PyPI as **`interlaced`**; the import name and CLI are `interlace`.
65
70
 
66
71
  ```bash
67
- uv pip install "interlaced[service]" # or from source: "interlaced[service] @ git+https://github.com/interlace-sh/interlace@v2"
72
+ pip install 'interlaced[service]' # the CLI + daemon; core CLI only: pip install interlaced
73
+ # more extras: [adbc] postgres/redshift · [spark] · [polars] · [all]
68
74
  ```
69
75
 
70
76
  ## Sixty seconds
@@ -73,7 +79,7 @@ uv pip install "interlaced[service]" # or from source: "interlaced[service] @ g
73
79
  interlace init my-project && cd my-project
74
80
  interlace plan # terraform-style preview: added / breaking / non-breaking / reuse
75
81
  interlace apply # build changed models, run checks, promote the environment
76
- interlace serve # the daemon: HTTP API + scheduler + stream ingestion, one process
82
+ interlace serve # the daemon: web UI (/ui) + HTTP API + scheduler + streams, one process
77
83
  ```
78
84
 
79
85
  Every model builds into a fingerprinted physical table (`interlace__main.orders__a1b2c3`);
@@ -89,7 +95,7 @@ prod; pass `--env dev` while developing.
89
95
 
90
96
  ```sql
91
97
  /* interlace:
92
- strategy: scd_type_2
98
+ strategy: scd
93
99
  key: customer_id
94
100
  schedule: {cron: "0 * * * *"}
95
101
  checks:
@@ -105,7 +111,7 @@ SELECT customer_id, name, tier FROM raw_customers
105
111
  ```python
106
112
  from interlace import model
107
113
 
108
- @model(strategy="merge_by_key", key="order_id", cursor="updated_at")
114
+ @model(strategy="merge", key="order_id", cursor="updated_at")
109
115
  def orders(cursor, this):
110
116
  """Incremental API extract: `cursor` is max(updated_at) already in the
111
117
  warehouse (None on first run); `this` is the previous materialisation."""
@@ -113,9 +119,9 @@ def orders(cursor, this):
113
119
  return pyarrow.Table.from_pylist(rows) # or RecordBatchReader / generator of batches
114
120
  ```
115
121
 
116
- **Strategies:** `full`, `view`, `ephemeral` (CTE-inlined), `merge_by_key` (upsert),
122
+ **Strategies:** `replace`, `view`, `ephemeral` (CTE-inlined), `merge` (upsert),
117
123
  `full_merge` (full-state source applied as a minimal diff), `incremental_by_time`
118
- (windowed, interval-ledger backfill/catchup), `scd_type_2` (history with validity windows).
124
+ (windowed, interval-ledger backfill/catchup), `scd` (history with validity windows).
119
125
 
120
126
  ## Plan / apply
121
127
 
@@ -126,22 +132,31 @@ $ interlace plan
126
132
  order_stats modified non_breaking reuse <- output provably identical: not rebuilt
127
133
  ```
128
134
 
129
- - Changes classify **breaking / non-breaking / forward-only**; downstream models whose output
130
- is provably identical **reuse their existing tables** instead of rebuilding (an improvement
131
- over model-granular invalidation).
135
+ - Changes classify **breaking / non-breaking / forward-only**; a plan with breaking changes
136
+ refuses to apply without `--force`. Downstream models whose output is provably identical
137
+ (column-pruned impact analysis) **reuse their existing tables** instead of rebuilding — an
138
+ improvement over model-granular invalidation.
132
139
  - `apply --forward-only` lets history-keeping models (scd2/merge/incremental) survive a
133
- definition change: the new logic inherits the existing table and applies going forward.
140
+ definition change: the existing table is copied to the new version, the new logic applies to
141
+ the copy going forward, and checks gate before views move.
134
142
  - **Checks gate promotion**: 10 built-in types (not_null, unique, accepted_values, row_count,
135
143
  freshness, expression, relationships, pattern, range, sql) plus `@check` Python functions —
136
- an error-severity failure blocks before the environment view moves.
137
- - `interlace gc` removes snapshots no environment references (reference-aware: tables shared
138
- through reuse survive).
144
+ an error-severity failure blocks before the environment view moves. `interlace checks run`
145
+ re-runs them ad hoc against any environment's promoted tables.
146
+ - **`--select state:modified`** scopes a plan/apply to models whose fingerprint drifted from the
147
+ target environment, plus everything downstream — the CI diff, one flag. **`interlace impact
148
+ model.column`** shows the column-level blast radius of a change.
149
+ - **`interlace env rollback`** repoints an environment's views at any earlier promotion — nothing
150
+ rebuilds, the views move; every apply records a generation, so a bad deploy reverts in one
151
+ command. `interlace gc` removes snapshots no environment references (reference-aware: tables
152
+ shared through reuse survive).
139
153
 
140
154
  ## Streaming
141
155
 
142
- Declare a stream; POST to it; rows are durable before the 200, deduplicated by idempotency
143
- key, and materialized exactly-once into `streams.<name>` — where SQL models just read them.
144
- A flush triggers the models that consume the stream.
156
+ Declare a stream; POST to it; rows are durable (SQLite WAL log) before the 200, deduplicated by
157
+ idempotency key, and materialized exactly-once into `streams.<name>` — a micro-batch flusher
158
+ commits the data and the watermark in one warehouse transaction, and SQL models just read the
159
+ table. A flush triggers the models that consume the stream.
145
160
 
146
161
  ```python
147
162
  from interlace import stream
@@ -156,7 +171,8 @@ curl -X POST localhost:8000/streams/orders -d '{"order_id": "o1", "total": 49.5}
156
171
  ```
157
172
 
158
173
  Schema drift is yours to choose: `reject` (400), `evolve` (new columns appear), or
159
- `quarantine` (bad events divert to `<stream>__quarantine`).
174
+ `quarantine` (bad events divert to `<stream>__quarantine`). When the warehouse falls behind,
175
+ publishes get **429 backpressure** instead of unbounded backlog.
160
176
 
161
177
  ## Reverse ETL
162
178
 
@@ -170,18 +186,49 @@ attach:
170
186
  ```
171
187
 
172
188
  ```sql
173
- /* interlace: {export: {to: table, target: crm.public.accounts, mode: merge_by_key, key: id}} */
189
+ /* interlace: {export: {to: table, target: crm.public.accounts, mode: merge, key: id}} */
174
190
  SELECT id, tier, lifetime_value FROM account_summary
175
191
  ```
176
192
 
177
- File exports (`to: parquet|csv|json`) work the same way.
193
+ File exports (`to: parquet|csv|json`) work the same way. Sinks are **environment-gated**: by
194
+ default the side effect fires only from prod — a dev apply never writes to a live external
195
+ table (opt in with `environments: [dev, prod]`).
196
+
197
+ ## Multi-engine
198
+
199
+ Models run on **named engines**: DuckDB/DuckLake by default, Postgres natively over ADBC
200
+ (`pip install 'interlaced[adbc]'`), Spark (beta, `[spark]` extra), plus alpha adapters for
201
+ MotherDuck, Redshift, Snowflake and BigQuery (wired and dialect-correct, not yet run against a
202
+ live account), with per-model pinning:
203
+
204
+ ```yaml
205
+ engines:
206
+ pg: {type: postgres, database: "${PG_DSN}"}
207
+ ```
208
+
209
+ ```sql
210
+ /* interlace: {engine: pg, strategy: merge, key: id} */
211
+ ```
212
+
213
+ Strategies execute *inside* the pinned engine (no DuckDB middleman); cross-engine dependencies
214
+ appear as explicit **transfer** lines in the plan and move as Arrow (or a federated `ATTACH`
215
+ fast lane when possible). Contract: `docs/architecture/MULTI_ENGINE.md`.
178
216
 
179
217
  ## The daemon
180
218
 
181
- `interlace serve` runs the HTTP API (Litestar + msgspec, OpenAPI at `/schema/scalar`), the
182
- scheduler (cron/interval triggers → durable run queue), stream ingestion, and retention in one
183
- process. Scoped API keys (`interlace apikey create ci --scope read`) lock it down; a durable
184
- event log backs `GET /events/stream` (SSE with replay).
219
+ `interlace serve` runs everything in one process:
220
+
221
+ - the **web UI** at `/ui` (in-package, zero build step) — ten views: overview, lineage canvas
222
+ with column-level tracing, models, plan/apply with SQL diffs, live runs, query console,
223
+ streams, checks, environments, and system — live over SSE;
224
+ - the **HTTP API** (Litestar + msgspec, OpenAPI at `/schema/scalar`) with the same surface as
225
+ the CLI: plan/apply, runs, checks, streams, engines, schedules, lineage, query, gc;
226
+ - the **scheduler**: cron/interval triggers enqueue onto a **durable run queue** (leases,
227
+ retries, cooperative cancellation — `interlace cancel <id>` or `POST /runs/{id}/cancel`);
228
+ - **stream ingestion** and retention.
229
+
230
+ Scoped API keys (`interlace apikey create ci --scope read`) lock it down; a durable event log
231
+ backs `GET /events/stream` (SSE with `Last-Event-ID` replay).
185
232
 
186
233
  Add `--quack quack:localhost:4213` to serve the warehouse itself over DuckDB's quack protocol —
187
234
  other processes (CLI runs, ad-hoc DuckDB clients) then share it concurrently by setting
@@ -198,7 +245,7 @@ other processes (CLI runs, ad-hoc DuckDB clients) then share it concurrently by
198
245
  warehouse transaction — exactly-once without distributed coordination.
199
246
  - No Jinja, no pandas in core, no external orchestrator.
200
247
 
201
- The full design rationale lives in `docs/architecture/v2-design.md`.
248
+ The full design rationale lives in `docs/architecture/architecture.md`.
202
249
 
203
250
  ## Development
204
251
 
@@ -208,9 +255,9 @@ Toolchain is pinned with [proto](https://moonrepo.dev/proto), tasks run via
208
255
  ```bash
209
256
  proto install
210
257
  moon run interlace:sync # install deps
211
- moon run interlace:test # 240+ tests
258
+ moon run interlace:test # 350+ tests
212
259
  moon run interlace:check # black + ruff (CI equivalent)
213
- moon run interlace:typecheck # mypy --strict
260
+ moon run interlace:typecheck # mypy
214
261
  ```
215
262
 
216
263
  MIT licensed.
@@ -8,11 +8,12 @@ Models are `.sql` files or Python functions; state is versioned snapshots with v
8
8
  environments and a terraform-style plan/apply; everything runs in a single daemon on
9
9
  DuckDB + DuckLake by default.
10
10
 
11
- > **Status: v2 pre-release.** This branch is a ground-up rebuild (the `interlace` package on
12
- > PyPI is the older 0.x line). APIs may still move. Requires Python 3.12+.
11
+ > **Status: 1.0.** Requires Python 3.12+.
12
+ > The package is published to PyPI as **`interlaced`**; the import name and CLI are `interlace`.
13
13
 
14
14
  ```bash
15
- uv pip install "interlaced[service]" # or from source: "interlaced[service] @ git+https://github.com/interlace-sh/interlace@v2"
15
+ pip install 'interlaced[service]' # the CLI + daemon; core CLI only: pip install interlaced
16
+ # more extras: [adbc] postgres/redshift · [spark] · [polars] · [all]
16
17
  ```
17
18
 
18
19
  ## Sixty seconds
@@ -21,7 +22,7 @@ uv pip install "interlaced[service]" # or from source: "interlaced[service] @ g
21
22
  interlace init my-project && cd my-project
22
23
  interlace plan # terraform-style preview: added / breaking / non-breaking / reuse
23
24
  interlace apply # build changed models, run checks, promote the environment
24
- interlace serve # the daemon: HTTP API + scheduler + stream ingestion, one process
25
+ interlace serve # the daemon: web UI (/ui) + HTTP API + scheduler + streams, one process
25
26
  ```
26
27
 
27
28
  Every model builds into a fingerprinted physical table (`interlace__main.orders__a1b2c3`);
@@ -37,7 +38,7 @@ prod; pass `--env dev` while developing.
37
38
 
38
39
  ```sql
39
40
  /* interlace:
40
- strategy: scd_type_2
41
+ strategy: scd
41
42
  key: customer_id
42
43
  schedule: {cron: "0 * * * *"}
43
44
  checks:
@@ -53,7 +54,7 @@ SELECT customer_id, name, tier FROM raw_customers
53
54
  ```python
54
55
  from interlace import model
55
56
 
56
- @model(strategy="merge_by_key", key="order_id", cursor="updated_at")
57
+ @model(strategy="merge", key="order_id", cursor="updated_at")
57
58
  def orders(cursor, this):
58
59
  """Incremental API extract: `cursor` is max(updated_at) already in the
59
60
  warehouse (None on first run); `this` is the previous materialisation."""
@@ -61,9 +62,9 @@ def orders(cursor, this):
61
62
  return pyarrow.Table.from_pylist(rows) # or RecordBatchReader / generator of batches
62
63
  ```
63
64
 
64
- **Strategies:** `full`, `view`, `ephemeral` (CTE-inlined), `merge_by_key` (upsert),
65
+ **Strategies:** `replace`, `view`, `ephemeral` (CTE-inlined), `merge` (upsert),
65
66
  `full_merge` (full-state source applied as a minimal diff), `incremental_by_time`
66
- (windowed, interval-ledger backfill/catchup), `scd_type_2` (history with validity windows).
67
+ (windowed, interval-ledger backfill/catchup), `scd` (history with validity windows).
67
68
 
68
69
  ## Plan / apply
69
70
 
@@ -74,22 +75,31 @@ $ interlace plan
74
75
  order_stats modified non_breaking reuse <- output provably identical: not rebuilt
75
76
  ```
76
77
 
77
- - Changes classify **breaking / non-breaking / forward-only**; downstream models whose output
78
- is provably identical **reuse their existing tables** instead of rebuilding (an improvement
79
- over model-granular invalidation).
78
+ - Changes classify **breaking / non-breaking / forward-only**; a plan with breaking changes
79
+ refuses to apply without `--force`. Downstream models whose output is provably identical
80
+ (column-pruned impact analysis) **reuse their existing tables** instead of rebuilding — an
81
+ improvement over model-granular invalidation.
80
82
  - `apply --forward-only` lets history-keeping models (scd2/merge/incremental) survive a
81
- definition change: the new logic inherits the existing table and applies going forward.
83
+ definition change: the existing table is copied to the new version, the new logic applies to
84
+ the copy going forward, and checks gate before views move.
82
85
  - **Checks gate promotion**: 10 built-in types (not_null, unique, accepted_values, row_count,
83
86
  freshness, expression, relationships, pattern, range, sql) plus `@check` Python functions —
84
- an error-severity failure blocks before the environment view moves.
85
- - `interlace gc` removes snapshots no environment references (reference-aware: tables shared
86
- through reuse survive).
87
+ an error-severity failure blocks before the environment view moves. `interlace checks run`
88
+ re-runs them ad hoc against any environment's promoted tables.
89
+ - **`--select state:modified`** scopes a plan/apply to models whose fingerprint drifted from the
90
+ target environment, plus everything downstream — the CI diff, one flag. **`interlace impact
91
+ model.column`** shows the column-level blast radius of a change.
92
+ - **`interlace env rollback`** repoints an environment's views at any earlier promotion — nothing
93
+ rebuilds, the views move; every apply records a generation, so a bad deploy reverts in one
94
+ command. `interlace gc` removes snapshots no environment references (reference-aware: tables
95
+ shared through reuse survive).
87
96
 
88
97
  ## Streaming
89
98
 
90
- Declare a stream; POST to it; rows are durable before the 200, deduplicated by idempotency
91
- key, and materialized exactly-once into `streams.<name>` — where SQL models just read them.
92
- A flush triggers the models that consume the stream.
99
+ Declare a stream; POST to it; rows are durable (SQLite WAL log) before the 200, deduplicated by
100
+ idempotency key, and materialized exactly-once into `streams.<name>` — a micro-batch flusher
101
+ commits the data and the watermark in one warehouse transaction, and SQL models just read the
102
+ table. A flush triggers the models that consume the stream.
93
103
 
94
104
  ```python
95
105
  from interlace import stream
@@ -104,7 +114,8 @@ curl -X POST localhost:8000/streams/orders -d '{"order_id": "o1", "total": 49.5}
104
114
  ```
105
115
 
106
116
  Schema drift is yours to choose: `reject` (400), `evolve` (new columns appear), or
107
- `quarantine` (bad events divert to `<stream>__quarantine`).
117
+ `quarantine` (bad events divert to `<stream>__quarantine`). When the warehouse falls behind,
118
+ publishes get **429 backpressure** instead of unbounded backlog.
108
119
 
109
120
  ## Reverse ETL
110
121
 
@@ -118,18 +129,49 @@ attach:
118
129
  ```
119
130
 
120
131
  ```sql
121
- /* interlace: {export: {to: table, target: crm.public.accounts, mode: merge_by_key, key: id}} */
132
+ /* interlace: {export: {to: table, target: crm.public.accounts, mode: merge, key: id}} */
122
133
  SELECT id, tier, lifetime_value FROM account_summary
123
134
  ```
124
135
 
125
- File exports (`to: parquet|csv|json`) work the same way.
136
+ File exports (`to: parquet|csv|json`) work the same way. Sinks are **environment-gated**: by
137
+ default the side effect fires only from prod — a dev apply never writes to a live external
138
+ table (opt in with `environments: [dev, prod]`).
139
+
140
+ ## Multi-engine
141
+
142
+ Models run on **named engines**: DuckDB/DuckLake by default, Postgres natively over ADBC
143
+ (`pip install 'interlaced[adbc]'`), Spark (beta, `[spark]` extra), plus alpha adapters for
144
+ MotherDuck, Redshift, Snowflake and BigQuery (wired and dialect-correct, not yet run against a
145
+ live account), with per-model pinning:
146
+
147
+ ```yaml
148
+ engines:
149
+ pg: {type: postgres, database: "${PG_DSN}"}
150
+ ```
151
+
152
+ ```sql
153
+ /* interlace: {engine: pg, strategy: merge, key: id} */
154
+ ```
155
+
156
+ Strategies execute *inside* the pinned engine (no DuckDB middleman); cross-engine dependencies
157
+ appear as explicit **transfer** lines in the plan and move as Arrow (or a federated `ATTACH`
158
+ fast lane when possible). Contract: `docs/architecture/MULTI_ENGINE.md`.
126
159
 
127
160
  ## The daemon
128
161
 
129
- `interlace serve` runs the HTTP API (Litestar + msgspec, OpenAPI at `/schema/scalar`), the
130
- scheduler (cron/interval triggers → durable run queue), stream ingestion, and retention in one
131
- process. Scoped API keys (`interlace apikey create ci --scope read`) lock it down; a durable
132
- event log backs `GET /events/stream` (SSE with replay).
162
+ `interlace serve` runs everything in one process:
163
+
164
+ - the **web UI** at `/ui` (in-package, zero build step) — ten views: overview, lineage canvas
165
+ with column-level tracing, models, plan/apply with SQL diffs, live runs, query console,
166
+ streams, checks, environments, and system — live over SSE;
167
+ - the **HTTP API** (Litestar + msgspec, OpenAPI at `/schema/scalar`) with the same surface as
168
+ the CLI: plan/apply, runs, checks, streams, engines, schedules, lineage, query, gc;
169
+ - the **scheduler**: cron/interval triggers enqueue onto a **durable run queue** (leases,
170
+ retries, cooperative cancellation — `interlace cancel <id>` or `POST /runs/{id}/cancel`);
171
+ - **stream ingestion** and retention.
172
+
173
+ Scoped API keys (`interlace apikey create ci --scope read`) lock it down; a durable event log
174
+ backs `GET /events/stream` (SSE with `Last-Event-ID` replay).
133
175
 
134
176
  Add `--quack quack:localhost:4213` to serve the warehouse itself over DuckDB's quack protocol —
135
177
  other processes (CLI runs, ad-hoc DuckDB clients) then share it concurrently by setting
@@ -146,7 +188,7 @@ other processes (CLI runs, ad-hoc DuckDB clients) then share it concurrently by
146
188
  warehouse transaction — exactly-once without distributed coordination.
147
189
  - No Jinja, no pandas in core, no external orchestrator.
148
190
 
149
- The full design rationale lives in `docs/architecture/v2-design.md`.
191
+ The full design rationale lives in `docs/architecture/architecture.md`.
150
192
 
151
193
  ## Development
152
194
 
@@ -156,9 +198,9 @@ Toolchain is pinned with [proto](https://moonrepo.dev/proto), tasks run via
156
198
  ```bash
157
199
  proto install
158
200
  moon run interlace:sync # install deps
159
- moon run interlace:test # 240+ tests
201
+ moon run interlace:test # 350+ tests
160
202
  moon run interlace:check # black + ruff (CI equivalent)
161
- moon run interlace:typecheck # mypy --strict
203
+ moon run interlace:typecheck # mypy
162
204
  ```
163
205
 
164
206
  MIT licensed.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "interlaced"
7
- version = "2.0.0a3"
7
+ version = "2.0.2"
8
8
  description = "Python/SQL-first data platform: transformation, built-in orchestration, and durable streaming ingestion"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -14,7 +14,7 @@ authors = [
14
14
  ]
15
15
  keywords = ["data", "pipeline", "orchestration", "transformation", "etl", "streaming", "dbt", "sqlmesh", "duckdb"]
16
16
  classifiers = [
17
- "Development Status :: 3 - Alpha",
17
+ "Development Status :: 5 - Production/Stable",
18
18
  "Intended Audience :: Developers",
19
19
  "Topic :: Software Development :: Build Tools",
20
20
  "Programming Language :: Python :: 3.12",
@@ -22,18 +22,16 @@ classifiers = [
22
22
  ]
23
23
 
24
24
  # Core: the IR + engine + state + plan spine (Phase 1). Everything is Arrow- and
25
- # sqlglot-native. See docs/architecture/v2-design.md.
25
+ # sqlglot-native. See docs/architecture/architecture.md.
26
26
  dependencies = [
27
- "sqlglot>=25.0,<31.0", # canonical IR, transpilation, semantic diff, column lineage
27
+ "sqlglot>=25.0,<29.0", # canonical IR, transpilation, semantic diff, column lineage
28
28
  "duckdb>=1.5.3", # default engine + federation hub; DuckLake storage + quack serving
29
29
  "pyarrow>=17.0", # the wire format: RecordBatchReader everywhere
30
- "msgspec>=0.18,<1.0", # ingest validation + fast (de)serialisation
31
30
  "pydantic>=2.5,<3.0", # config + manifest validation (cold paths only)
32
31
  "typer>=0.12,<1.0", # CLI
33
32
  "rich>=13.0,<15.0", # display, strictly an event subscriber
34
33
  "cronsim>=2.5,<3.0", # cron parsing for the trigger engine
35
34
  "tenacity>=8.2,<10.0", # retries (tasks, DuckLake commit conflicts, transfers)
36
- "structlog>=24.0,<26.0", # structured logging
37
35
  "pyyaml>=6.0,<7.0", # project config (config + env overlays)
38
36
  ]
39
37
 
@@ -45,22 +43,26 @@ interlace = "interlace.cli.main:main"
45
43
  service = [
46
44
  "litestar>=2.12,<3.0",
47
45
  "uvicorn>=0.30,<1.0",
48
- "httpx>=0.27,<1.0",
49
- "argon2-cffi>=23.1,<26.0",
50
- "joserfc>=1.0,<2.0",
51
- "watchfiles>=0.22,<2.0",
46
+ "msgspec>=0.18,<1.0", # the wire types (litestar serialises msgspec structs natively)
52
47
  ]
53
- # Remote engines via Arrow-native transfer (Phase 4).
48
+ # Remote engines via Arrow-native ADBC transfer. postgres (+ redshift, same wire)
49
+ # are the tested transport; snowflake/bigquery are ALPHA (wired, not run live).
54
50
  adbc = [
55
51
  "adbc-driver-manager>=1.2,<2.0",
56
52
  "adbc-driver-postgresql>=1.2,<2.0",
57
53
  ]
54
+ adbc-snowflake = ["interlaced[adbc]", "adbc-driver-snowflake>=1.2,<2.0"]
55
+ adbc-bigquery = ["interlaced[adbc]", "adbc-driver-bigquery>=1.2,<2.0"]
56
+ # Spark engine: PySpark session transport (Arrow-native), Delta for mutating strategies.
57
+ # Pinned to the Spark 4.0 / Delta 4.0 pair (Delta lags the latest Spark minor).
58
+ spark = ["pyspark>=4.0,<4.1", "delta-spark>=4.0,<4.1"]
58
59
  postgres = ["psycopg[binary]>=3.1,<4.0"]
59
60
  polars = ["polars>=1.0,<2.0"]
60
61
  pandas = ["pandas>=2.0,<4.0"]
61
62
  all = ["interlaced[service,adbc,postgres,polars]"]
62
63
  dev = [
63
64
  "pytest>=8.0,<10.0",
65
+ "httpx>=0.27,<1.0", # litestar's TestClient transport
64
66
  "pytest-asyncio>=1.0,<2.0",
65
67
  "ruff>=0.6,<1.0",
66
68
  "black>=24.0,<27.0",
@@ -71,7 +73,7 @@ dev = [
71
73
  where = ["src"]
72
74
 
73
75
  [tool.setuptools.package-data]
74
- interlace = ["py.typed"]
76
+ interlace = ["py.typed", "service/ui/*", "service/ui/**/*"]
75
77
 
76
78
  [tool.black]
77
79
  line-length = 120
@@ -116,6 +118,10 @@ module = [
116
118
  "sqlglot.*",
117
119
  "pyarrow.*",
118
120
  "yaml",
121
+ "adbc_driver_snowflake.*", # optional 'adbc-snowflake' extra — absent in the base env
122
+ "adbc_driver_bigquery.*", # optional 'adbc-bigquery' extra — absent in the base env
123
+ "pyspark.*", # optional 'spark' extra — absent in the base env
124
+ "delta.*", # optional 'spark' extra (delta-spark) — absent in the base env
119
125
  ]
120
126
  ignore_missing_imports = true
121
127
 
@@ -0,0 +1,26 @@
1
+ """Interlace — Python/SQL-first data platform.
2
+
3
+ Transformation (sqlmesh-grade snapshots, virtual environments, plan/apply),
4
+ built-in orchestration (durable work queue + unified triggers), and durable
5
+ streaming ingestion — in one process. See docs/architecture/architecture.md.
6
+
7
+ The public surface is the ``@model`` / ``@stream`` / ``@check`` decorators.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from importlib.metadata import PackageNotFoundError, version
13
+
14
+ from interlace.dsl.decorators import check, model, stream
15
+
16
+ try:
17
+ __version__ = version("interlaced")
18
+ except PackageNotFoundError: # running from a source tree without an install
19
+ __version__ = "0+unknown"
20
+
21
+ __all__ = [
22
+ "__version__",
23
+ "check",
24
+ "model",
25
+ "stream",
26
+ ]