Skip to content

Conversation

@strawgate
Copy link
Owner

This PR implements the change discussed in #95 to require a single TTL for all items in put_many() operations.

Summary

This breaking change simplifies the put_many API by requiring a single TTL value for all items in a batch operation, instead of supporting per-item TTLs. This aligns with industry standards (Django, Flask, redis-py) and addresses backend store limitations.

Changes

  • Updated protocol signature for put_many()
  • Simplified TTL preparation utilities
  • Removed IncorrectTTLCountError exception
  • Updated all store implementations, wrappers, and adapters
  • Updated tests and documentation
  • Incorporated recent type improvements from main (Sequence, Mapping)

Breaking Change

Users who were passing a sequence of TTLs to put_many() will need to:

  1. Make multiple put_many() calls grouped by TTL value, or
  2. Use individual put() calls

This follows the same pattern used in Django and other major frameworks.

Post-Merge Action Required

After merging, run make codegen to regenerate the sync library.

Fixes #95

🤖 Generated with Claude Code) | View job run | [Branch](https://github.com/strawgate/py-key-value/tree/claude/issue-95-20251026-0453

github-actions bot and others added 2 commits October 26, 2025 04:56
This breaking change simplifies the put_many API by requiring a single
TTL value for all items in a batch operation, instead of supporting
per-item TTLs.

Changes:
- Updated AsyncKeyValueProtocol.put_many() to accept single TTL
- Simplified BaseStore._prepare_put_many() to handle single TTL
- Removed prepare_ttls() utility function (no longer needed)
- Removed IncorrectTTLCountError exception class
- Updated all wrappers and adapters to use single TTL
- Updated tests to reflect new behavior
- Updated README.md with new signature

Rationale:
- Industry standard: Django, Flask, redis-py all use single TTL
- Simplifies API and reduces complexity
- Most backend stores don't support per-item TTLs in batch operations
- Pre-GA library, so breaking changes are acceptable

For users needing different TTLs:
- Make multiple put_many() calls grouped by TTL
- Use individual put() calls

Fixes #95

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: William Easton <strawgate@users.noreply.github.com>
- Fixed compression and encryption wrappers
- Fixed fallback, logging, passthrough_cache, prefix_collections, prefix_keys, read_only, retry, single_collection, statistics, and timeout wrappers
- Updated passthrough_cache to use individual put() calls for items with different TTLs
- Updated test to reflect single TTL approach
- All async type checking now passes

Note: Sync library needs regeneration with make codegen

Co-authored-by: William Easton <strawgate@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Warning

Rate limit exceeded

@strawgate has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2ca7e33 and 9112714.

📒 Files selected for processing (43)
  • README.md (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/adapters/raise_on_missing/adapter.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/stores/base.py (5 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/base.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/compression/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/encryption/base.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/fallback/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/limit_size/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/logging/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/passthrough_cache/wrapper.py (3 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/prefix_collections/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/prefix_keys/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/read_only/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/retry/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/single_collection/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/statistics/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/timeout/wrapper.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/ttl_clamp/wrapper.py (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_limit_size.py (1 hunks)
  • key-value/key-value-shared/src/key_value/shared/errors/key_value.py (0 hunks)
  • key-value/key-value-shared/src/key_value/shared/utils/time_to_live.py (0 hunks)
  • key-value/key-value-shared/tests/utils/test_time_to_live.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/raise_on_missing/adapter.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (10 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/base.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/compression/wrapper.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/encryption/base.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/fallback/wrapper.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/limit_size/wrapper.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/logging/wrapper.py (6 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/passthrough_cache/wrapper.py (6 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/prefix_collections/wrapper.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/prefix_keys/wrapper.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/read_only/wrapper.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/retry/wrapper.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/single_collection/wrapper.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/statistics/wrapper.py (4 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/ttl_clamp/wrapper.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_limit_size.py (1 hunks)
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/issue-95-20251026-0453

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
5.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@strawgate
Copy link
Owner Author

@coderabbitai please review!

@strawgate
Copy link
Owner Author

@claude please do a very thorough line by line review for issues

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

@strawgate: I'll review the changes in this PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


Thorough Line-by-Line Review

I've completed a comprehensive review of all 43 changed files. Here are my findings:


✅ Overall Assessment

This is a well-executed breaking change that successfully simplifies the put_many API. The implementation is consistent across all layers of the codebase, and I found no critical bugs. However, there are a few minor issues and suggestions for improvement.


🔍 Detailed Findings

1. ✅ Protocol & API Changes (EXCELLENT)

Files reviewed:

  • key-value/key-value-aio/src/key_value/aio/protocols/key_value.py:85-103
  • key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py:81-95

Verdict: Clean signature change with clear documentation. The docstring correctly explains that the same TTL applies to all items in the batch.


2. ✅ Base Store Implementation (EXCELLENT)

Files reviewed:

  • key-value/key-value-aio/src/key_value/aio/stores/base.py:206-239
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py:189-218

Verdict: The refactoring is clean:

  • Correctly removed the TTL count validation logic
  • Simplified _prepare_put_many() return type from list[float | None] to float | None
  • List comprehension in line 237 (async) properly applies the same TTL to all entries
  • No issues found

3. ✅ Shared Utilities (EXCELLENT)

Files reviewed:

  • key-value/key-value-shared/src/key_value/shared/utils/time_to_live.py (removed prepare_ttls)
  • key-value/key-value-shared/src/key_value/shared/errors/key_value.py (removed IncorrectTTLCountError)
  • key-value/key-value-shared/tests/utils/test_time_to_live.py (updated tests)

Verdict: Clean removal of unused code:

  • prepare_ttls() function completely removed (33 lines)
  • IncorrectTTLCountError exception removed (10 lines)
  • Error was never exported from __init__.py, so no breaking change for external consumers
  • Tests updated to only test prepare_ttl() (singular)
  • Verified with grep: no remaining references to either removed item

4. ✅ Wrappers - Mostly Excellent

Files reviewed (19 wrapper files):

All wrappers correctly updated to use single TTL. Special attention to:

a) PassthroughCacheWrapper ✅ (Smart refactoring)

  • key-value/key-value-aio/src/key_value/aio/wrappers/passthrough_cache/wrapper.py:77-84
  • key-value/key-value-aio/src/key_value/aio/wrappers/passthrough_cache/wrapper.py:122-127

Excellent: The wrapper now uses individual put() calls when caching items retrieved from the primary store, since those items may have different TTLs. This is the correct approach given that entries from the primary store can have varying TTLs.

b) LimitSizeWrapper

  • key-value/key-value-aio/src/key_value/aio/wrappers/limit_size/wrapper.py:88-104

Excellent: Correctly simplified to filter keys/values without tracking per-item TTLs. The single TTL is passed through unchanged.

c) TTLClampWrapper

  • key-value/key-value-aio/src/key_value/aio/wrappers/ttl_clamp/wrapper.py:50-59

Excellent: Simplified from clamping each TTL in a sequence to clamping a single TTL. Clean refactoring.


5. ✅ Adapters (EXCELLENT)

Files reviewed:

  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py:112-116
  • key-value/key-value-aio/src/key_value/aio/adapters/raise_on_missing/adapter.py:158-173

Verdict: Both adapters updated correctly with proper docstring updates explaining the single TTL behavior.


6. ✅ Tests (GOOD)

Files reviewed:

  • key-value/key-value-aio/tests/stores/wrappers/test_limit_size.py:107-121
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_limit_size.py:107-121

Verdict: Test correctly updated from per-item TTLs to single TTL. Test name and comments updated appropriately.


7. ✅ Sync Library Codegen Consistency (EXCELLENT)

Spot-checked files:

  • All sync files have the auto-gen warning comment
  • Sync implementations correctly mirror async implementations
  • Type signatures match (e.g., Sequence[str] for keys in both)

Note: The PR description mentions running make codegen after merge, which is correct since sync is generated from async.


8. ✅ Documentation (EXCELLENT)

Files reviewed:

  • README.md:167 - Updated signature in documentation

Verdict: README correctly shows the new signature.


📊 SonarQube Duplication Analysis

SonarQube flagged 5.6% duplication (threshold is 3%). Let me analyze:

Likely causes:

  1. Sync library duplication - The sync library is auto-generated from async, so similar code patterns appear in both. This is expected and acceptable.
  2. Wrapper boilerplate - Multiple wrappers have similar put_many() implementations that just pass through to the wrapped store. This is also expected.

Recommendation: This duplication is acceptable because:

  • Sync library is generated code (documented as such)
  • Wrapper pattern naturally creates similar-looking code
  • The alternative (complex abstractions to reduce duplication) would harm readability

You could suppress this SonarQube warning for generated files by updating .sonarcloud.properties or similar config.


🎯 Specific Issues Found

NONE - No critical or major issues found

Minor observations:

  1. All type signatures are consistent
  2. All docstrings updated appropriately
  3. No logic errors detected
  4. Edge cases (empty lists, None TTLs) handled correctly
  5. Error handling preserved (keys/values length mismatch still checked)

✨ Code Quality Highlights

What's done exceptionally well:

  1. Consistent refactoring - All 43 files updated in a uniform way
  2. Smart design decisions - PassthroughCacheWrapper uses individual puts for caching (correct given varying TTLs from primary)
  3. Clean removals - Removed prepare_ttls() and IncorrectTTLCountError without leaving orphaned references
  4. Type safety - Return types properly simplified (e.g., list[float | None]float | None)
  5. Documentation - Docstrings explain the "same TTL for all items" clearly
  6. Breaking change communication - PR description clearly explains migration path

📝 Recommendations

Minor Suggestions (Optional):

  1. Consider adding a migration guide example in the PR description or CHANGELOG showing before/after for users with per-item TTLs:

    # Before (multiple TTLs):
    await store.put_many(["k1", "k2"], [v1, v2], ttl=[60, 120])
    
    # After - Option 1 (group by TTL):
    await store.put_many(["k1"], [v1], ttl=60)
    await store.put_many(["k2"], [v2], ttl=120)
    
    # After - Option 2 (individual puts):
    await store.put("k1", v1, ttl=60)
    await store.put("k2", v2, ttl=120)
  2. SonarQube config: Consider excluding generated sync files from duplication analysis by adding to .sonarcloud.properties:

    sonar.cpd.exclusions=**/sync/code_gen/**/*.py

✅ Final Verdict

This PR is production-ready.

  • ✅ No bugs found
  • ✅ Consistent implementation across all layers
  • ✅ Proper test coverage
  • ✅ Clear breaking change documentation
  • ✅ Aligns with industry standards (Django, Flask, redis-py)

The SonarQube duplication warning is a false positive due to generated code and expected wrapper patterns - not a real quality issue.

Recommendation: APPROVE and MERGE (after running make codegen post-merge as noted in PR description)


@strawgate strawgate merged commit e689f79 into main Oct 26, 2025
23 of 25 checks passed
@strawgate strawgate deleted the claude/issue-95-20251026-0453 branch October 26, 2025 05:27
claude bot pushed a commit that referenced this pull request Oct 26, 2025
Update RoutingWrapper to use correct type signatures:
- Change keys parameters from list[str] to Sequence[str] (per #119)
- Change put_many ttl from Sequence to single SupportsFloat (per #121)
- Update test cases to match new put_many signature

All 11 routing wrapper tests pass successfully.

Co-authored-by: William Easton <strawgate@users.noreply.github.com>
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.

Consider requiring the same TTL for all items in a put

2 participants