Skip to content

Go SDK: generate DagSpec and TaskSpec from the serialization schema - #73936

Merged
jason810496 merged 1 commit into
apache:mainfrom
henry3260:go-sdk-generate-dag-task-spec
Sep 30, 2026
Merged

jason810496 merged 1 commit into
apache:mainfrom
henry3260:go-sdk-generate-dag-task-spec

Conversation

@henry3260

@henry3260 henry3260 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

airflow.DagSpec and airflow.TaskSpec describe the attributes
airflow-core/src/airflow/serialization/schema.json declares, and Python owns that schema.
Hand-maintained, they drift from it in silence: a property added or retyped there leaves a
Go-authored Dag serializing a shape core no longer means.

Decision 12 of ADR 8,
on top of #73767, following what pkg/execution/genmodels already does for the supervisor schema.

What

go:generate on go-sdk/airflow/spec.go: internal/genspec rewrites the Python-owned schema into
the authoring shape, go-jsonschema writes the committed airflow/spec.gen.go, and
genspec -license restores the Apache header so regeneration is idempotent.
scripts/ci/prek/check_go_sdk_spec_drift.py fails when the committed file differs.

The schema is the serialized shape, so internal/genspec/authoring.go holds three tables, each
entry with its reason: exclusions (fileloc, tasks, ui_color, derived has_on_*_callback
flags, anything with no Go authoring type yet), type overrides (time.Time, time.Duration,
int where the schema declares a number), and injection of Schedule over the serialized
timetable. A property not excluded generates, so one added on the Python side surfaces in review
rather than vanishing. Injecting rather than hand-writing keeps every field in one struct
declaration, which is what lets TaskSpec implement the sealed TaskOption. A field is a pointer where the
Go zero value is something an author could mean and the schema does not say it is already the
default: a scalar whose schema default is not the zero value, the rule genmodels applies, and a
boolean with no schema default at all, whose real default comes from Airflow config and can be true.

TriggerRule stays hand-written, since the schema types trigger_rule as a plain string;
TestTriggerRuleConstantsMatchPython is its tripwire against Airflow's enum. The Apache-header
insertion genmodels/gen carried moves to internal/genlicense for both generators, leaving
models.gen.go unchanged.


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5)

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Thanks for the PR, LGTM overall, I just compared this with my previous work #67155 using Claude.

Comment thread go-sdk/internal/genspec/authoring.go Outdated
Comment thread go-sdk/internal/genspec/authoring.go Outdated
Comment thread go-sdk/internal/genspec/authoring.go Outdated
Comment thread go-sdk/internal/genspec/authoring.go Outdated
Comment thread go-sdk/airflow/dag.go Outdated
Comment thread go-sdk/airflow/spec.gen.go Outdated
Comment thread go-sdk/airflow/spec.gen.go Outdated
Comment thread go-sdk/airflow/spec.go Outdated
Comment thread go-sdk/airflow/spec.go Outdated
Comment thread go-sdk/airflow/spec.go
@henry3260
henry3260 force-pushed the go-sdk-generate-dag-task-spec branch 4 times, most recently from 3b9fb19 to 33cc533 Compare September 30, 2026 12:16
The two structs a Dag author fills in describe the same attributes
airflow-core's Dag serialization schema declares, and Python owns that
schema. Hand-maintained, they drift from it in silence: a property added,
renamed or retyped there leaves a Go-authored Dag serializing a shape
core no longer means, and nothing says so.

The schema is the serialized shape rather than the authoring one, so
generation cannot take it whole. It requires the paths and task lists the
bundle fills in, types a moment in time and a duration as a number of
seconds, carries a serialized timetable where an author writes a
schedule, and names none of the values a trigger rule may take. Those are
decisions about the SDK's surface, and the shape tables record each one
with the reason it was made, so that the next property to appear on the
Python side is a review question rather than an accident. Anything not
listed generates, which is what keeps a new property from vanishing.
@henry3260
henry3260 force-pushed the go-sdk-generate-dag-task-spec branch from 33cc533 to 4bfb7c8 Compare September 30, 2026 12:18

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@jason810496
jason810496 merged commit 6317c63 into apache:main Sep 30, 2026
97 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants