Skip to content

fix(idempotency): read expiry from expiry_attr in CachePersistenceLayer - #8606

Merged
leandrodamascena merged 2 commits into
aws-powertools:developfrom
Thanatos9404:fix/8575-redis-expiry-attr
Oct 22, 2026
Merged

leandrodamascena merged 2 commits into
aws-powertools:developfrom
Thanatos9404:fix/8575-redis-expiry-attr

Conversation

@Thanatos9404

Copy link
Copy Markdown
Contributor

Issue number: closes #8575

Summary

Changes

_item_to_data_record read the expiry with item.get("expiration"), while _put_in_progress_record and _update_record write it under self.expiry_attr. With a custom expiry_attr, every record came back with expiry_timestamp=None. It now reads self.expiry_attr, like the other attributes.

I checked the other attribute names in the same method: in_progress_expiry_attr, status_attr, data_attr and validation_key_attr already use the configured names. "expiration" was the only hardcoded one.

Tests (added to tests/functional/idempotency/_redis/test_redis_layer.py, each run with the default expiration and a custom exp):

  • test_get_record_reads_expiry_from_expiry_attr: the snippet from the issue. A COMPLETED record stored with an expiry of 1 reads back with expiry_timestamp == 1, is_expired true and the matching get_expiration_datetime().
  • test_idempotent_function_redis_expiry_attr_round_trip: through @idempotent_function with a response_hook:
    • the first call stores the record with the expiry under the configured name (and no other expiry key);
    • the second call returns the stored response, and the hook gets the stored expiration instead of None;
    • once the stored expiry is in the past while the key is still in the cache, the function runs again instead of returning the old response.

The two exp cases fail on develop (expiry_timestamp is None, and the hook gets [None]); the expiration cases pass on both. All 4 pass with this change.

Checks

  • tests/functional/idempotency and tests/unit/idempotency: 198 passed.
  • ruff check and ruff format --check on the changed files: clean.
  • mypy on redis.py: clean. ty check on redis.py shows the same 8 deprecation warnings as develop (the deprecated Redis* names) and nothing new.

User experience

Before:

layer = CachePersistenceLayer(client=my_redis_client, expiry_attr="exp")
my_redis_client.set("k", '{"status": "COMPLETED", "exp": 1, "data": "{}"}')

record = layer._get_record("k")
print(record.expiry_timestamp, record.is_expired)  # None False

After: 1 True.

What this changes for users with a custom expiry_attr:

  • data_record.get_expiration_datetime() in a response_hook returns the expiration instead of None.
  • A record that has passed its expiry but is still in the cache is treated as expired, so the function runs again instead of returning the old response.
  • With use_local_cache=True, a record read from the cache (one written by another execution environment) is stored in the local cache with its expiry. Before, it had no expiry, so the local copy never expired and was served until it was evicted.

With the default expiry_attr, nothing changes.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

_item_to_data_record read the expiry timestamp from a hardcoded
"expiration" key, while records are written under self.expiry_attr.
With a custom expiry_attr, records came back without expiry_timestamp:
is_expired was always False, get_expiration_datetime() returned None in
the response hook, and an expired record still in the cache was
returned instead of running the function again.
@Thanatos9404
Thanatos9404 requested a review from a team as a code owner October 18, 2026 12:13
@Thanatos9404
Thanatos9404 requested a review from svozza October 18, 2026 12:13
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Oct 18, 2026
@boring-cyborg boring-cyborg Bot added the tests label Oct 18, 2026
@leandrodamascena leandrodamascena added this to the 3.38.0 milestone Oct 19, 2026

@leandrodamascena leandrodamascena 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.

Thanks @Thanatos9404, two for two! I ran the suite and compared develop and your branch side by side with a custom expiry_attr: on develop an expired record that is still in Redis gets replayed forever, with your fix it re-runs like DynamoDB does, and the response hook finally sees the real expiration. Default expiration behaves exactly as before.

Approving now, this one also goes out in 3.38.0, so it will wait here a bit before I merge. No need to rebase or touch anything, I'll handle it and you'll get the credit in the release notes. Thank you!

@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.93%. Comparing base (ab83696) to head (ac156f6).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #8606   +/-   ##
========================================
  Coverage    96.93%   96.93%           
========================================
  Files          318      318           
  Lines        16067    16067           
  Branches      1430     1430           
========================================
  Hits         15575    15575           
  Misses         349      349           
  Partials       143      143           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sonarqubecloud

Copy link
Copy Markdown

@leandrodamascena
leandrodamascena merged commit 1d09a3b into aws-powertools:develop Oct 22, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Denotes a PR that changes 30-99 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: CachePersistenceLayer ignores a custom expiry_attr when reading records

2 participants