Uh oh!
There was an error while loading. Please reload this page.
Pass native datetime and UUID literals to stub tasks - #71536
Conversation
da71819 to
a517a7fCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
A @task.stub TaskFlow call could only carry values json.dumps already knew, so a datetime, date, timedelta or UUID argument was rejected outright and Dag authors had to hand-write the JSON spelling their lang SDK expected -- with nothing keeping that spelling consistent between authors, or in step with the value_schema the same parameter advertises. Rendering the value through the adapter that produced its schema means the two cannot disagree, and every language runtime sees one spelling per format. Timestamps are pinned to an explicit offset first: an offset-less timestamp is a different instant to each runtime -- UTC in Go, worker-local in JavaScript, unparsable in Java -- so leaving it naive on the wire would make a task's behaviour depend on the language that happens to run it.
a517a7f to
c8812b3Compare
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
c8812b3 to
31e01bcComparePydantic JSON mode accepts a much broader set of Python objects than the temporal and UUID contract, which could silently change previously rejected values. Nested timestamps also need the same timezone normalization as top-level arguments.
31e01bc to
108dbf5Compare
jason810496
left a comment
There was a problem hiding this comment.
Thanks TP for the review.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
uranusjr
left a comment
There was a problem hiding this comment.
Mostly nits on documentation wording
| ========================= ======================== ==================================== ================== ================================= | ||
| Python annotation JSON-schema signal Wire spelling Native target Inline literal handling | ||
| ========================= ======================== ==================================== ================== ================================= | ||
| ``datetime`` string + ``date-time`` ``2024-01-02T03:04:05Z`` timestamp converted |
| ``Enum`` (string value) string + ``enum`` ``"value"`` enum member rejected; pass ``.value`` | ||
| ``str``-backed ``Enum`` string + ``enum`` ``"value"`` enum JSON-native | ||
| ``int``-backed ``Enum`` integer + ``enum`` ``1`` enum JSON-native | ||
| ``Decimal`` number or string ``1.2`` or ``"1.20"`` decimal rejected; pass number/string |
There was a problem hiding this comment.
Same on alignment.
(Unrelatedly, I'm slightly worried about this one since decimal may be quite useful for specific data use cases. But we can always add support to this if someone raises an issue.)
| ``int``-backed ``Enum`` integer + ``enum`` ``1`` enum JSON-native | ||
| ``Decimal`` number or string ``1.2`` or ``"1.20"`` decimal rejected; pass number/string | ||
| ``Path`` string + ``path`` ``"/tmp/example"`` path or string rejected; pass string | ||
| ``set[datetime]`` array + ``uniqueItems`` ``["2024-01-02T03:04:05Z"]`` set of timestamps converted in stable order |
There was a problem hiding this comment.
Does the ordering point apply to any kind of set? It seems weird set[datetime] is specifically mentioned.
| ``set[datetime]`` array + ``uniqueItems`` ``["2024-01-02T03:04:05Z"]`` set of timestamps converted in stable order | ||
| ========================= ======================== ==================================== ================== ================================= | ||
| ``Inline literal handling`` describes a value captured directly from the Python Dag. The |
There was a problem hiding this comment.
This shouldn't be a code block (double backticks)
| Timestamps always carry an explicit offset -- a naive ``datetime`` is pinned to Airflow's | ||
| default timezone at serialization time -- because an offset-less timestamp means different | ||
| instants to different runtimes (UTC in Go, worker-local in JavaScript, unparsable in Java). |
There was a problem hiding this comment.
I would simply say they "have different offset handling semantics" instead. Saying "instants" may be confusing since an instant also mat means slightly different things in different languages.
@task.stubTaskFlow arg-binding contract; this fills in the value half of it.Why
A
@task.stubTaskFlow call may only carry valuesjson.dumpsalready knows, so the most natural arguments to pass a foreign-language task were rejected outright:The parameter's
value_schemaalready told the runtime this was{"type": "string", "format": "date-time"}but the value could not travel.What
Temporal and UUID literals are now rendered through the same Pydantic adapter that produced their
value_schema, so the value and schema cannot disagree by construction. The conversion recurses through typed lists, sequences, tuples, mapping values, sets, and unions, so supported values use the same wire spelling at every depth. Sets containing supported leaves become deterministically sorted JSON arrays whileuniqueItems: truetells the runtime it may reconstruct set semantics.value_schemadatetime(2024, 1, 2, 3, 4, 5)"2024-01-02T03:04:05Z"{"type": "string", "format": "date-time"}date(2024, 1, 2)"2024-01-02"{"type": "string", "format": "date"}time(3, 4, 5)"03:04:05"{"type": "string", "format": "time"}timedelta(days=1, hours=2)"P1DT2H"{"type": "string", "format": "duration"}UUID("6BA7B810-...")"6ba7b810-..."{"type": "string", "format": "uuid"}[datetime(2024, 1, 2, 3, 4, 5)]["2024-01-02T03:04:05Z"]{"type": "array", "items": {"type": "string", "format": "date-time"}}{datetime(2024, 1, 2), datetime(2024, 1, 3)}["2024-01-02T00:00:00Z", "2024-01-03T00:00:00Z"]{"type": "array", "items": {"type": "string", "format": "date-time"}, "uniqueItems": true}Status.READYfor plainEnum{"type": "string", "enum": ["ready"], "title": "Status"}Status.READYforstr-backed Enum"ready"{"type": "string", "enum": ["ready"], "title": "Status"}Priority.HIGHforint-backed Enum1{"type": "integer", "enum": [1], "title": "Priority"}Decimal("1.20")Path("/tmp/example"){"type": "string", "format": "path"}Only temporal and UUID leaves opt into Pydantic serialization. Plain Enum members,
Decimal, andPathretain their previous rejection behavior; string- and integer-backed Enums retain their existing JSON-native behavior, and bytes remain unsupported. Schema generation and literal conversion are deliberately separate: Pydantic may describe a type even when its Python object cannot be emitted as JSON.Timezone-naive timestamps
A naive
datetimeis pinned to an explicit offset before serializing, via the samecoerce_datetimethe rest of Airflow uses. An offset-less timestamp is a different instant to each language runtime, so leaving it naive would make a task's behaviour depend on which language happens to run it.Instant.parsenew Date2024-01-02T03:04:05Z03:04:05Z03:04:05Z03:04:05Z2024-01-02T03:04:0503:04:05Z08:04:05Z(worker-local)Was generative AI tooling used to co-author this PR?