Skip to content

Fix DateTimeSensor crash when target_time renders to a datetime#70320

Open
1fanwang wants to merge 4 commits into
apache:mainfrom
1fanwang:fix-datetime-sensor-init-template-field
Open

Fix DateTimeSensor crash when target_time renders to a datetime#70320
1fanwang wants to merge 4 commits into
apache:mainfrom
1fanwang:fix-datetime-sensor-init-template-field

Conversation

@1fanwang

@1fanwang 1fanwang commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

target_time is a template field, so it is only rendered after __init__ runs. DateTimeSensor validated and normalized it in the constructor — acting on the un-rendered Jinja expression — and poke() parsed self.target_time directly with timezone.parse(). When target_time is templated and the Dag uses render_template_as_native_obj=True, the field renders to a datetime, and poke() crashed:

TypeError: argument 'input': 'DateTime' object cannot be cast as 'str'

The deferrable path already read the value through _moment, which handles both str and datetime, so only the synchronous sensor was affected. The constructor now stores target_time verbatim, and the datetime→ISO-string normalization it used to do is relocated to poke() (the post-render execute path) rather than dropped, so self.target_time stays a string once the sensor runs. _moment still parses and validates the rendered value, and both sensors route through it. __init__ only assigns the template field now, in line with the burn-down in #70296.

related: #70296

Testing Done

Operator-level repro (real Dag, real render, real poke) — before the fix:

target_time after render: DateTime(2020, 1, 1, 0, 0, 0, tzinfo=Timezone('UTC')) DateTime
Traceback (most recent call last):
  ...
  return timezone.utcnow() > timezone.parse(self.target_time)
TypeError: argument 'input': 'DateTime' object cannot be cast as 'str'

After the fix:

target_time after render: DateTime(2020, 1, 1, 0, 0, 0, tzinfo=Timezone('UTC')) DateTime
poke -> True

test_date_time.py: 13 passed. The two normalization guards — poke() turns a native-rendered and a constructor-passed datetime into its ISO string — fail on the pre-fix source and pass after.


Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: GitHub Copilot CLI following the guidelines

@1fanwang
1fanwang marked this pull request as draft July 24, 2026 05:47
@1fanwang
1fanwang marked this pull request as ready for review July 24, 2026 06:23
@shahar1
shahar1 dismissed their stale review July 24, 2026 06:24

Concerns addressed

@shahar1 shahar1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Almost there,
Also - please ensure enough test coverage for all edge cases, thanks!

Comment thread providers/standard/src/airflow/providers/standard/sensors/date_time.py Outdated
Comment thread providers/standard/src/airflow/providers/standard/sensors/date_time.py Outdated
1fanwang added 3 commits July 24, 2026 11:04
target_time is a template field, so it is only rendered after __init__ runs.
DateTimeSensor validated and normalized it in the constructor (acting on the
un-rendered Jinja expression), and poke() parsed self.target_time directly with
timezone.parse(). With a templated target_time and render_template_as_native_obj
enabled, the field renders to a datetime and poke() raised
"TypeError: 'DateTime' object cannot be cast as 'str'".

Store target_time verbatim in the constructor and defer normalization and
validation to _moment, the accessor that already runs after rendering; poke()
now goes through it.

related: apache#70296
Signed-off-by: 1fanwang <1fannnw@gmail.com>
The earlier refactor removed the datetime-to-ISO-string normalization that
the constructor used to apply to target_time, leaving a datetime stored in
the template field. Relocate that normalization to poke (the post-render
execute path) so a datetime target_time is again stored as an ISO string,
while __init__ keeps only the plain assignment.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
poke() is not reached by DateTimeSensorAsync, which overrides execute(); moving the
datetime normalization into _moment (the shared post-render accessor) localizes a
naive datetime for both the sync and async sensors, and drops the explanatory
comments.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwang force-pushed the fix-datetime-sensor-init-template-field branch from 2d99c66 to 0e4bc6a Compare July 24, 2026 18:05
Drop exemption entries that sibling burn-down PRs already resolved on main, which a stale rebase base had re-introduced. Net change to this file is only the DateTimeSensor removal.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwang force-pushed the fix-datetime-sensor-init-template-field branch from 1f67ce4 to 2ffad8d Compare July 25, 2026 09:22
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