@@ -23,9 +23,13 @@ package org.apache.airflow.sdk.internal
2323
2424import com.fasterxml.jackson.databind.ObjectMapper
2525import com.fasterxml.jackson.databind.json.JsonMapper
26+ import org.apache.airflow.sdk.Arg
2627import org.apache.airflow.sdk.Client
28+ import org.apache.airflow.sdk.Context
29+ import org.apache.airflow.sdk.LiteralArg
2730import org.apache.airflow.sdk.MissingXComException
2831import org.apache.airflow.sdk.TaskInput
32+ import org.apache.airflow.sdk.TaskRef
2933import org.apache.airflow.sdk.execution.ArgBinding
3034import org.apache.airflow.sdk.execution.Logger
3135import java.lang.reflect.Field
@@ -42,6 +46,11 @@ import java.lang.reflect.Type
4246 * graph the scheduler ordered the run by. Flat data parameters resolve the
4347 * binding at their position (through [TaskArgs]); [TaskInput] fields resolve
4448 * bindings by name.
49+ *
50+ * A natively authored Dag has no stub call site, so the supervisor sends no
51+ * bindings for it and the inputs the Dag itself wired stand in. When the
52+ * supervisor sends bindings they are used for every parameter; the Dag's own
53+ * inputs are read only when it sends none.
4554 */
4655object ArgValues {
4756 private val mapper: ObjectMapper = JsonMapper .builder().build().findAndRegisterModules()
@@ -68,9 +77,17 @@ object ArgValues {
6877 */
6978 @JvmStatic
7079 fun <I : TaskInput > bindInput (
80+ context : Context ,
7181 client : Client ,
7282 type : Class <I >,
7383 ): I {
84+ // Runtime bindings carry argument names to match fields against. A wired
85+ // input carries none, so it decodes into the whole input at once -- which
86+ // is well defined because a TaskInput is a task's only data parameter.
87+ wiredInputs(context, client)?.let { wired ->
88+ return decode(resolveWired(wired[0 ], client), type) as I ?
89+ ? : throw missingInput(wired[0 ], type.simpleName)
90+ }
7491 val input = newInput(type)
7592 val arguments = ArgIndex (client.argBindings)
7693 val unfilled = mutableListOf<String >()
@@ -147,6 +164,66 @@ object ArgValues {
147164 type : Type ,
148165 ): Any? = decode(client.resolveBinding(binding), type)
149166
167+ /* *
168+ * Resolves the wired input at a data parameter's position into [type],
169+ * passing null through.
170+ */
171+ internal fun valueWired (
172+ input : Arg <* >,
173+ client : Client ,
174+ type : Type ,
175+ ): Any? = decode(resolveWired(input, client), type)
176+
177+ /* *
178+ * The inputs the Dag wired for this task, or null when the run's arguments
179+ * come from the stub call site. A task with no wired inputs reads the
180+ * bindings, so a stub call that bound nothing keeps its own diagnostics.
181+ */
182+ internal fun wiredInputs (
183+ context : Context ,
184+ client : Client ,
185+ ): List <Arg <* >>? = if (client.argBindings.isEmpty()) context.taskDef?.inputs?.takeIf { it.isNotEmpty() } else null
186+
187+ /* *
188+ * The failure for a wired argument that resolved to nothing where a value is
189+ * required, naming [target] — the position of the parameter it feeds.
190+ */
191+ internal fun missingWired (
192+ input : Arg <* >,
193+ target : String ,
194+ ): MissingXComException =
195+ when (input) {
196+ is TaskRef <* > -> MissingXComException (input.def.id, target)
197+ else ->
198+ MissingXComException (
199+ " Task parameter '$target ' is wired to a null literal, but has a primitive type that cannot " +
200+ " be null; declare a boxed type (e.g. Integer instead of int) to receive null." ,
201+ )
202+ }
203+
204+ /* * The failure for a wired input that resolved to nothing for a [TaskInput]. */
205+ private fun missingInput (
206+ input : Arg <* >,
207+ target : String ,
208+ ): MissingXComException =
209+ when (input) {
210+ is TaskRef <* > ->
211+ MissingXComException (
212+ " Input '$target ' requires an XCom from task '${input.def.id} ', but none was pushed." ,
213+ )
214+ else -> MissingXComException (" Input '$target ' is wired to a null literal, so there is nothing to bind." )
215+ }
216+
217+ private fun resolveWired (
218+ input : Arg <* >,
219+ client : Client ,
220+ ): Any? =
221+ when (input) {
222+ is TaskRef <* > -> client.getXCom(taskId = input.def.id)
223+ is LiteralArg <* > -> input.value
224+ else -> null
225+ }
226+
150227 /* *
151228 * Builds the failure for a binding that resolved to nothing where a value is
152229 * required, naming [target] — the stub argument, or the [TaskInput] field
0 commit comments