Skip to content

fix(idempotency): resolve tech debt issues with falsy responses and Redis persistency#8176

Merged
leandrodamascena merged 10 commits intoaws-powertools:developfrom
hirenkumar-n-dholariya:fix/idempotency-tech-debt-8090
Apr 30, 2026
Merged

fix(idempotency): resolve tech debt issues with falsy responses and Redis persistency#8176
leandrodamascena merged 10 commits intoaws-powertools:developfrom
hirenkumar-n-dholariya:fix/idempotency-tech-debt-8090

Conversation

@hirenkumar-n-dholariya
Copy link
Copy Markdown
Contributor

@hirenkumar-n-dholariya hirenkumar-n-dholariya commented Apr 27, 2026

Issue number: fixes #8090

Summary

Fixes all 4 tech debt issues found during code review of the idempotency utility.

Changes

aws_lambda_powertools/utilities/idempotency/base.py

  • Fix falsy response handling: if response else Noneif response is not None else None
    so valid falsy return values (0, False, {}, [], "") are correctly serialized and stored instead of being silently discarded on replay
  • Fix inconsistent status constant: string literal "INPROGRESS"
    STATUS_CONSTANTS["INPROGRESS"] for consistency with the rest of the codebase

aws_lambda_powertools/utilities/idempotency/persistence/redis.py

  • Fix str(None) producing the string "None" in _item_to_data_record() when data_attr or validation_key_attr is missing from Redis

User experience

Before: Functions returning falsy values (0, False, {}) would have their response silently discarded and re-executed on replay instead of returning the cached value.

After: All non-None return values including falsy ones are correctly cached and returned on replay.

Tests

Added one regression test in tests/functional/idempotency/_redis/test_redis_layer.py next to existing test_item_to_datarecord_conversion, verifying missing Redis attributes return None instead of the string "None".

Acknowledgment


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.

Copy link
Copy Markdown
Contributor

@leandrodamascena leandrodamascena left a comment

Choose a reason for hiding this comment

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

Hey @hirenkumar-n-dholariya, thanks for working on this and submitting the PR!

The Redis str(None) fix is good. Removing the str() wrapper is the right fix.

However, I have some concerns with the rest of the PR, and there are missing items from the issue.

Missing items from the issue

In your comment on #8090 you mentioned that items 1 and 2 "appear already resolved", but they are still present in the codebase:

Item 1 - Falsy response handling is still in base.py:299:

serialized_response: dict = self.output_serializer.to_dict(response) if response else None

This silently discards valid falsy return values like 0, False, "", [], {}. It should be if response is not None. This is a real bug that affects customers.

Item 2 - "INPROGRESS" string literal is still in base.py:170:

if is_replay and record is not None and record.status == "INPROGRESS":

Should use STATUS_CONSTANTS["INPROGRESS"] for consistency with the rest of the code.

Helper method

The helper _get_idempotency_key_or_return_none() doesn't actually reduce the duplication. After the refactor, each caller still does:

idempotency_key = self._get_idempotency_key_or_return_none(data=data)
if idempotency_key is None:
    return None

We moved one line into a wrapper but the if None: return is still in all 4 methods. The helper is a passthrough that adds indirection without simplifying anything. Let's drop this part and keep the original inline pattern, it's 3 lines and every Python developer reads it instantly.

Tests

The tests need a full rework. Please don't create a new test file for this.

We already have test_item_to_datarecord_conversion in tests/functional/idempotency/_redis/test_redis_layer.py (line 318) that tests exactly _item_to_data_record(). Add your test case there, next to the existing one. That's our pattern: tests go in the existing test files, close to related tests.

What's wrong with the current tests:

  • Don't test Python builtins. test_str_none_produces_wrong_string asserts that str(None) == "None" and test_redis_missing_data_attr_returns_none_not_string asserts that dict.get() returns None. These are testing the Python language, not our code.
  • Don't mock internal methods with MagicMock. We don't do that in idempotency tests. Look at the existing tests in _redis/test_redis_layer.py and _boto3/test_idempotency.py for the patterns we use. We test through the actual classes with fixtures, not by patching _get_hashed_idempotency_key with a MagicMock.
  • ConcretePersistenceLayer is defined 5 times instead of once.
  • 208 lines of tests for a 2-line fix is too much surface area. A single test in the existing file that calls _item_to_data_record() with missing attributes and asserts response_data is None (not "None") would cover the Redis fix properly.

Suggestion

