Skip to content

Python script actions (type: "python") — execution-time staging/glue steps in the DAG #48

Description

@ihistand

Summary

Add type: "python" script actions: execution-time Python steps in the DAG for file staging and glue — the ingestion slot Dataform/dbt don't have (dbt's Python models require warehouse-native compute, which Postgres/Supabase lack). Declared in actions.yaml pointing at a plain .py file (no new extension — .pyx collides with Cython). Design spec: sqlanvil-private/planning/specs/2026-07-04-python-script-actions-design.md.

Target shape (the mailing_list example's OpenAddresses chain becomes one sqlanvil run):

python: load_openaddresses.py → import (gs://…) → stg_addresses → addresses_cache → mart

Contract (the load-bearing decisions)

  • Execution-time only. Compile never runs Python (hermetic/deterministic compile is a Cloud invariant). This is NOT a compile-time templating layer — that alternative is explicitly rejected in the spec.
  • No warehouse credentials injected. Python actions stage files; import loads them. Preserves zero-credential-custody + single-write-warehouse.
  • User owns the environment; sqlanvil validates, never installs. Config declares requirements:, pythonVersion:, optional venv:.
  • Phase mapping:
    • compile — file-existence/config-shape only (deterministic).
    • validate / run --dry-run — interpreter version check; requirements satisfied via importlib.metadata + packaging (offline, nothing installed); script syntax via py_compile (the Python analog of EXPLAIN). FAILURE ⇒ downstream BLOCKED, like imports.
    • run — spawn <interpreter> <file> <args>, cwd = project dir, SA_VARS env, exit 0 = success, output captured, timeout.
  • Hosted Cloud rejects python actions at compile (same pattern as local import/export paths) until the managed-execution strategy question is decided separately.

Deliverables

  • Protos: language-neutral Script (core.proto, language field — "python" in v1) + generic ScriptSpec on ExecutionAction (execution.proto). HARD REQUIREMENT: nothing Python-named in the protos — a second language (likely TypeScript, probably via Deno: no node_modules, native TS, permission-flag sandbox, Supabase-aligned) must be a CLI resolver + config sugar, not a proto migration. type: "python" stays as actions.yaml sugar.
  • Core: core/actions/python_script.ts + actions.yaml wiring + CompiledGraph.pythonScripts
  • CLI: build.ts marker task; run.ts dispatch (injectable seam, mirrors duckdbImport); interpreter/env resolver + validate checker (py_compile, importlib.metadata)
  • Hosted-mode compile rejection
  • Tests: compile + dispatch-seam + validate units; integration (script writes CSV → import → assertion)
  • Example: python-action variant of the OpenAddresses loader in examples/supabase_bigquery_mailing_list (in-DAG local alternative to mailing_list example: scheduled OpenAddresses staging loader on GCP Cloud Run #47's scheduled Cloud Run variant)
  • Docs: python-actions guide, validate page, AGENTS.md + skill; whats-new
  • Release: 1.20.0

Out of scope (v1)

Compile-time Python; sqlanvil-managed environments (pip/uv install); hosted execution; dataframe-return contracts (files are the boundary); additional language implementations (TypeScript/Deno — protos are language-neutral from v1, so these are follow-ups, not migrations).

Related: #47 (scheduled staging loader on the user's own Cloud Run — stays valid as the hosted/scheduled variant).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions