This is an automated email from the ASF dual-hosted git repository.
jason810496 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 81c2eab5595 Update Go SDK docs for the airflow.Bundle authoring
surface (#73528)
81c2eab5595 is described below
commit 81c2eab55955d72715c615edb50cc345be5a2685
Author: Henry Chen <[email protected]>
AuthorDate: Tue Sep 22 21:27:11 2026 +0800
Update Go SDK docs for the airflow.Bundle authoring surface (#73528)
* Update Go SDK docs for the airflow.Bundle authoring surface
The Go SDK's authoring API was replaced: handlers are registered with
airflow.TaskHandler under an explicit dag_id and task_id, and every handler
takes an airflow.Context first instead of having the logger and client
injected by parameter type.
The docs still taught the removed API, so a Dag author following them writes
a bundle that no longer compiles and handlers the runtime now rejects at
registration. They also stated that task_id is derived from the Go function
name, which is no longer true and silently produces a bundle whose tasks the
supervisor cannot find.
* Call it the stub Task where the docs mean the task, not the Dag
A TaskFlow call and its arguments belong to the @task.stub task, not to the
Python Dag that contains it, so saying "stub Dag" there tells a Go author to
look in the wrong place for what binds to their handler's parameters.
The places that genuinely describe the Dag itself keep saying Dag.
* Lead the Go SDK argument docs with the two binding shapes
A Go author arriving at the arguments section met the positional rules with
no sign that a second, keyword-style shape existed further down, so the
struct form read as a special case rather than one of two choices.
Also drop the cross-SDK aside from the Go SDK README, which has no reason to
explain itself in terms of the Python and Java SDKs.
* Keep the two Go SDK argument shapes side by side
The note about defaulted stub parameters sat between the positional and
struct forms, splitting the comparison a reader is making and reading as if
it applied only to the positional one. It applies to both.
---
.../authoring-and-scheduling/language-sdks/go.rst | 191 ++++++++++++++-------
go-sdk/README.md | 113 ++++++------
.../adr/0007-mixed-lang-task-handler-interface.md | 12 +-
.../cmd/airflow-go-pack/pack_integration_test.go | 4 +-
.../bundle/concurrentxcom/concurrentxcom.go | 2 +-
5 files changed, 206 insertions(+), 116 deletions(-)
diff --git a/airflow-core/docs/authoring-and-scheduling/language-sdks/go.rst
b/airflow-core/docs/authoring-and-scheduling/language-sdks/go.rst
index 42b1ecf6213..f84803843dc 100644
--- a/airflow-core/docs/authoring-and-scheduling/language-sdks/go.rst
+++ b/airflow-core/docs/authoring-and-scheduling/language-sdks/go.rst
@@ -93,78 +93,82 @@ implementation. The ``queue`` value routes the task to the
Go coordinator.
Go implementation
~~~~~~~~~~~~~~~~~~
-A task is an ordinary Go function. The runtime inspects its signature and
injects arguments by type, so each
-task declares only the parameters it needs.
+A task is an ordinary Go function whose first parameter is an
``airflow.Context``. Everything Airflow gives
+the task is a method on it, so the signature stays the same whatever the task
uses.
.. code-block:: go
import (
- "log/slog"
"runtime"
- "github.com/apache/airflow/go-sdk/sdk"
+ "github.com/apache/airflow/go-sdk/airflow"
)
- func extract(ctx sdk.TIRunContext, client sdk.Client, log *slog.Logger)
(any, error) {
- conn, err := client.GetConnection(ctx, "test_http")
+ func extract(actx airflow.Context) (any, error) {
+ conn, err := actx.Client().GetConnection(actx, "test_http")
if err != nil {
return nil, err
}
- log.Info("fetched connection", "host", conn.Host)
- // ... do work, honour ctx cancellation ...
+ actx.Logger().InfoContext(actx, "fetched connection", "host",
conn.Host)
+ // ... do work, honour actx cancellation ...
return map[string]any{"go_version": runtime.Version()}, nil
}
- func transform(ctx sdk.TIRunContext, client sdk.VariableClient, log
*slog.Logger) error {
- val, err := client.GetVariable(ctx, "my_variable")
+ func transform(actx airflow.Context) error {
+ val, err := actx.Client().GetVariable(actx, "my_variable")
if err != nil {
return err
}
- log.Info("obtained variable", "my_variable", val)
+ actx.Logger().InfoContext(actx, "obtained variable", "my_variable",
val)
return nil
}
.. note::
As with the other language SDKs, XCom *dependencies* are declared in the
Python stub Dag (they define task
- order). The value must still be read explicitly in Go via
``client.GetXCom``, and produced either by the
- task's ``(any, error)`` return value or by ``client.PushXCom``.
+ order). An upstream task's value reaches a downstream task either through a
parameter, when the stub Task
+ passes it in the TaskFlow call (see :ref:`go-sdk/arguments`), or by reading
it explicitly with
+ ``actx.Client().GetXCom``.
Go entry point
~~~~~~~~~~~~~~~
-Implement ``bundlev1.BundleProvider`` to register your Dags and tasks;
``main`` is one line. ``RegisterDags``
-is the single source of truth for which ``dag_id`` and task names this bundle
can run, so the generated
-manifest can never drift from what the binary actually executes.
+Build a bundle with ``airflow.Bundle()``, register a handler for each task,
and call ``Serve`` as the last
+statement of ``main``. The ``Register`` calls are the single source of truth
for which ``dag_id`` and task
+names this bundle can run, so the generated manifest can never drift from what
the binary actually executes.
.. code-block:: go
import (
"log"
- v1 "github.com/apache/airflow/go-sdk/bundle/bundlev1"
- "github.com/apache/airflow/go-sdk/bundle/bundlev1/bundlev1server"
+ "github.com/apache/airflow/go-sdk/airflow"
)
- type myBundle struct{}
-
- var _ v1.BundleProvider = (*myBundle)(nil)
+ func main() {
+ bundle := airflow.Bundle()
- func (m *myBundle) RegisterDags(dagbag v1.Registry) error {
- simpleDag := dagbag.AddDag("simple_dag") // must match the Python
dag_id
- simpleDag.AddTask(extract) // task_id is taken from the
function name
- simpleDag.AddTask(transform)
- return nil
- }
+ bundle.Register(
+ airflow.TaskHandler("simple_dag", "extract", extract),
+ airflow.TaskHandler("simple_dag", "transform", transform),
+ )
- func main() {
- if err := bundlev1server.Serve(&myBundle{}); err != nil {
+ if err := bundle.Serve(); err != nil {
log.Fatal(err)
}
}
-The ``dag_id`` passed to ``AddDag`` must match the ``dag_id`` of the Python
Dag, and each registered task's
-name must match a ``@task.stub`` function in that Dag.
+``TaskHandler`` names the ``dag_id`` and the ``task_id`` explicitly: the
``dag_id`` must match the Python
+Dag, and the ``task_id`` must match a ``@task.stub`` function in that Dag.
Neither is derived from the Go
+function name, so a handler can be named whatever reads best in Go.
+
+``TaskHandler`` also checks the signature of the function it is given and
panics if the check fails -- for
+instance when the function does not take an ``airflow.Context`` first, or does
not return an ``error``.
+Because ``main`` registers every handler before ``Serve``, a mistake stops the
executable as soon as it
+starts rather than when the task first runs.
+
+A package that defines task handlers of its own can export them as a
``[]airflow.Registerable`` for ``main``
+to pass on with ``bundle.Register(reports.Handlers()...)``.
Coordinator configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -200,37 +204,58 @@ There is no separate Go worker to run: the Airflow worker
forks the bundle binar
Writing tasks
-------------
-The runtime inspects a task function's signature and injects arguments by
type, so you only declare the
-parameters your task actually needs:
+Every task function takes an ``airflow.Context`` as its first parameter, and
reaches what Airflow provides
+through its methods:
.. list-table::
:header-rows: 1
:widths: 35 65
- * - Parameter type
- - Injected value
- * - ``sdk.TIRunContext``
- - The task's execution context: the cancellation/deadline signal plus the
task instance identifiers and
- Dag run timestamps. Respect it for long-running work. See
:ref:`go-sdk/runtime-context`.
- * - ``*slog.Logger``
- - A logger whose output is routed back to the Airflow task log.
- * - ``sdk.Client`` (or a narrower interface)
- - A client for Airflow Variables, Connections, and XCom.
+ * - Method
+ - What it returns
+ * - ``actx.Logger()``
+ - An ``*slog.Logger`` whose output is routed back to the Airflow task log.
+ * - ``actx.Client()``
+ - A client for Airflow Variables, Connections, and XCom. See
:ref:`go-sdk/client`.
+ * - ``actx.TaskInstance()``
+ - The identifiers of the running task instance. See
:ref:`go-sdk/runtime-context`.
+ * - ``actx.DagRun()``
+ - The identifiers and scheduling timestamps of its Dag run. See
:ref:`go-sdk/runtime-context`.
+
+``airflow.Context`` is itself a ``context.Context``, so pass it straight to a
client call or to
+``http.NewRequestWithContext``, and select on ``actx.Done()``, which fires
when the supervisor asks the task
+to stop. Respect it for long-running work. Cleanup that must outlive that
cancellation runs under ``context.WithoutCancel(actx)``.
+A helper typed as a plain ``context.Context`` recovers the same surface with
``airflow.FromContext``.
+
+Every parameter after the Context is data, filled from the stub Task's
TaskFlow call; see
+:ref:`go-sdk/arguments`.
An optional ``(any, error)`` return value becomes the task's ``return_value``
XCom. A non-nil ``error`` (or a
panic, which the runtime recovers) marks the task instance failed in Airflow,
triggering retries if
configured on the stub.
-Requesting the narrowest interface you need (for example
``sdk.VariableClient`` instead of the full
-``sdk.Client``) documents which Airflow features the task touches and makes
unit testing easier, because you
-can pass a fake in tests.
+``airflow.NewContext`` builds a Context, so a task is an ordinary function
call in a unit test:
+
+.. code-block:: go
+
+ actx := airflow.NewContext(
+ t.Context(), slog.Default(), fakeClient,
+ airflow.TaskInstance{DagID: "simple_dag", TaskID: "transform",
TryNumber: 1},
+ airflow.DagRun{DagID: "simple_dag", RunID: "run1"},
+ )
+ require.NoError(t, transform(actx))
+
+A helper the task calls can still ask for the narrowest interface it needs
(for example
+``sdk.VariableClient`` instead of the full ``sdk.Client``), which documents
the Airflow features it touches
+and lets a test pass a fake.
.. _go-sdk/client:
The ``sdk.Client`` surface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-``sdk.Client`` composes three smaller interfaces, so a task can depend on just
one:
+``actx.Client()`` returns an ``sdk.Client``, which composes three smaller
interfaces, so a helper can depend
+on just one:
* ``VariableClient`` - ``GetVariable`` (returns the Variable as a string),
``UnmarshalJSONVariable``
(decodes a JSON Variable into a pointer you provide), ``SetVariable``, and
``DeleteVariable``.
@@ -247,10 +272,11 @@ before storing it.
.. code-block:: go
- if err := client.SetVariable(ctx, "process_threshold", "42", "Rows above
this count take the slow path"); err != nil {
+ client := actx.Client()
+ if err := client.SetVariable(actx, "process_threshold", "42", "Rows above
this count take the slow path"); err != nil {
return err
}
- if err := client.DeleteVariable(ctx, "legacy_threshold"); err != nil {
+ if err := client.DeleteVariable(actx, "legacy_threshold"); err != nil {
return err
}
@@ -268,30 +294,79 @@ so you can branch on a missing value with ``errors.Is``
rather than parsing an e
Reading the task runtime context
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Declare an ``sdk.TIRunContext`` parameter on a task to read the identifiers
and scheduling timestamps of the
-running task instance and its Dag run -- the Go equivalent of the execution
context the Python and Java SDKs
-expose. It is an interface that embeds ``context.Context``, so the same
``ctx`` drives cancellation and
-client calls. The runtime binds it by type, just like the other injected
parameters:
+``airflow.Context`` carries the identifiers and scheduling timestamps of the
running task instance and its
+Dag run -- the Go equivalent of the execution context the Python and Java SDKs
expose:
.. code-block:: go
- func extract(ctx sdk.TIRunContext, log *slog.Logger) (any, error) {
- ti := ctx.TaskInstance()
- log.Info("running",
+ func extract(actx airflow.Context) (any, error) {
+ ti := actx.TaskInstance()
+ actx.Logger().InfoContext(actx, "running",
"dag_id", ti.DagID,
"run_id", ti.RunID,
"task_id", ti.TaskID,
"try_number", ti.TryNumber,
- "logical_date", ctx.DagRun().LogicalDate,
+ "logical_date", actx.DagRun().LogicalDate,
)
return nil, nil
}
-``ctx.TaskInstance()`` returns ``DagID``, ``RunID``, ``TaskID``, ``MapIndex``
(nil for an unmapped task),
-and ``TryNumber``; ``ctx.DagRun()`` returns ``DagID``, ``RunID``, and the
``*time.Time`` fields
+``actx.TaskInstance()`` returns ``DagID``, ``RunID``, ``TaskID``, ``MapIndex``
(nil for an unmapped task),
+and ``TryNumber``; ``actx.DagRun()`` returns ``DagID``, ``RunID``, and the
``*time.Time`` fields
``LogicalDate``, ``DataIntervalStart``, and ``DataIntervalEnd`` (nil when the
run has no such value, e.g. a
manual trigger).
+.. _go-sdk/arguments:
+
+Receiving arguments from the stub Task
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A stub Task's arguments reach a Go handler in one of two ways:
+
+1. **Positional binding** -- each data parameter takes the argument in the
same position.
+2. **Struct-based (keyword) binding** -- a sole struct parameter takes the
arguments by field name.
+
+Every parameter after the ``airflow.Context`` is a **data parameter**, filled
in declaration order from the
+arguments of the Python stub Task's TaskFlow call. A literal in the Dag file
(``transform("uk", ...)``)
+decodes straight into the parameter; an upstream task's output
(``transform(..., extract())``) is pulled
+from that task's XCom in the current Dag run. If the argument count does not
match, or an argument's
+declared type cannot fill the Go type, the task fails before its body runs.
+
+.. code-block:: go
+
+ // The Python stub Task calls transform("uk", extract()).
+ func transform(actx airflow.Context, country string, extracted
map[string]any) error {
+ actx.Logger().InfoContext(actx, "transforming", "country", country)
+ return nil
+ }
+
+When a task's **sole** data parameter is a struct, its fields bind **by name**
instead of by position --
+keyword arguments rather than positional ones. Being the only data parameter
is the opt-in; there is no
+marker to add.
+
+.. code-block:: go
+
+ type CombineInput struct {
+ Region string `arg:"region_code"` // renamed
+ Threshold float64
+ }
+
+ // The Python stub Task calls combine(region_code="uk", threshold=0.5).
+ func Combine(actx airflow.Context, input CombineInput) (any, error) {
+ return nil, nil
+ }
+
+An exported field binds the argument matching its own Go name, folding case
and underscores, so
+``Threshold`` takes ``threshold``; reach for an ``arg:"<name>"`` tag when the
names genuinely differ, as
+``Region`` does above. The `Go SDK README
+<https://github.com/apache/airflow/blob/main/go-sdk/README.md>`__ has the full
binding rules, including
+how unmatched fields and arguments are treated and when an untagged struct is
decoded whole from a single
+argument instead.
+
+Stub parameters the Dag author left at their Python defaults are the exception
to both shapes: they reach
+the wire but need no Go parameter, so adding a defaulted parameter to a stub
does not break the Go
+functions already bound to it.
+
.. _go-sdk/types:
XCom type mapping
diff --git a/go-sdk/README.md b/go-sdk/README.md
index a97303b6c96..8b2e7f490f0 100644
--- a/go-sdk/README.md
+++ b/go-sdk/README.md
@@ -74,67 +74,65 @@ known limitation.
## Authoring a bundle
-Implement `bundlev1.BundleProvider`, register your Dags and tasks, and `main`
is one line. From
+Build a bundle with `airflow.Bundle()`, register a handler for each task, and
call `Serve` last. From
[`example/bundle/main.go`](./example/bundle/main.go):
```go
-type myBundle struct{}
-
-var _ v1.BundleProvider = (*myBundle)(nil)
+func main() {
+ bundle := airflow.Bundle()
-func (m *myBundle) RegisterDags(dagbag v1.Registry) error {
- simpleDag := dagbag.AddDag("simple_dag")
- simpleDag.AddTask(extract)
- simpleDag.AddTask(transform)
- return nil
-}
+ bundle.Register(
+ airflow.TaskHandler("simple_dag", "extract", extract),
+ airflow.TaskHandler("simple_dag", "transform", transform),
+ )
-func main() {
- if err := bundlev1server.Serve(&myBundle{}); err != nil {
+ if err := bundle.Serve(); err != nil {
log.Fatal(err)
}
}
```
-A task is an ordinary Go function. The runtime inspects its signature and
injects arguments by type:
-`sdk.TIRunContext`, `*slog.Logger`, and an `sdk.Client` (or a narrower
interface such as
-`sdk.VariableClient`). An optional `(any, error)` return becomes the task's
XCom; an `error` return marks
-the task failed.
+`TaskHandler` names the `dag_id` and the `task_id` explicitly; neither is
derived from the Go function
+name, so a handler can be called whatever reads best in Go. It checks the
signature of the function and
+panics if the check fails, which stops the executable as it starts rather than
when the task first runs.
+A package that defines handlers of its own can export them as a
`[]airflow.Registerable` for `main` to
+pass on with `bundle.Register(reports.Handlers()...)`.
-Any other parameter is a **data parameter**, filled in declaration order from
the arguments of the
-Python stub Dag's TaskFlow call. A literal in the Dag file (`transform("uk",
...)`) decodes straight
-into the parameter; an upstream task's output (`transform(..., extract())`) is
pulled from that
-task's XCom in the current Dag run. If the argument count doesn't match, or an
argument's declared
-type can't fill the Go type, the task fails before its body runs.
+A task is an ordinary Go function whose first parameter is an
[`airflow.Context`](#the-task-context).
+An optional `(any, error)` return becomes the task's XCom; an `error` return
marks the task failed.
+
+Every parameter after the Context is a **data parameter**, filled in
declaration order from the
+arguments of the Python stub Task's TaskFlow call. A literal in the Dag file
(`transform("uk", ...)`)
+decodes straight into the parameter; an upstream task's output
(`transform(..., extract())`) is
+pulled from that task's XCom in the current Dag run. If the argument count
doesn't match, or an
+argument's declared type can't fill the Go type, the task fails before its
body runs.
Stub parameters the Dag author left at their Python defaults are the
exception: they reach the wire
but need no Go parameter, so adding a defaulted parameter to a stub doesn't
break the Go functions
already bound to it.
```go
-func extract(ctx sdk.TIRunContext, client sdk.Client, log *slog.Logger) (any,
error) {
- conn, err := client.GetConnection(ctx, "test_http")
- // ... do work, honour ctx cancellation ...
+func extract(actx airflow.Context) (any, error) {
+ conn, err := actx.Client().GetConnection(actx, "test_http")
+ // ... do work, honour actx cancellation ...
return map[string]any{"go_version": runtime.Version()}, nil
}
// The stub's literal and XCom arguments bind to country and extracted.
-func transform(
- ctx sdk.TIRunContext, client sdk.VariableClient, log *slog.Logger,
- country string, extracted map[string]any,
-) error {
- val, err := client.GetVariable(ctx, "my_variable")
+func transform(actx airflow.Context, country string, extracted map[string]any)
error {
+ val, err := actx.Client().GetVariable(actx, "my_variable")
if err != nil {
return err
}
- log.Info("Obtained variable", "my_variable", val, "country", country)
+ actx.Logger().InfoContext(actx, "Obtained variable", "my_variable", val,
"country", country)
return nil
}
```
-Asking for the narrowest interface a task needs (e.g. `sdk.VariableClient`
instead of `sdk.Client`) makes
-unit testing easier and documents which Airflow features the task touches.
`RegisterDags` is the single
-source of truth for which `dag_id`s and `task_id`s a bundle can run.
+The `Register` calls are the single source of truth for which `dag_id`s and
`task_id`s a bundle can run.
+A helper a task calls can still ask for the narrowest interface it needs (e.g.
`sdk.VariableClient`
+instead of `sdk.Client`), which documents the Airflow features it touches and
lets a test pass a fake;
+a handler itself takes the Context and reaches the client through it.
### Name-based struct binding
@@ -148,8 +146,8 @@ type CombineInput struct {
Threshold float64
}
-// The stub Dag calls combine(region_code="uk", threshold=0.5).
-func Combine(ctx sdk.TIRunContext, log *slog.Logger, input CombineInput) (any,
error) {
+// The Python stub Task calls combine(region_code="uk", threshold=0.5).
+func Combine(actx airflow.Context, input CombineInput) (any, error) {
return nil, nil
}
```
@@ -169,37 +167,54 @@ data parameters is rejected at registration. A sole
struct parameter also falls
decoding when it gets exactly one passed argument no field claims, so a task
can still take an
upstream object as a single argument.
-### Reading the task runtime context
+### The task context
+
+`airflow.Context` is the first parameter of every task handler. Everything
Airflow gives the task is a method on it:
+
+| Method | What it returns |
+| --- | --- |
+| `actx.Logger()` | the `*slog.Logger` that writes to the task's Airflow log |
+| `actx.Client()` | the `sdk.Client` for Variables, Connections and XCom |
+| `actx.TaskInstance()` | `DagID`, `RunID`, `TaskID`, `MapIndex` (nil for an
unmapped task), `TryNumber` |
+| `actx.DagRun()` | `DagID`, `RunID`, and the `*time.Time` fields
`LogicalDate`, `DataIntervalStart`, `DataIntervalEnd` (nil when the run has no
such value, e.g. a manual trigger) |
-Declare an `sdk.TIRunContext` parameter on a task to read the identifiers and
scheduling timestamps of the
-running task instance and its Dag run -- the Go equivalent of the execution
context the Python and Java SDKs
-expose. It is an interface that embeds `context.Context`, so the same `ctx`
drives cancellation and client
-calls. The runtime binds it by type, just like the other injected parameters:
+It is itself a `context.Context`, so pass it straight to a client call or to
+`http.NewRequestWithContext`, and select on `actx.Done()`, which fires when
the supervisor asks the
+task to stop. Cleanup that must outlive that cancellation runs under
`context.WithoutCancel(actx)`.
+A helper typed as a plain `context.Context` recovers the same surface with
`airflow.FromContext`.
```go
-func extract(ctx sdk.TIRunContext, log *slog.Logger) (any, error) {
- ti := ctx.TaskInstance()
- log.Info("running",
+func extract(actx airflow.Context) (any, error) {
+ ti := actx.TaskInstance()
+ actx.Logger().InfoContext(actx, "running",
"dag_id", ti.DagID,
"run_id", ti.RunID,
"task_id", ti.TaskID,
"try_number", ti.TryNumber,
- "logical_date", ctx.DagRun().LogicalDate,
+ "logical_date", actx.DagRun().LogicalDate,
)
return nil, nil
}
```
-`ctx.TaskInstance()` returns `DagID`, `RunID`, `TaskID`, `MapIndex` (nil for
an unmapped task), and
-`TryNumber`; `ctx.DagRun()` returns `DagID`, `RunID`, and the `*time.Time`
fields `LogicalDate`,
-`DataIntervalStart`, and `DataIntervalEnd` (nil when the run has no such
value, e.g. a manual trigger).
+`airflow.NewContext` builds one, so a handler is an ordinary function call in
a unit test -- see
+[`example/bundle/main_test.go`](./example/bundle/main_test.go):
+
+```go
+actx := airflow.NewContext(
+ context.Background(), slog.Default(), &mockVars{},
+ airflow.TaskInstance{}, airflow.DagRun{},
+)
+err := transform(actx, "uk", map[string]any{"go_version": "go1.24"})
+assert.NoError(t, err)
+```
### Task logging
-In coordinator mode, the injected logger filters records using Airflow's
configured `[logging] logging_level` before sending them to the supervisor.
Airflow also propagates `[logging] namespace_levels`; use a group-scoped logger
to set the namespace:
+In coordinator mode, the logger from `actx.Logger()` filters records using
Airflow's configured `[logging] logging_level` before sending them to the
supervisor. Airflow also propagates `[logging] namespace_levels`; use a
group-scoped logger to set the namespace:
```go
-databaseLog := log.WithGroup("example.database")
+databaseLog := actx.Logger().WithGroup("example.database")
databaseLog.Debug("query complete", "rows", 42)
```
diff --git a/go-sdk/adr/0007-mixed-lang-task-handler-interface.md
b/go-sdk/adr/0007-mixed-lang-task-handler-interface.md
index 6150ca4c2fc..f3739149248 100644
--- a/go-sdk/adr/0007-mixed-lang-task-handler-interface.md
+++ b/go-sdk/adr/0007-mixed-lang-task-handler-interface.md
@@ -30,7 +30,7 @@ Proposed.
1. **A "bundle" is a value the author builds.** `airflow.Bundle()` returns a
`*airflow.BundleRef`;
`main` reads build, register, serve, with `bundle.Serve()` as its last
statement.
It replaces `BundleProvider` and `Registry`, the callback and the write
half of the same bundle.
-2. **`bundle.Register(items ...airflow.Registraterable)`** is the single
registration verb, taking native Dags and task handlers.
+2. **`bundle.Register(items ...airflow.Registerable)`** is the single
registration verb, taking native Dags and task handlers.
3. **A Go bundle registers task handlers, not Dags**:
`airflow.TaskHandler(dagId, taskId, fn)`, the Go body for a task Python
declares with `@task.stub`.
4. **Both dag_id and task_id are written out on TaskHandler definition**,
because Python owns them; nothing is derived from the Go function name.
5. **Every handler must take an `airflow.Context` first**: a struct embedding
`context.Context`, exposing `Logger()`, `Client()`, `TaskInstance()`, and
`DagRun()`.
@@ -75,7 +75,7 @@ func main() {
}
```
-Registration can be spread across packages, either by passing the bundle along
or by returning `[]airflow.Registraterable` for the caller:
`bundle.Register(taskflowbinding.Handlers()...)`.
+Registration can be spread across packages, either by passing the bundle along
or by returning `[]airflow.Registerable` for the caller:
`bundle.Register(taskflowbinding.Handlers()...)`.
Three ways a Go function receives a stub task's data, all live in
`go-sdk/example/bundle/`.
@@ -126,15 +126,15 @@ package airflow
func Bundle() *BundleRef
-func (b *BundleRef) Register(items ...Registraterable)
+func (b *BundleRef) Register(items ...Registerable)
func (b *BundleRef) Serve() error
-// Registraterable is sealed: its only method is unexported, so the set of
things a bundle
+// Registerable is sealed: its only method is unexported, so the set of things
a bundle
// accepts stays closed to the SDK's own types — task handlers today, a Dag
authored in Go
// once there is one.
-type Registraterable interface{ registraterable() }
+type Registerable interface{ registerable() }
-func TaskHandler(dagId, taskId string, fn any) Registraterable
+func TaskHandler(dagId, taskId string, fn any) Registerable
// Context is what every handler takes first. It is a context, so it passes
straight to the
// logger and the client rather than being stored inside either of them.
diff --git a/go-sdk/cmd/airflow-go-pack/pack_integration_test.go
b/go-sdk/cmd/airflow-go-pack/pack_integration_test.go
index f064935d6a5..f0b735ebf2c 100644
--- a/go-sdk/cmd/airflow-go-pack/pack_integration_test.go
+++ b/go-sdk/cmd/airflow-go-pack/pack_integration_test.go
@@ -67,8 +67,8 @@ func TestPack_CrossArchExecutableWithMetadataFile(t
*testing.T) {
t.Skipf("no cross-arch mapping for host arch %q",
runtime.GOARCH)
}
- // The example bundle is a real BundleProvider that answers
- // --airflow-metadata, so it exercises the genuine metadata path.
+ // The example bundle is a real bundle that answers --airflow-metadata,
+ // so it exercises the genuine metadata path.
exampleDir, err := filepath.Abs(filepath.Join("..", "..", "example",
"bundle"))
require.NoError(t, err)
sourceFile := filepath.Join(exampleDir, "main.go")
diff --git a/go-sdk/example/bundle/concurrentxcom/concurrentxcom.go
b/go-sdk/example/bundle/concurrentxcom/concurrentxcom.go
index bb810ab65a2..e85111421ec 100644
--- a/go-sdk/example/bundle/concurrentxcom/concurrentxcom.go
+++ b/go-sdk/example/bundle/concurrentxcom/concurrentxcom.go
@@ -17,7 +17,7 @@
// Package concurrentxcom holds the pull_xcoms_concurrently task in its own
// package, so main.go can register tasks defined across packages with one
-// RegisterDags.
+// bundle.Register call.
package concurrentxcom
import (