Let's scope this PR to fix all 4 items from the issue:

  1. Fix if response else None to if response is not None (item 1 - bug fix)
  2. Fix "INPROGRESS" string literal to use STATUS_CONSTANTS (item 2 - consistency)
  3. Fix str(None) in Redis (item 3 - already done, good)
  4. Drop the helper method extraction (item 4 - the current approach doesn't improve things)

Add tests in the existing test files following our current patterns. Also pls fix the issues in SonarCloud.

Thanks again for contributing!

@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Apr 28, 2026
@hirenkumar-n-dholariya
Copy link
Copy Markdown
Contributor Author

hirenkumar-n-dholariya commented Apr 28, 2026

Hey @hirenkumar-n-dholariya, thanks for working on this and submitting the PR!

The Redis str(None) fix is good. Removing the str() wrapper is the right fix.

However, I have some concerns with the rest of the PR, and there are missing items from the issue.

Missing items from the issue

In your comment on #8090 you mentioned that items 1 and 2 "appear already resolved", but they are still present in the codebase:

Item 1 - Falsy response handling is still in base.py:299:

serialized_response: dict = self.output_serializer.to_dict(response) if response else None

This silently discards valid falsy return values like 0, False, "", [], {}. It should be if response is not None. This is a real bug that affects customers.

Item 2 - "INPROGRESS" string literal is still in base.py:170:

if is_replay and record is not None and record.status == "INPROGRESS":

Should use STATUS_CONSTANTS["INPROGRESS"] for consistency with the rest of the code.

Helper method

The helper _get_idempotency_key_or_return_none() doesn't actually reduce the duplication. After the refactor, each caller still does:

idempotency_key = self._get_idempotency_key_or_return_none(data=data)
if idempotency_key is None:
    return None

We moved one line into a wrapper but the if None: return is still in all 4 methods. The helper is a passthrough that adds indirection without simplifying anything. Let's drop this part and keep the original inline pattern, it's 3 lines and every Python developer reads it instantly.

Tests

The tests need a full rework. Please don't create a new test file for this.

We already have test_item_to_datarecord_conversion in tests/functional/idempotency/_redis/test_redis_layer.py (line 318) that tests exactly _item_to_data_record(). Add your test case there, next to the existing one. That's our pattern: tests go in the existing test files, close to related tests.

What's wrong with the current tests:

  • Don't test Python builtins. test_str_none_produces_wrong_string asserts that str(None) == "None" and test_redis_missing_data_attr_returns_none_not_string asserts that dict.get() returns None. These are testing the Python language, not our code.
  • Don't mock internal methods with MagicMock. We don't do that in idempotency tests. Look at the existing tests in _redis/test_redis_layer.py and _boto3/test_idempotency.py for the patterns we use. We test through the actual classes with fixtures, not by patching _get_hashed_idempotency_key with a MagicMock.
  • ConcretePersistenceLayer is defined 5 times instead of once.
  • 208 lines of tests for a 2-line fix is too much surface area. A single test in the existing file that calls _item_to_data_record() with missing attributes and asserts response_data is None (not "None") would cover the Redis fix properly.

Suggestion

Let's scope this PR to fix all 4 items from the issue:

  1. Fix if response else None to if response is not None (item 1 - bug fix)
  2. Fix "INPROGRESS" string literal to use STATUS_CONSTANTS (item 2 - consistency)
  3. Fix str(None) in Redis (item 3 - already done, good)
  4. Drop the helper method extraction (item 4 - the current approach doesn't improve things)

Add tests in the existing test files following our current patterns. Also pls fix the issues in SonarCloud.

Thanks again for contributing!

Hey @leandrodamascena,
Thank you for the thorough and detailed review - really appreciate the clear guidance!

I have addressed all the feedback:

  1. Fixed falsy response handling - I changed if response else None to if response is not None else None in idempotency/base.py so valid falsy return values (0, False, {}, [], "") are correctly cached and returned on replay

  2. Fixed STATUS_CONSTANTS consistency - I replaced string literal "INPROGRESS" with STATUS_CONSTANTS["INPROGRESS"] in_process_idempotency()`

  3. Reverted helper method - restored the original inline 3-line null-check pattern across all 4 methods in persistence/base.py. You are right that the helper added indirection without real simplification.

  4. Tests reworked - removed the standalone test file and added a single focused test directly in _redis/test_redis_layer.py next to test_item_to_datarecord_conversion, following the existing fixture-based patterns

  5. Fixed PR description - added the Acknowledgment section as required by the template

Thanks again for taking the time to explain the patterns and reasoning behind each point. It really helps understand the project's conventions better. Please let me know if anything else needs adjusting.

…istence

Replace str(item.get(...)) with item.get(...) to avoid storing the
string "None" when a value is missing from Redis hash map.

When data_attr or validation_key_attr is missing from Redis,
item.get() returns None. Wrapping with str() converts it to the
string "None" which is incorrect. Now correctly returns None.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
…into helper method

Replace 4 identical null-check blocks across save_success, save_inprogress, delete_record, and get_record with a single helper method _get_idempotency_key_or_return_none() to reduce code duplication.

The helper encapsulates the pattern of calling _get_hashed_idempotency_key() and returning None early if the key is None, keeping each method cleaner and easier to read.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
…wertools#8090

Fix 1 - str(None) in Redis _item_to_data_record:
- Missing data_attr returns None not string "None"
- Existing data_attr returns value correctly
- Demonstrates old bug vs new correct behavior

Fix 2 - _get_idempotency_key_or_return_none helper:
- Returns None when key is None
- Returns key string when key exists
- Correctly used in save_success, save_inprogress,
  delete_record, and get_record (all return None early)

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
… constant

Fix 1 - Falsy response handling in _get_function_response(): Replace `if response else None` with `if response is not None else None`. So valid falsy return values (0, False, {}, [], "") are correctly   serialized and stored instead of being silently discarded.

Fix 2 - Inconsistent status constant in _process_idempotency():
Replace string literal "INPROGRESS" with STATUS_CONSTANTS["INPROGRESS"] for consistency with the rest of the codebase.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
… null-check pattern

Per Leandro Damascena's feedback: _get_idempotency_key_or_return_none() helper added
indirection without reducing duplication since the if None: return None check remained in all 4 callers anyway.

Restored original inline 3-line pattern in save_success, save_inprogress, delete_record, and get_record which is clearer and instantly readable.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
Tests should be added to existing test files following established patterns, not in new standalone files. The Redis fix will be tested in
_redis/test_redis_layer.py next to the existing test_item_to_datarecord_conversion.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
…data_record

Added single test next to existing test_item_to_datarecord_conversion to verify missing Redis attributes return None instead of string "None".

Follows existing test patterns using fixtures instead of MagicMock.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
@hirenkumar-n-dholariya hirenkumar-n-dholariya force-pushed the fix/idempotency-tech-debt-8090 branch from e78eeb2 to 01f565a Compare April 28, 2026 11:07
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Apr 28, 2026
… base.py

Remove trailing whitespace on blank line between is_missing_idempotency_key
and _get_hashed_payload methods to pass ruff format check.

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Apr 29, 2026
…st_redis_layer.py

- Fix fixture name from persistence_store_redis to persistence_store_standalone_redis
  to match existing fixture defined in the test file
- Remove trailing whitespace on blank line after test function to pass ruff format check

Part of aws-powertools#8090

Signed-off-by: hirenkumar-n-dholariya <hirenkumarnd@gmail.com>
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Apr 29, 2026
@hirenkumar-n-dholariya hirenkumar-n-dholariya changed the title fix(idempotency): fix str(None) in Redis persistence and extract duplicated null-check helper - tech debt 8090 fix(idempotency): fix falsy response handling, STATUS_CONSTANTS, and str(None) in Redis- tech debt 8090 Apr 29, 2026
@hirenkumar-n-dholariya
Copy link
Copy Markdown
Contributor Author

Hey @leandrodamascena, I've addressed all the feedback from your review:

  • Fixed falsy response handling
  • Fixed STATUS_CONSTANTS string literal
  • Fixed str(None) in Redis
  • Reverted helper method
  • Fixed tests and formatting

All SonarCloud checks are passing. Could you please approve the workflows and take another look when you get a chance? Thank you!

@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Apr 30, 2026
@sonarqubecloud
Copy link
Copy Markdown

@leandrodamascena leandrodamascena changed the title fix(idempotency): fix falsy response handling, STATUS_CONSTANTS, and str(None) in Redis- tech debt 8090 fix(idempotency): resolve tech debt issues with falsy responses and Redis persistency Apr 30, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.72%. Comparing base (0834363) to head (75e47d4).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #8176   +/-   ##
========================================
  Coverage    96.72%   96.72%           
========================================
  Files          286      286           
  Lines        14341    14341           
  Branches      1200     1200           
========================================
  Hits         13871    13871           
  Misses         341      341           
  Partials       129      129           

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Copy Markdown
Contributor

@leandrodamascena leandrodamascena left a comment

Choose a reason for hiding this comment

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

Thanks for working on this @hirenkumar-n-dholariya! Approved.

@leandrodamascena leandrodamascena merged commit 08c9921 into aws-powertools:develop Apr 30, 2026
17 checks passed
@hirenkumar-n-dholariya
Copy link
Copy Markdown
Contributor Author

Thank you @leandrodamascena; sincerely appreciate your suggestions, feedback and help to merge this!
I will continue exploring other issues and contribute on this project.

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

Labels

size/S Denotes a PR that changes 10-29 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tech debt: Fix falsy response handling, inconsistencies, and duplicated code in idempotency utility

2 participants