contractforge-snowflake 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.
- contractforge_snowflake-0.1.0/.gitignore +18 -0
- contractforge_snowflake-0.1.0/CHANGELOG.md +15 -0
- contractforge_snowflake-0.1.0/PKG-INFO +210 -0
- contractforge_snowflake-0.1.0/README.md +178 -0
- contractforge_snowflake-0.1.0/pyproject.toml +47 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/__init__.py +68 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/access/__init__.py +5 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/access/runtime.py +264 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/adapter.py +78 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/annotations/__init__.py +9 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/annotations/runtime.py +258 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/api.py +51 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/capabilities/__init__.py +17 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/capabilities/sql_warehouse.py +46 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/__init__.py +79 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/_helpers.py +132 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/cost.py +52 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/dashboard.py +34 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/lineage.py +47 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/maintenance.py +47 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/plan.py +22 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/project.py +84 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/publish.py +54 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/run.py +24 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cli/smoke.py +112 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/connection_options.py +70 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/contract_extensions.py +52 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cost/__init__.py +5 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/cost/reconciliation.py +210 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/dashboards/__init__.py +15 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/dashboards/control_tables.py +145 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/deployment/__init__.py +15 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/deployment/procedure.py +193 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/deployment/task_graph.py +211 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/diagnostics/__init__.py +13 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/diagnostics/portability.py +212 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/environment.py +61 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/evidence/__init__.py +34 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/evidence/ddl.py +84 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/evidence/writer.py +877 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/lineage/__init__.py +8 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/lineage/reconciliation.py +96 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/maintenance/__init__.py +15 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/maintenance/retention.py +84 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/naming/__init__.py +9 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/naming/identifiers.py +29 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/operations/__init__.py +5 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/operations/runtime.py +63 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/polling.py +14 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/preparation/__init__.py +11 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/preparation/registry.py +34 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/preparation/sql.py +360 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/publish/__init__.py +5 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/publish/bundle.py +112 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/rendering/__init__.py +5 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/rendering/review.py +132 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/__init__.py +54 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/artifacts.py +85 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/execution.py +520 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/project.py +627 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/publish.py +196 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/quality.py +242 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/runner.py +186 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/schema_policy.py +350 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/session.py +177 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/runtime/snowpark_handler.py +20 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/session_ops.py +60 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/__init__.py +26 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/failure_paths.py +75 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/minimal.py +75 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/models.py +362 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/procedure.py +273 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/runner.py +224 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/stage_publish.py +194 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/smoke/task_graph.py +298 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/__init__.py +6 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/models.py +15 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/registry.py +36 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/review.py +23 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/sql.py +23 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/stage_files.py +106 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/table.py +25 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sources/table_refs.py +81 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/sql.py +11 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/state/__init__.py +23 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/state/runtime.py +368 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/subtargets.py +27 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/values.py +55 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/__init__.py +17 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/append.py +12 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/hash_diff.py +48 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/models.py +41 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/overwrite.py +12 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/registry.py +77 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/upsert.py +32 -0
- contractforge_snowflake-0.1.0/src/contractforge_snowflake/write_modes/validation.py +60 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `contractforge-snowflake` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows Keep a Changelog, and this package follows semantic
|
|
6
|
+
versioning as described in `../../docs/specs/api-stability.md`.
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial public alpha release of the Snowflake adapter.
|
|
13
|
+
- Snowflake SQL warehouse planning, Snowpark library-runner support, staged
|
|
14
|
+
source bindings, deployment artifacts, quality, evidence, lineage, cost and
|
|
15
|
+
project task graph surfaces.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contractforge-snowflake
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Snowflake adapter for ContractForge Core.
|
|
5
|
+
Project-URL: Homepage, https://github.com/marquesantero/contractforge-core/tree/main/adapters/snowflake
|
|
6
|
+
Project-URL: Documentation, https://marquesantero.github.io/contractforge-core/docs/adapters/snowflake
|
|
7
|
+
Project-URL: Repository, https://github.com/marquesantero/contractforge-core
|
|
8
|
+
Project-URL: Issues, https://github.com/marquesantero/contractforge-core/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/marquesantero/contractforge-core/blob/main/adapters/snowflake/CHANGELOG.md
|
|
10
|
+
Author: ContractForge contributors
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: contractforge,data-contracts,ingestion,snowflake,warehouse
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: contractforge-core<0.2,>=0.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
27
|
+
Provides-Extra: runtime
|
|
28
|
+
Requires-Dist: snowflake-connector-python>=3; extra == 'runtime'
|
|
29
|
+
Provides-Extra: snowpark
|
|
30
|
+
Requires-Dist: snowflake-snowpark-python>=1; extra == 'snowpark'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# ContractForge Snowflake Adapter
|
|
34
|
+
|
|
35
|
+
`contractforge-snowflake` is the Snowflake adapter for ContractForge.
|
|
36
|
+
|
|
37
|
+
The implementation is planning/publish/runtime-first:
|
|
38
|
+
|
|
39
|
+
- consumes ContractForge Core contracts through public core models;
|
|
40
|
+
- declares conservative Snowflake capabilities;
|
|
41
|
+
- builds publish bundles for a stable Snowflake library runner;
|
|
42
|
+
- runs supported contracts through the adapter runtime with Snowflake sessions;
|
|
43
|
+
- writes ContractForge evidence/control tables in Snowflake;
|
|
44
|
+
- renders project task graph deployment artifacts for schedule/timezone and
|
|
45
|
+
step dependencies;
|
|
46
|
+
- keeps Snowflake connector and Snowpark dependencies optional.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install contractforge-core contractforge-snowflake
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from contractforge_snowflake import plan_snowflake_contract
|
|
54
|
+
|
|
55
|
+
result = plan_snowflake_contract(contract)
|
|
56
|
+
print(result.status)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from contractforge_snowflake import build_snowflake_publish_bundle
|
|
61
|
+
|
|
62
|
+
bundle = build_snowflake_publish_bundle(contract)
|
|
63
|
+
print(bundle.artifacts["snowflake.publish_manifest.json"])
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from contractforge_snowflake import run_snowflake_contract
|
|
68
|
+
|
|
69
|
+
result = run_snowflake_contract(
|
|
70
|
+
contract_uri="@CONTRACTFORGE_ARTIFACTS/dev/runtime/ANALYTICS.BRONZE_CUSTOMERS.contract.json",
|
|
71
|
+
environment_uri="@CONTRACTFORGE_ARTIFACTS/dev/runtime/ANALYTICS.BRONZE_CUSTOMERS.environment.json",
|
|
72
|
+
session=snowflake_session,
|
|
73
|
+
)
|
|
74
|
+
print(result["status"])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The adapter does not generate per-contract ingestion SQL as the default
|
|
78
|
+
execution model. Published contracts are consumed by the `contractforge_snowflake`
|
|
79
|
+
runtime library. Project deployment artifacts may create Snowflake tasks, but
|
|
80
|
+
those tasks only call the stable runner with contract and environment artifact
|
|
81
|
+
URIs.
|
|
82
|
+
|
|
83
|
+
Governance annotations support table and column comments, plus Snowflake tags
|
|
84
|
+
when tag objects are already provisioned. Use fully qualified tag names such as
|
|
85
|
+
`GOVERNANCE.PUBLIC.DOMAIN` for deterministic live apply. To validate tag intent
|
|
86
|
+
and record `ctrl_ingestion_annotations` evidence without executing tag DDL, set:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
extensions:
|
|
90
|
+
snowflake:
|
|
91
|
+
annotation_tag_mode: validate_only
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Access governance supports table grants, row access policy attachments and
|
|
95
|
+
masking policy attachments, with each step recorded in
|
|
96
|
+
`ctrl_ingestion_access`. Use `access.mode: validate_only` to verify grant and
|
|
97
|
+
policy intent without applying DDL. `revoke_unmanaged: true` remains
|
|
98
|
+
review-required because it can remove inherited or unmanaged Snowflake access.
|
|
99
|
+
|
|
100
|
+
Use `contractforge-snowflake smoke-stage-publish --execute --execute-cleanup`
|
|
101
|
+
to live-test stage publication. The smoke creates a temporary internal stage,
|
|
102
|
+
uploads the publish bundle, reloads the staged manifest and runtime artifacts,
|
|
103
|
+
and runs the contract through the connector-backed library runner.
|
|
104
|
+
|
|
105
|
+
Staged-file sources support CSV, JSON and Parquet batch reads from Snowflake
|
|
106
|
+
stages. Provide a named Snowflake file format through
|
|
107
|
+
`source.options.file_format`, or use a stage with a default file format. CSV
|
|
108
|
+
sources can project positional fields with `source.options.columns` as either a
|
|
109
|
+
list of names or a mapping of output names to Snowflake expressions. JSON and
|
|
110
|
+
Parquet sources default to a `payload` `VARIANT` projection and can project
|
|
111
|
+
typed columns with expressions such as `$1:order_id::NUMBER`.
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
source:
|
|
115
|
+
type: staged_files
|
|
116
|
+
path: '@RAW_STAGE/orders/orders.csv'
|
|
117
|
+
format: csv
|
|
118
|
+
options:
|
|
119
|
+
file_format: RAW_CSV_FORMAT
|
|
120
|
+
columns:
|
|
121
|
+
order_id: '$1::NUMBER'
|
|
122
|
+
status: '$2::STRING'
|
|
123
|
+
amount: '$3::NUMBER(10,2)'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use `contractforge-snowflake smoke-procedure --execute --execute-cleanup` to
|
|
127
|
+
deploy and call the stable Snowpark runtime procedure. The service role needs
|
|
128
|
+
`CREATE PROCEDURE` on the target schema. The smoke accepts the built core and
|
|
129
|
+
adapter wheels locally, stages Snowflake-compatible ZIP copies, and imports
|
|
130
|
+
those ZIP archives from the procedure:
|
|
131
|
+
|
|
132
|
+
```sql
|
|
133
|
+
GRANT CREATE PROCEDURE ON SCHEMA CONTRACTFORGE_TEST_DB.PUBLIC
|
|
134
|
+
TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Use `contractforge-snowflake smoke-task-graph --execute --execute-cleanup` to
|
|
138
|
+
deploy and manually execute a two-step task graph. In addition to the procedure
|
|
139
|
+
grant, the service role needs task creation/execution privileges in the task
|
|
140
|
+
schema, for example:
|
|
141
|
+
|
|
142
|
+
```sql
|
|
143
|
+
GRANT CREATE TASK ON SCHEMA CONTRACTFORGE_TEST_DB.PUBLIC
|
|
144
|
+
TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
145
|
+
GRANT EXECUTE TASK ON ACCOUNT TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Project CLI parity includes `deploy-project`, `run-project`, and
|
|
149
|
+
`cleanup-plan`. `run-project --dry-run` renders the root task `EXECUTE TASK`
|
|
150
|
+
commands without connecting; `run-project --wait` polls bounded
|
|
151
|
+
`INFORMATION_SCHEMA.TASK_HISTORY` for terminal task states. `cleanup-plan`
|
|
152
|
+
prints explicit drop commands for tasks and the runtime procedure but does not
|
|
153
|
+
drop data target tables or staged artifacts.
|
|
154
|
+
|
|
155
|
+
Each runtime run records immediate lineage and explain evidence. The lineage
|
|
156
|
+
row in `ctrl_ingestion_lineage` includes a ContractForge/OpenLineage-style
|
|
157
|
+
event with the run id, source reference, target table, row count and Snowflake
|
|
158
|
+
query ids. The explain row in `ctrl_ingestion_explain` captures
|
|
159
|
+
`EXPLAIN USING TEXT` output for the rendered write statement. Disable plan
|
|
160
|
+
capture for a contract with:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
extensions:
|
|
164
|
+
snowflake:
|
|
165
|
+
explain_enabled: false
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Native Snowflake lineage from `SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY` is
|
|
169
|
+
handled as delayed reconciliation because Account Usage can lag runtime
|
|
170
|
+
execution. The helper probes by structured `QUERY_TAG` `run_id` and returns
|
|
171
|
+
`PENDING` without inserting rows until matching Access History rows are visible.
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
contractforge-snowflake reconcile-lineage \
|
|
175
|
+
--connect-options .tmp/snowflake-smoke/connect-options.yaml \
|
|
176
|
+
--environment .tmp/snowflake-smoke/environment.json \
|
|
177
|
+
--run-id "<contractforge-run-id>"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Use `contractforge-snowflake reconcile-cost` after a run to append delayed
|
|
181
|
+
`ctrl_ingestion_cost` signals from Snowflake Account Usage. The command probes
|
|
182
|
+
`QUERY_HISTORY` by structured `QUERY_TAG` `run_id`; if rows have not arrived
|
|
183
|
+
yet or the service role cannot read Account Usage, it returns `PENDING` without
|
|
184
|
+
inserting duplicate evidence and includes a warning when access is unavailable.
|
|
185
|
+
When rows are available, it deletes prior adapter-owned cost signals for the
|
|
186
|
+
same `run_id`/target table before inserting query-history and, when accessible,
|
|
187
|
+
query-attribution signals.
|
|
188
|
+
|
|
189
|
+
To let the same service role query Account Usage, grant the Snowflake database
|
|
190
|
+
role that covers `QUERY_HISTORY` and `QUERY_ATTRIBUTION_HISTORY`:
|
|
191
|
+
|
|
192
|
+
```sql
|
|
193
|
+
GRANT DATABASE ROLE SNOWFLAKE.GOVERNANCE_VIEWER
|
|
194
|
+
TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
contractforge-snowflake reconcile-cost \
|
|
199
|
+
--connect-options .tmp/snowflake-smoke/connect-options.yaml \
|
|
200
|
+
--environment .tmp/snowflake-smoke/environment.json \
|
|
201
|
+
--run-id "<contractforge-run-id>" \
|
|
202
|
+
--target-table '"CONTRACTFORGE_TEST_DB"."PUBLIC"."CF_SMOKE_APPEND_TARGET"' \
|
|
203
|
+
--wait --max-wait-seconds 300
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Architecture and implementation plan:
|
|
207
|
+
|
|
208
|
+
- `docs/specs/snowflake-capability-parity.md`
|
|
209
|
+
- `docs/specs/snowflake-adapter-implementation-plan.md`
|
|
210
|
+
- `docs/specs/snowflake-adapter-parity-execution-plan.md`
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# ContractForge Snowflake Adapter
|
|
2
|
+
|
|
3
|
+
`contractforge-snowflake` is the Snowflake adapter for ContractForge.
|
|
4
|
+
|
|
5
|
+
The implementation is planning/publish/runtime-first:
|
|
6
|
+
|
|
7
|
+
- consumes ContractForge Core contracts through public core models;
|
|
8
|
+
- declares conservative Snowflake capabilities;
|
|
9
|
+
- builds publish bundles for a stable Snowflake library runner;
|
|
10
|
+
- runs supported contracts through the adapter runtime with Snowflake sessions;
|
|
11
|
+
- writes ContractForge evidence/control tables in Snowflake;
|
|
12
|
+
- renders project task graph deployment artifacts for schedule/timezone and
|
|
13
|
+
step dependencies;
|
|
14
|
+
- keeps Snowflake connector and Snowpark dependencies optional.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install contractforge-core contractforge-snowflake
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from contractforge_snowflake import plan_snowflake_contract
|
|
22
|
+
|
|
23
|
+
result = plan_snowflake_contract(contract)
|
|
24
|
+
print(result.status)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from contractforge_snowflake import build_snowflake_publish_bundle
|
|
29
|
+
|
|
30
|
+
bundle = build_snowflake_publish_bundle(contract)
|
|
31
|
+
print(bundle.artifacts["snowflake.publish_manifest.json"])
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from contractforge_snowflake import run_snowflake_contract
|
|
36
|
+
|
|
37
|
+
result = run_snowflake_contract(
|
|
38
|
+
contract_uri="@CONTRACTFORGE_ARTIFACTS/dev/runtime/ANALYTICS.BRONZE_CUSTOMERS.contract.json",
|
|
39
|
+
environment_uri="@CONTRACTFORGE_ARTIFACTS/dev/runtime/ANALYTICS.BRONZE_CUSTOMERS.environment.json",
|
|
40
|
+
session=snowflake_session,
|
|
41
|
+
)
|
|
42
|
+
print(result["status"])
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The adapter does not generate per-contract ingestion SQL as the default
|
|
46
|
+
execution model. Published contracts are consumed by the `contractforge_snowflake`
|
|
47
|
+
runtime library. Project deployment artifacts may create Snowflake tasks, but
|
|
48
|
+
those tasks only call the stable runner with contract and environment artifact
|
|
49
|
+
URIs.
|
|
50
|
+
|
|
51
|
+
Governance annotations support table and column comments, plus Snowflake tags
|
|
52
|
+
when tag objects are already provisioned. Use fully qualified tag names such as
|
|
53
|
+
`GOVERNANCE.PUBLIC.DOMAIN` for deterministic live apply. To validate tag intent
|
|
54
|
+
and record `ctrl_ingestion_annotations` evidence without executing tag DDL, set:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
extensions:
|
|
58
|
+
snowflake:
|
|
59
|
+
annotation_tag_mode: validate_only
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Access governance supports table grants, row access policy attachments and
|
|
63
|
+
masking policy attachments, with each step recorded in
|
|
64
|
+
`ctrl_ingestion_access`. Use `access.mode: validate_only` to verify grant and
|
|
65
|
+
policy intent without applying DDL. `revoke_unmanaged: true` remains
|
|
66
|
+
review-required because it can remove inherited or unmanaged Snowflake access.
|
|
67
|
+
|
|
68
|
+
Use `contractforge-snowflake smoke-stage-publish --execute --execute-cleanup`
|
|
69
|
+
to live-test stage publication. The smoke creates a temporary internal stage,
|
|
70
|
+
uploads the publish bundle, reloads the staged manifest and runtime artifacts,
|
|
71
|
+
and runs the contract through the connector-backed library runner.
|
|
72
|
+
|
|
73
|
+
Staged-file sources support CSV, JSON and Parquet batch reads from Snowflake
|
|
74
|
+
stages. Provide a named Snowflake file format through
|
|
75
|
+
`source.options.file_format`, or use a stage with a default file format. CSV
|
|
76
|
+
sources can project positional fields with `source.options.columns` as either a
|
|
77
|
+
list of names or a mapping of output names to Snowflake expressions. JSON and
|
|
78
|
+
Parquet sources default to a `payload` `VARIANT` projection and can project
|
|
79
|
+
typed columns with expressions such as `$1:order_id::NUMBER`.
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
source:
|
|
83
|
+
type: staged_files
|
|
84
|
+
path: '@RAW_STAGE/orders/orders.csv'
|
|
85
|
+
format: csv
|
|
86
|
+
options:
|
|
87
|
+
file_format: RAW_CSV_FORMAT
|
|
88
|
+
columns:
|
|
89
|
+
order_id: '$1::NUMBER'
|
|
90
|
+
status: '$2::STRING'
|
|
91
|
+
amount: '$3::NUMBER(10,2)'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `contractforge-snowflake smoke-procedure --execute --execute-cleanup` to
|
|
95
|
+
deploy and call the stable Snowpark runtime procedure. The service role needs
|
|
96
|
+
`CREATE PROCEDURE` on the target schema. The smoke accepts the built core and
|
|
97
|
+
adapter wheels locally, stages Snowflake-compatible ZIP copies, and imports
|
|
98
|
+
those ZIP archives from the procedure:
|
|
99
|
+
|
|
100
|
+
```sql
|
|
101
|
+
GRANT CREATE PROCEDURE ON SCHEMA CONTRACTFORGE_TEST_DB.PUBLIC
|
|
102
|
+
TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use `contractforge-snowflake smoke-task-graph --execute --execute-cleanup` to
|
|
106
|
+
deploy and manually execute a two-step task graph. In addition to the procedure
|
|
107
|
+
grant, the service role needs task creation/execution privileges in the task
|
|
108
|
+
schema, for example:
|
|
109
|
+
|
|
110
|
+
```sql
|
|
111
|
+
GRANT CREATE TASK ON SCHEMA CONTRACTFORGE_TEST_DB.PUBLIC
|
|
112
|
+
TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
113
|
+
GRANT EXECUTE TASK ON ACCOUNT TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Project CLI parity includes `deploy-project`, `run-project`, and
|
|
117
|
+
`cleanup-plan`. `run-project --dry-run` renders the root task `EXECUTE TASK`
|
|
118
|
+
commands without connecting; `run-project --wait` polls bounded
|
|
119
|
+
`INFORMATION_SCHEMA.TASK_HISTORY` for terminal task states. `cleanup-plan`
|
|
120
|
+
prints explicit drop commands for tasks and the runtime procedure but does not
|
|
121
|
+
drop data target tables or staged artifacts.
|
|
122
|
+
|
|
123
|
+
Each runtime run records immediate lineage and explain evidence. The lineage
|
|
124
|
+
row in `ctrl_ingestion_lineage` includes a ContractForge/OpenLineage-style
|
|
125
|
+
event with the run id, source reference, target table, row count and Snowflake
|
|
126
|
+
query ids. The explain row in `ctrl_ingestion_explain` captures
|
|
127
|
+
`EXPLAIN USING TEXT` output for the rendered write statement. Disable plan
|
|
128
|
+
capture for a contract with:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
extensions:
|
|
132
|
+
snowflake:
|
|
133
|
+
explain_enabled: false
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Native Snowflake lineage from `SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY` is
|
|
137
|
+
handled as delayed reconciliation because Account Usage can lag runtime
|
|
138
|
+
execution. The helper probes by structured `QUERY_TAG` `run_id` and returns
|
|
139
|
+
`PENDING` without inserting rows until matching Access History rows are visible.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
contractforge-snowflake reconcile-lineage \
|
|
143
|
+
--connect-options .tmp/snowflake-smoke/connect-options.yaml \
|
|
144
|
+
--environment .tmp/snowflake-smoke/environment.json \
|
|
145
|
+
--run-id "<contractforge-run-id>"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Use `contractforge-snowflake reconcile-cost` after a run to append delayed
|
|
149
|
+
`ctrl_ingestion_cost` signals from Snowflake Account Usage. The command probes
|
|
150
|
+
`QUERY_HISTORY` by structured `QUERY_TAG` `run_id`; if rows have not arrived
|
|
151
|
+
yet or the service role cannot read Account Usage, it returns `PENDING` without
|
|
152
|
+
inserting duplicate evidence and includes a warning when access is unavailable.
|
|
153
|
+
When rows are available, it deletes prior adapter-owned cost signals for the
|
|
154
|
+
same `run_id`/target table before inserting query-history and, when accessible,
|
|
155
|
+
query-attribution signals.
|
|
156
|
+
|
|
157
|
+
To let the same service role query Account Usage, grant the Snowflake database
|
|
158
|
+
role that covers `QUERY_HISTORY` and `QUERY_ATTRIBUTION_HISTORY`:
|
|
159
|
+
|
|
160
|
+
```sql
|
|
161
|
+
GRANT DATABASE ROLE SNOWFLAKE.GOVERNANCE_VIEWER
|
|
162
|
+
TO ROLE CONTRACTFORGE_INGEST_ROLE;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
contractforge-snowflake reconcile-cost \
|
|
167
|
+
--connect-options .tmp/snowflake-smoke/connect-options.yaml \
|
|
168
|
+
--environment .tmp/snowflake-smoke/environment.json \
|
|
169
|
+
--run-id "<contractforge-run-id>" \
|
|
170
|
+
--target-table '"CONTRACTFORGE_TEST_DB"."PUBLIC"."CF_SMOKE_APPEND_TARGET"' \
|
|
171
|
+
--wait --max-wait-seconds 300
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Architecture and implementation plan:
|
|
175
|
+
|
|
176
|
+
- `docs/specs/snowflake-capability-parity.md`
|
|
177
|
+
- `docs/specs/snowflake-adapter-implementation-plan.md`
|
|
178
|
+
- `docs/specs/snowflake-adapter-parity-execution-plan.md`
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "contractforge-snowflake"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Snowflake adapter for ContractForge Core."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "ContractForge contributors" }]
|
|
13
|
+
keywords = ["contractforge", "snowflake", "data-contracts", "ingestion", "warehouse"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Database",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["contractforge-core>=0.1,<0.2"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/marquesantero/contractforge-core/tree/main/adapters/snowflake"
|
|
30
|
+
Documentation = "https://marquesantero.github.io/contractforge-core/docs/adapters/snowflake"
|
|
31
|
+
Repository = "https://github.com/marquesantero/contractforge-core"
|
|
32
|
+
Issues = "https://github.com/marquesantero/contractforge-core/issues"
|
|
33
|
+
Changelog = "https://github.com/marquesantero/contractforge-core/blob/main/adapters/snowflake/CHANGELOG.md"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = ["pytest>=8"]
|
|
37
|
+
runtime = ["snowflake-connector-python>=3"]
|
|
38
|
+
snowpark = ["snowflake-snowpark-python>=1"]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
contractforge-snowflake = "contractforge_snowflake.cli:main"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/contractforge_snowflake"]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist]
|
|
47
|
+
include = ["/src/contractforge_snowflake", "/README.md", "/CHANGELOG.md", "/pyproject.toml"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Public API for the ContractForge Snowflake adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError
|
|
6
|
+
from importlib.metadata import version as _version
|
|
7
|
+
|
|
8
|
+
from contractforge_snowflake.adapter import SnowflakeAdapter
|
|
9
|
+
from contractforge_snowflake.api import (
|
|
10
|
+
build_snowflake_publish_bundle,
|
|
11
|
+
plan_snowflake_contract,
|
|
12
|
+
render_snowflake_contract,
|
|
13
|
+
)
|
|
14
|
+
from contractforge_snowflake.capabilities import (
|
|
15
|
+
SNOWFLAKE_SUBTARGET_SNOWPIPE,
|
|
16
|
+
SNOWFLAKE_SUBTARGET_SQL_WAREHOUSE,
|
|
17
|
+
SNOWFLAKE_SUBTARGET_STREAMS_TASKS,
|
|
18
|
+
SNOWFLAKE_SUBTARGET_TASK_GRAPH,
|
|
19
|
+
snowflake_sql_warehouse_capabilities,
|
|
20
|
+
)
|
|
21
|
+
from contractforge_snowflake.dashboards import render_control_dashboard_artifacts, render_control_dashboard_sql
|
|
22
|
+
from contractforge_snowflake.environment import SnowflakeEnvironment
|
|
23
|
+
from contractforge_snowflake.maintenance import build_control_retention_plan, execute_control_retention_plan
|
|
24
|
+
from contractforge_snowflake.runtime import (
|
|
25
|
+
SnowflakeAccessHistoryLineageResult,
|
|
26
|
+
build_snowflake_project_cleanup_plan,
|
|
27
|
+
deploy_snowflake_project,
|
|
28
|
+
publish_snowflake_contract,
|
|
29
|
+
reconcile_snowflake_access_history_lineage,
|
|
30
|
+
reconcile_snowflake_cost_evidence,
|
|
31
|
+
run_snowflake_contract,
|
|
32
|
+
run_snowflake_project,
|
|
33
|
+
wait_snowflake_project_tasks,
|
|
34
|
+
)
|
|
35
|
+
from contractforge_snowflake.subtargets import list_snowflake_subtargets
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
__version__ = _version("contractforge-snowflake")
|
|
39
|
+
except PackageNotFoundError: # pragma: no cover - editable/source tree without installed metadata
|
|
40
|
+
__version__ = "0.1.0"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"SNOWFLAKE_SUBTARGET_SNOWPIPE",
|
|
44
|
+
"SNOWFLAKE_SUBTARGET_SQL_WAREHOUSE",
|
|
45
|
+
"SNOWFLAKE_SUBTARGET_STREAMS_TASKS",
|
|
46
|
+
"SNOWFLAKE_SUBTARGET_TASK_GRAPH",
|
|
47
|
+
"SnowflakeAdapter",
|
|
48
|
+
"SnowflakeAccessHistoryLineageResult",
|
|
49
|
+
"SnowflakeEnvironment",
|
|
50
|
+
"__version__",
|
|
51
|
+
"build_snowflake_publish_bundle",
|
|
52
|
+
"build_control_retention_plan",
|
|
53
|
+
"build_snowflake_project_cleanup_plan",
|
|
54
|
+
"deploy_snowflake_project",
|
|
55
|
+
"execute_control_retention_plan",
|
|
56
|
+
"list_snowflake_subtargets",
|
|
57
|
+
"plan_snowflake_contract",
|
|
58
|
+
"publish_snowflake_contract",
|
|
59
|
+
"reconcile_snowflake_access_history_lineage",
|
|
60
|
+
"reconcile_snowflake_cost_evidence",
|
|
61
|
+
"render_control_dashboard_artifacts",
|
|
62
|
+
"render_control_dashboard_sql",
|
|
63
|
+
"render_snowflake_contract",
|
|
64
|
+
"run_snowflake_contract",
|
|
65
|
+
"run_snowflake_project",
|
|
66
|
+
"snowflake_sql_warehouse_capabilities",
|
|
67
|
+
"wait_snowflake_project_tasks",
|
|
68
|
+
]
|