Skip to content

fix(idempotency): add deterministic support for set, frozenset, and bytes types - #8741

Merged
leandrodamascena merged 2 commits into
aws-powertools:developfrom
Neeraj111010:fix/8725-idempotency-set-bytes-support
Nov 14, 2026
Merged

leandrodamascena merged 2 commits into
aws-powertools:developfrom
Neeraj111010:fix/8725-idempotency-set-bytes-support

Conversation

@Neeraj111010

@Neeraj111010 Neeraj111010 commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: closes #8725

Summary

This PR adds native support for set, frozenset, and bytes types to the AWS Lambda Powertools _IdempotencyKeyEncoder. It ensures sets and frozensets are sorted deterministically regardless of insertion order, and encodes bytes using Base64 so they can be securely and reliably hashed and validated in the idempotency utility

Changes

  • Encoder Logic (aws_lambda_powertools/utilities/idempotency/persistence/base.py):
    • Added support for set and frozenset by converting them into sorted lists to guarantee identical hashes across varying element insertion orders, with safe fallback sorting (key=repr) for non-comparable items.
    • Added Base64 encoding support for bytes (base64.b64encode(obj).decode("utf-8")).
  • Test Suite (tests/functional/idempotency/_boto3/test_idempotency_hash_serialization.py):
    • Removed set and bytes from unsupported error test cases.
    • Added test_set_frozenset_and_bytes_support to validate persistent replay, payload validation, and insertion order determinism.

User experience

  • Before: Passing a payload containing set, frozenset, or bytes objects to an idempotent function would raise a TypeError because they were not JSON serializable or produced non-deterministic hashes due to unordered iteration.
  • After: These types are now natively and deterministically serialized, allowing seamless caching and payload validation without requiring custom serializations.

Verification

  • Linting & Formatting: Ran make lint / ruff, passing all style and formatting checks with zero errors or warnings.
  • Test Suite: Ran uv run pytest tests/functional/idempotency/, with 33 functional test cases passing successfully.
  • AI Assistance Note: An AI assistant was used to help structure the implementation logic and write test cases, which were fully verified and tested locally by the author.

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.

@Neeraj111010
Neeraj111010 requested a review from a team as a code owner November 1, 2026 22:57
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Nov 1, 2026
@boring-cyborg boring-cyborg Bot added the tests label Nov 1, 2026
@boring-cyborg

boring-cyborg Bot commented Nov 1, 2026

Copy link
Copy Markdown

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

@blacksoxx blacksoxx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment. The encoder change does what #8725 asks and the two insertion orders hashing to one key is the test that matters; one small gap in the docs.

Reviewed head fc3b4a5a against develop at 3b261550; both unchanged just before posting, PR open. Validation: diff read against the current default(); the key=repr fallback is deterministic for equal contents and an element that is not serialisable still fails when the sorted list is encoded, so no silent hash. Not run here: the idempotency suites. History: #8725 is the maintainers' own issue, assigned to you; no earlier PR for these types (searched "idempotency set frozenset bytes", "idempotency set bytes hash", 100 results per query, capped).

[P3] docs/utilities/idempotency.md line 195 lists which values the hash encoder converts (UUID, paths, dates, enums); sets, frozensets (as sorted lists) and bytes (base64) belong in that sentence now, otherwise the docs still say they are unsupported. No unrelated scope changes.

I would merge this with that line added; the decision is with the maintainers.

@leandrodamascena leandrodamascena added this to the 3.41.0 milestone Nov 4, 2026

@Manishram-ai Manishram-ai 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.

I tried this with a set of frozensets and the key changed between processes, details on the set branch below.

Comment on lines +60 to +64
if isinstance(obj, (set, frozenset)):
try:
return sorted(obj)
except TypeError:
return sorted(obj, key=repr)

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.

sorted() doesn't raise for a set of frozensets, because frozensets compare as subsets, so it just returns them in whatever order the set iterates. String hashes change between processes, so the same payload can get a different idempotency key after a cold start. I ran {"groups": {frozenset({"admins"}), frozenset({"billing"}), frozenset({"ops", "dev"})}} through the encoder with PYTHONHASHSEED 1 to 6 and got three different orders. The repr fallback has the same problem, since a frozenset's repr follows hash order too.

Sorting by each item's JSON gives the same order every time. With this, all six seeds give [["admins"], ["billing"], ["dev", "ops"]], and test_idempotency_hash_serialization.py still passes:

Suggested change
if isinstance(obj, (set, frozenset)):
try:
return sorted(obj)
except TypeError:
return sorted(obj, key=repr)
if isinstance(obj, (set, frozenset)):
# Sort by each item's JSON so the order doesn't depend on hashing. sorted() alone isn't enough:
# frozensets compare as subsets, and their repr() follows hash order, which changes between processes.
return sorted(obj, key=lambda item: json.dumps(item, cls=_IdempotencyKeyEncoder, sort_keys=True))

@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 @Neeraj111010! Sorting the sets and base64 for bytes only touches types that blow up today, so nothing stored changes its hash, which was my main worry. Ran the idempotency suite on top of today's develop, all green. Goes out in 3.41.0, no need to rebase, you'll be in the release notes.

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.17%. Comparing base (1e42825) to head (5886a97).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
...wertools/utilities/idempotency/persistence/base.py 88.88% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #8741      +/-   ##
===========================================
- Coverage    97.18%   97.17%   -0.02%     
===========================================
  Files          323      323              
  Lines        16562    16573      +11     
  Branches      1497     1499       +2     
===========================================
+ Hits         16096    16105       +9     
- Misses         326      328       +2     
  Partials       140      140              

☔ 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.

@leandrodamascena
leandrodamascena merged commit 8932649 into aws-powertools:develop Nov 14, 2026
12 of 14 checks passed
@boring-cyborg

boring-cyborg Bot commented Nov 14, 2026

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

@powertools-for-aws-oss-automation

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

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

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: idempotency payloads with set, frozenset or bytes fields cannot be hashed

4 participants