Skip to content

Make cron timetables hashable - #73859

Merged
ashb merged 4 commits into
apache:mainfrom
Pebble32:fix-cron-timetable-hash
Sep 29, 2026
Merged

ashb merged 4 commits into
apache:mainfrom
Pebble32:fix-cron-timetable-hash

Conversation

@Pebble32

@Pebble32 Pebble32 commented Sep 28, 2026 •

Copy link
Copy Markdown

hash() on any cron-based timetable raises, because CronMixin.__hash__ puts the pendulum Timezone object, which is unhashable, into the hashed tuple:

>>> from airflow.timetables.trigger import CronTriggerTimetable
>>> hash(CronTriggerTimetable("0 0 * * *", timezone="UTC"))
TypeError: unhashable type: 'Timezone'

Hash the timezone name instead, which is what __eq__ effectively compares, so equal timetables still hash equal.

Found while working on #72475, where the fix rode along; split out here as suggested in review since it is an independent bugfix that can land and be backported on its own.

Tests

test_cron_timetables_are_hashable in test_trigger_timetable.py: hash() no longer raises, and two equal timetables hash equal.


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

Generated-by: Claude (Claude Code), following the guidelines


related: #72475

Signed-off-by: Adam <111773160+Pebble32@users.noreply.github.com>
Signed-off-by: Adam <111773160+Pebble32@users.noreply.github.com>
@Pebble32
Pebble32 marked this pull request as ready for review September 29, 2026 07:55
Comment thread airflow-core/tests/unit/timetables/test_trigger_timetable.py Outdated
Comment thread airflow-core/newsfragments/73859.bugfix.rst Outdated
Pebble32 and others added 2 commits September 29, 2026 10:52
Co-authored-by: Ash Berlin-Taylor <ash_github@firemirror.com>
Signed-off-by: Adam <111773160+Pebble32@users.noreply.github.com>
@Pebble32
Pebble32 requested a review from ashb September 29, 2026 11:41
@Pebble32

Copy link
Copy Markdown
Author

Root cause fix on the pendulum side: python-pendulum/pendulum#1021

@ashb
ashb merged commit 5eba743 into apache:main Sep 29, 2026
78 checks passed
@boring-cyborg

boring-cyborg Bot commented Sep 29, 2026

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

@github-actions github-actions Bot added this to the Airflow 3.3.3 milestone Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi maintainer, this PR was merged without a milestone set.
We've automatically set the milestone to Airflow 3.3.3 based on: backport label targeting v3-3-test
If this milestone is not correct, please update it to the appropriate milestone.

This comment was generated by Milestone Tag Assistant.

@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-3-test. View the failure log Run details

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
❌ v3-3-test Commit Link

You can attempt to backport this manually by running:

cherry_picker 5eba743 v3-3-test

This should apply the commit to the v3-3-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

If you don't have cherry-picker installed, see the installation guide.

ashb pushed a commit that referenced this pull request Sep 29, 2026

@namanjain24-sudo namanjain24-sudo 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.

Nice fix — using `str(self._timezone)` is a good general approach: it also fixes `FixedTimezone` instances (e.g. from an integer UTC-offset), which are independently unhashable in pendulum for the same reason (`eq` defined without `hash`), not just named `Timezone`/`ZoneInfo` timezones. Confirmed it also covers `CronDataIntervalTimetable` and `CronPartitionTimetable` since they all share `CronMixin`.

One minor edge case worth being aware of (not blocking, and not reachable through Airflow's own `parse_timezone`): `FixedTimezone.eq` only compares `_offset`, but `str()`/`repr()` also encodes `name`, so two `FixedTimezone` instances with the same offset but different explicit names would be `==` but hash differently. Only matters if something constructs a `FixedTimezone` directly with a custom name rather than going through `parse_timezone`, which only ever produces the offset-derived default name.


Drafted-by: Claude Code (Sonnet 5) (no human review before posting)

@namanjain24-sudo

Copy link
Copy Markdown
Contributor

(Formatting fix — the backticks in my review above got double-escaped and render literally. Reposting cleanly:)

Nice fix — using str(self._timezone) is a good general approach: it also fixes FixedTimezone instances (e.g. from an integer UTC-offset), which are independently unhashable in pendulum for the same reason (__eq__ defined without __hash__), not just named Timezone/ZoneInfo timezones. Confirmed it also covers CronDataIntervalTimetable and CronPartitionTimetable since they all share CronMixin.

One minor edge case worth being aware of (not blocking, and not reachable through Airflow's own parse_timezone): FixedTimezone.__eq__ only compares _offset, but str()/repr() also encodes name, so two FixedTimezone instances with the same offset but different explicit names would be == but hash differently. Only matters if something constructs a FixedTimezone directly with a custom name rather than going through parse_timezone, which only ever produces the offset-derived default name.


Drafted-by: Claude Code (Sonnet 5) (no human review before posting)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants