Skip to content

Conversation

@strawgate
Copy link
Owner

@strawgate strawgate commented Oct 26, 2025

Switch all input parameters from dict[str, Any] to Mapping[str, Any] while keeping return types as dict[str, Any]. This follows the Liskov Substitution Principle - accept more general types as inputs, return more specific types as outputs.

This change improves the API's flexibility by accepting any mapping-like object (dict, OrderedDict, custom mappings, etc.) as input while maintaining backward compatibility. All existing code continues to work since dict is a subclass of Mapping.

Changes

  • Updated AsyncKeyValueProtocol with Mapping for put() and put_many() parameters
  • Updated ManagedEntry.value field to accept Mapping[str, Any]
  • Updated BaseStore and all wrapper implementations
  • Updated adapters (RaiseOnMissingAdapter)
  • Added dict() conversions where needed (encryption, compression, limit_size wrappers)

Testing Required

Sync library needs regeneration:

make codegen

Fixes #94

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Features
    • Public APIs now accept generic mapping types (Mapping[str, Any]) — single and bulk put operations across stores and wrappers accept broader mapping inputs.
  • Chores
    • Internal handling now converts mappings to concrete dicts where needed (compression/encryption/size checks) and serializes values as dicts for consistent output.

Switch all input parameters from `dict[str, Any]` to `Mapping[str, Any]` while keeping return types as `dict[str, Any]`. This follows the Liskov Substitution Principle - accept more general types as inputs, return more specific types as outputs.

This change improves the API's flexibility by accepting any mapping-like object (dict, OrderedDict, custom mappings, etc.) as input while maintaining backward compatibility. All existing code continues to work since dict is a subclass of Mapping.

Changes:
- Updated AsyncKeyValueProtocol with Mapping for put() and put_many() parameters
- Updated ManagedEntry.value field to accept Mapping[str, Any]
- Updated BaseStore and all wrapper implementations
- Updated adapters (RaiseOnMissingAdapter)
- Added dict() conversions where needed (encryption, compression, limit_size wrappers)

Note: Sync library will need to be regenerated from the async version.

Fixes #94

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

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

coderabbitai bot commented Oct 26, 2025

📝 Walkthrough

Walkthrough

Public APIs and internal signatures across async/sync protocols, stores, adapters, wrappers, tests, and shared utilities were changed to accept Mapping[str, Any] (and Sequence[Mapping[str, Any]] for batch ops) instead of concrete dict[str, Any]; Mapping imports were added and a few places convert mappings to dict(...) for processing/serialization.

Changes

Cohort / File(s) Summary
Protocols & base stores
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py, key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py, key-value/key-value-aio/src/key_value/aio/stores/base.py, key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py
put / put_many signatures now accept Mapping[str, Any] / Sequence[Mapping[str, Any]]; added Mapping imports; BaseStore methods wrap/serialize mappings to dict where values are returned or size/ttl logic applied.
Adapters
key-value/key-value-aio/src/key_value/aio/adapters/raise_on_missing/adapter.py, key-value/key-value-sync/src/key_value/sync/code_gen/adapters/raise_on_missing/adapter.py
put / put_many signatures updated to accept Mapping[str, Any] / Sequence[Mapping[str, Any]]; added Mapping import.
Async & sync base wrappers
key-value/key-value-aio/src/key_value/aio/wrappers/base.py, key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/base.py
put / put_many signatures updated to accept Mapping[str, Any] / Sequence[Mapping[str, Any]]; added Mapping import; delegation unchanged.
Transformation wrappers (convert-to-dict internally)
.../aio/wrappers/compression/wrapper.py, .../sync/code_gen/wrappers/compression/wrapper.py, .../aio/wrappers/encryption/base.py, .../sync/code_gen/wrappers/encryption/base.py, .../aio/wrappers/limit_size/wrapper.py, .../sync/code_gen/wrappers/limit_size/wrapper.py
Signatures accept Mapping[...]; implementations call dict(value) (or dict(v)) before compression/encryption/size checks; added Mapping imports.
Delegation wrappers (pass-through)
.../aio/wrappers/logging/wrapper.py, .../sync/code_gen/wrappers/logging/wrapper.py, .../aio/wrappers/fallback/wrapper.py, .../sync/code_gen/wrappers/fallback/wrapper.py, .../aio/wrappers/passthrough_cache/wrapper.py, .../sync/code_gen/wrappers/passthrough_cache/wrapper.py, .../aio/wrappers/prefix_collections/wrapper.py, .../sync/code_gen/wrappers/prefix_collections/wrapper.py, .../aio/wrappers/prefix_keys/wrapper.py, .../sync/code_gen/wrappers/prefix_keys/wrapper.py
put / put_many signatures changed to Mapping / Sequence[Mapping]; added Mapping imports; no control-flow changes.
Control/meta wrappers
.../aio/wrappers/read_only/wrapper.py, .../sync/code_gen/wrappers/read_only/wrapper.py, .../aio/wrappers/retry/wrapper.py, .../sync/code_gen/wrappers/retry/wrapper.py, .../aio/wrappers/single_collection/wrapper.py, .../sync/code_gen/wrappers/single_collection/wrapper.py, .../aio/wrappers/statistics/wrapper.py, .../sync/code_gen/wrappers/statistics/wrapper.py, .../aio/wrappers/timeout/wrapper.py, .../aio/wrappers/ttl_clamp/wrapper.py, .../sync/code_gen/wrappers/ttl_clamp/wrapper.py
put / put_many signatures updated to accept Mapping types; added Mapping imports; behavior unchanged.
Shared utilities
key-value/key-value-shared/src/key_value/shared/utils/managed_entry.py
ManagedEntry.value annotation changed from dict[str, Any] to Mapping[str, Any]; to_json ensures concrete dict(...) when serializing only data; added Mapping import.
Tests
key-value/key-value-aio/tests/stores/wrappers/test_fallback.py, key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py
Updated test FailingStore.put signatures to accept Mapping[str, Any] instead of dict[str, Any].

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "refactor: switch input parameters from dict to Mapping" is concise, clear, and directly summarizes the primary change across the codebase. It uses proper commit message format with the "refactor:" prefix and accurately reflects the main objective of changing input parameter types from concrete dict to abstract Mapping. The title is specific enough that a teammate reviewing the git history would immediately understand the nature of the change without requiring detailed examination of the changeset.
Linked Issues Check ✅ Passed The code changes comprehensively satisfy the requirements of issue #94. The pull request successfully replaces concrete dict[str, Any] parameter types with abstract Mapping[str, Any] for input parameters across all public interfaces, including protocols, stores, adapters, and all wrapper implementations in both async and sync variants. Return types remain as concrete dict as required. Additionally, internal conversions using dict(value) are implemented where necessary (e.g., in compression and encryption wrappers) to ensure proper functionality. The changes maintain backward compatibility since dict implements the Mapping protocol, and test files are updated to reflect the new signatures. The ManagedEntry.value field is also correctly updated to use Mapping[str, Any].
Out of Scope Changes Check ✅ Passed All code changes in this pull request are directly scoped to implementing the type signature changes from dict to Mapping as outlined in issue #94. The modifications consistently focus on updating input parameter types across protocols, stores, adapters, wrappers, and test files; adding necessary Mapping imports; and implementing internal conversions where required for compatibility. No unrelated refactoring, bug fixes, feature additions, or behavioral logic changes are present. The changes are cohesive and narrowly focused on the stated objective of improving API flexibility through abstract mapping types.
✨ 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-94-20251026-0043

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55adfe3 and ad70497.

📒 Files selected for processing (7)
  • key-value/key-value-aio/src/key_value/aio/stores/base.py (7 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/logging/wrapper.py (5 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (2 hunks)
  • key-value/key-value-shared/src/key_value/shared/utils/managed_entry.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (7 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/logging/wrapper.py (5 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (2)
key-value/key-value-aio/src/key_value/aio/stores/base.py (2)
  • put (193-204)
  • ttl (148-157)
key-value/key-value-aio/src/key_value/aio/wrappers/logging/wrapper.py (2)
  • put (149-154)
  • ttl (126-133)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (2)
key-value/key-value-aio/src/key_value/aio/stores/base.py (6)
  • get_many (140-145)
  • keys (296-302)
  • setup_collection (89-99)
  • _get_managed_entries (105-108)
  • ttl (148-157)
  • put (193-204)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (4)
  • get_many (58-68)
  • keys (129-136)
  • ttl (24-35)
  • put (37-47)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (3)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (2)
  • put (182-189)
  • ttl (146-155)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (2)
  • put (37-47)
  • ttl (24-35)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/fallback/wrapper.py (2)
  • put (77-84)
  • ttl (63-67)
key-value/key-value-aio/src/key_value/aio/wrappers/logging/wrapper.py (4)
key-value/key-value-aio/src/key_value/aio/stores/base.py (2)
  • put (193-204)
  • ttl (148-157)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (2)
  • put (39-49)
  • ttl (26-37)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/logging/wrapper.py (2)
  • put (147-152)
  • ttl (124-131)
key-value/key-value-aio/src/key_value/aio/wrappers/base.py (2)
  • put (31-32)
  • ttl (23-24)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/logging/wrapper.py (4)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (2)
  • put (182-189)
  • ttl (146-155)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (2)
  • put (37-47)
  • ttl (24-35)
key-value/key-value-aio/src/key_value/aio/wrappers/logging/wrapper.py (2)
  • put (149-154)
  • ttl (126-133)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/base.py (2)
  • put (34-35)
  • ttl (26-27)
key-value/key-value-aio/src/key_value/aio/stores/base.py (5)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (6)
  • get_many (138-143)
  • keys (281-287)
  • setup_collection (92-102)
  • _get_managed_entries (108-111)
  • ttl (146-155)
  • put (182-189)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (4)
  • get_many (60-70)
  • keys (131-138)
  • ttl (26-37)
  • put (39-49)
key-value/key-value-aio/src/key_value/aio/wrappers/compression/wrapper.py (3)
  • get_many (119-121)
  • ttl (124-126)
  • put (134-136)
key-value/key-value-shared/src/key_value/shared/utils/managed_entry.py (1)
  • is_expired (38-41)
key-value/key-value-aio/src/key_value/aio/wrappers/limit_size/wrapper.py (1)
  • put (83-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: test_all (3.13, windows-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.13, windows-2022, key-value/key-value-sync)
  • GitHub Check: test_all (3.13, windows-latest, key-value/key-value-sync)
  • GitHub Check: test_all (3.13, windows-2022, key-value/key-value-aio)
  • GitHub Check: test_all (3.12, windows-latest, key-value/key-value-sync)
  • GitHub Check: test_all (3.13, ubuntu-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.12, windows-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.13, macos-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.12, windows-2022, key-value/key-value-aio)
  • GitHub Check: test_all (3.13, ubuntu-22.04, key-value/key-value-aio)
  • GitHub Check: test_all (3.12, windows-2022, key-value/key-value-sync)
  • GitHub Check: test_all (3.12, ubuntu-22.04, key-value/key-value-aio)
  • GitHub Check: test_all (3.12, macos-14, key-value/key-value-sync)
  • GitHub Check: test_all (3.11, windows-2022, key-value/key-value-aio)
  • GitHub Check: test_all (3.12, ubuntu-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.11, windows-2022, key-value/key-value-sync)
  • GitHub Check: test_all (3.11, macos-latest, key-value/key-value-sync)
  • GitHub Check: test_all (3.11, windows-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.11, macos-latest, key-value/key-value-aio)
  • GitHub Check: test_all (3.11, windows-latest, key-value/key-value-sync)
  • GitHub Check: test_all (3.11, ubuntu-latest, key-value/key-value-sync)
  • GitHub Check: test_all (3.11, ubuntu-22.04, key-value/key-value-aio)
  • GitHub Check: test_all (3.11, ubuntu-latest, key-value/key-value-aio)
🔇 Additional comments (26)
key-value/key-value-shared/src/key_value/shared/utils/managed_entry.py (2)

2-2: LGTM: Import added correctly.

The Mapping import from collections.abc is appropriate and necessary for the field type change.


22-22: LGTM: Field type generalized correctly.

Changing the value field from dict[str, Any] to Mapping[str, Any] follows the Liskov Substitution Principle and maintains backward compatibility while accepting more general mapping-like inputs.

key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (2)

1-1: LGTM: Mapping import added correctly.

The import of Mapping from collections.abc is necessary to support the updated type signatures that accept any mapping-like input.


19-19: Signature update aligns with PR objectives.

The put method signature now accepts Mapping[str, Any] instead of dict[str, Any], which correctly follows the Liskov Substitution Principle by accepting more general mapping-like inputs while maintaining backward compatibility.

key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (2)

4-4: LGTM: Import correctly synchronized with async version.

The Mapping import is properly added in the generated sync version, maintaining consistency with the async source file.


22-22: Signature correctly mirrors async version.

The generated code properly reflects the Mapping[str, Any] signature change from the async source.

key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (8)

10-10: LGTM: Required imports added.

The addition of Mapping and Sequence from collections.abc is necessary to support the new type signatures that accept more flexible input types.


135-135: Correct: dict() wrapper ensures concrete return type.

The dict() conversion ensures the return type is always dict[str, Any] even though managed_entry.value is now typed as Mapping[str, Any]. This creates a shallow copy which provides encapsulation and prevents external mutation of stored values.


143-143: Correct: dict() wrappers in list comprehension.

The dict() conversions ensure all returned values are concrete dict[str, Any] instances, maintaining the promised return type of list[dict[str, Any] | None].


155-155: Correct: dict() wrapper in tuple return.

The dict() conversion ensures the first element of the returned tuple is dict[str, Any], maintaining type consistency with the method signature.


168-168: Correct: dict() wrappers maintain return type contract.

The dict() conversions in the list comprehension ensure the return type matches list[tuple[dict[str, Any] | None, float | None]] as declared.


182-182: Signature correctly broadened to accept Mapping.

The put method now accepts Mapping[str, Any] for the value parameter, following the Liskov Substitution Principle by accepting any mapping-like object (dict, OrderedDict, ChainMap, etc.) while maintaining backward compatibility.


191-193: Type signatures correctly updated for batch operations.

The _prepare_put_many method signatures now accept and return Sequence[Mapping[str, Any]], maintaining consistency with the single-value put operation while supporting bulk operations.


214-214: put_many correctly accepts Sequence[Mapping[str, Any]].

The signature change allows callers to pass sequences of any mapping-like objects, increasing flexibility while maintaining type safety.

key-value/key-value-aio/src/key_value/aio/stores/base.py (5)

8-8: LGTM: Required imports added for async store.

The Mapping and Sequence imports enable the type signature changes that broaden input acceptance while maintaining return type specificity.


137-137: Correct: dict() wrappers ensure concrete dict returns throughout.

All return paths correctly wrap managed_entry.value with dict() to ensure the return type is always dict[str, Any] rather than the more general Mapping[str, Any]. This maintains the API contract that callers receive mutable, concrete dictionaries.

Also applies to: 145-145, 157-157, 175-175


193-193: Async put signature correctly broadened.

The async put method now accepts Mapping[str, Any], matching the sync version and allowing callers to pass any mapping-like object.


207-208: _prepare_put_many signatures correctly updated.

The method now accepts and returns Sequence[Mapping[str, Any]], maintaining consistency with the single-value operations while supporting batch operations on any sequence of mapping-like objects.


229-229: put_many correctly accepts flexible sequence of mappings.

The signature change enables callers to pass sequences of any mapping types while maintaining type safety and backward compatibility.

key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/logging/wrapper.py (3)

6-6: LGTM: Imports added for logging wrapper.

The Mapping and Sequence imports are correctly added to support the updated type signatures.


65-65: Internal logging methods correctly handle Mapping types.

The _format_message and _log methods now accept Mapping[str, Any] | Sequence[Mapping[str, Any]] | None for values, ensuring they can log any mapping-like objects passed to the public API methods.

Also applies to: 93-93


147-147: Public API signatures correctly updated.

The put and put_many methods now accept Mapping[str, Any] and Sequence[Mapping[str, Any]] respectively, maintaining consistency with the underlying store's updated signatures.

Also applies to: 158-158

key-value/key-value-aio/src/key_value/aio/wrappers/logging/wrapper.py (4)

3-3: LGTM: Required imports added for async logging wrapper.

The Mapping and Sequence imports support the updated type signatures in the async version of the logging wrapper.


62-62: Internal methods correctly accept Mapping types.

The _format_message and _log helper methods are properly updated to handle Mapping[str, Any] | Sequence[Mapping[str, Any]] | None, aligning with the public API changes.

Also applies to: 95-95


65-78: Same json.dumps consideration as sync version.

The structured logging path uses json.dumps(structured_data) where values may be non-dict Mapping types. This has the same serialization considerations as noted in the sync version (key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/logging/wrapper.py lines 68-76).

Since this mirrors the sync implementation, the same verification applies regarding custom Mapping types and JSON serialization compatibility.


149-149: Async methods correctly accept Mapping inputs.

The put and put_many async methods now accept Mapping[str, Any] and Sequence[Mapping[str, Any]], maintaining consistency with the underlying async store.

Also applies to: 160-160


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

@strawgate
Copy link
Owner Author

@claude you lint and codegen please

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


Running lint and codegen

Tasks

  • Read repository setup instructions
  • Get project context
  • Attempted to run linting - permission issue
  • Attempted to run code generation - permission issue

Summary

I attempted to run the requested linting and code generation tasks, but I don't have permission to execute the necessary commands:

  • make lint (or uv run ruff format and uv run ruff check --fix)
  • make codegen (or uv run python scripts/build_sync_library.py)

What you can do

You have a few options:

  1. Grant me permission to run these commands by updating my --allowedTools configuration to include the Bash commands I need:

    • uv run ruff format
    • uv run ruff check --fix
    • markdownlint --fix -c .markdownlint.jsonc .
    • uv run python scripts/build_sync_library.py
  2. Run the commands locally yourself:

    make lint
    make codegen

    Then commit and push the changes.

  3. Use the Makefile's precommit target which runs all three operations:

    make precommit

According to the Makefile, the codegen target runs uv run python scripts/build_sync_library.py, which regenerates the sync library from the async code. The lint target formats and checks Python code with ruff, and lints markdown files with markdownlint.

--- |

@strawgate
Copy link
Owner Author

@claude you have access now try again

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


Running lint and codegen

Tasks

  • Read repository setup instructions
  • Check current git status
  • Run linting (ruff format, ruff check --fix, markdownlint) - All passed ✓
  • Run code generation - Completed successfully ✓
  • Commit and push changes

Summary

Successfully ran linting and code generation:

Linting: All checks passed with no changes needed

  • ruff format - 291 files left unchanged
  • ruff check --fix - All checks passed
  • markdownlint - No issues found

Code Generation: Successfully regenerated sync library

  • Installed Python 3.10 via uv (required for codegen script)
  • Generated 17 updated sync library files reflecting the dict[str, Any]Mapping[str, Any] refactor
  • Committed changes: 55adfe3

The sync library is now up to date with the async library changes from this PR.


View job

Regenerated the sync library from async sources to reflect the changes
from PR #103 which switched input parameters from dict to Mapping.

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

@claude looks like type checking is failing on the air project maybe you didn't change the mock stores in the tests? Please review

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


Type checking issues resolved

Summary

Fixed all type checking failures related to the Mapping refactor. The issues were caused by missing type conversions between Mapping[str, Any] (input type) and dict[str, Any] (return type).

Issues Found and Fixed

  1. BaseStore return types (key_value/aio/stores/base.py:137): Methods get(), get_many(), ttl(), and ttl_many() were returning Mapping[str, Any] from managed_entry.value but should return dict[str, Any]. Fixed by wrapping in dict() constructor.

  2. LoggingWrapper internal methods (key_value/aio/wrappers/logging/wrapper.py:62): The _log() and _format_message() methods had signatures expecting dict[str, Any] but receive Mapping[str, Any] from the refactored public API. Updated signatures to accept Mapping[str, Any] | Sequence[Mapping[str, Any]] | None.

  3. Test mock store (tests/stores/wrappers/test_fallback.py:19): Mock FailingStore.put() used dict[str, Any] instead of Mapping[str, Any], causing protocol incompatibility. Updated to match the protocol signature.

  4. ManagedEntry.to_json() (key_value/shared/utils/managed_entry.py:57): When include_metadata=False, assigned self.value (now Mapping[str, Any]) to a variable declared as dict[str, Any]. Fixed by wrapping in dict() constructor.

Verification

  • ✅ All type checking passes locally (aio, sync, shared packages)
  • ✅ Sync library regenerated with all fixes
  • ✅ Changes committed and pushed: ad70497

The CI should now pass. The key insight is that while we accept Mapping[str, Any] as input (following Liskov Substitution Principle), we need to ensure we return dict[str, Any] as specified in the protocol, requiring explicit conversions when returning values from ManagedEntry.


View job

- Fix BaseStore return types by converting Mapping to dict in get/ttl methods
- Update LoggingWrapper internal methods to accept Mapping types
- Fix test mock store to use Mapping instead of dict for protocol compatibility
- Fix ManagedEntry.to_json() to convert Mapping to dict
- Regenerate sync library with all fixes

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

Co-Authored-By: William Easton <strawgate@users.noreply.github.com>
@strawgate
Copy link
Owner Author

@coderabbitai please review again

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

@strawgate, I'll review the changes in this PR for you now.

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

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@strawgate strawgate requested a review from Copilot October 26, 2025 03:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the key-value library to accept Mapping[str, Any] instead of dict[str, Any] for input parameters while maintaining dict[str, Any] for return types. This follows the Liskov Substitution Principle, making the API more flexible by accepting any mapping-like objects (dict, OrderedDict, custom mappings) while preserving backward compatibility.

Key changes:

  • Updated all put() and put_many() method signatures across protocols, stores, wrappers, and adapters to accept Mapping[str, Any]
  • Added dict() conversions in encryption, compression, and limit_size wrappers where concrete dict objects are required
  • Modified ManagedEntry.value field and return value serialization to ensure dict output

Reviewed Changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
key_value/aio/protocols/key_value.py Updated protocol signatures for put operations to accept Mapping
key_value/aio/stores/base.py Modified base store to accept Mapping inputs and return dict outputs
key_value/aio/wrappers/*.py Updated all async wrapper implementations to accept Mapping parameters
key_value/aio/adapters/raise_on_missing/adapter.py Updated adapter to accept Mapping in put operations
key_value/sync/code_gen/**/*.py Synchronized changes to generated sync library
key_value/shared/utils/managed_entry.py Changed ManagedEntry.value field type and ensured dict serialization

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

) -> None:
filtered_keys: list[str] = []
filtered_values: list[dict[str, Any]] = []
filtered_values: list[Mapping[str, Any]] = []
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

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

The type annotation should remain list[dict[str, Any]] since the values are passed through unchanged to the underlying store. The Mapping type is only needed for the input parameter, not for internal storage.

Copilot uses AI. Check for mistakes.
) -> None:
filtered_keys: list[str] = []
filtered_values: list[dict[str, Any]] = []
filtered_values: list[Mapping[str, Any]] = []
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

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

The type annotation should remain list[dict[str, Any]] since the values are passed through unchanged to the underlying store. The Mapping type is only needed for the input parameter, not for internal storage.

Copilot uses AI. Check for mistakes.
@strawgate
Copy link
Owner Author

@claude please thoroughly review this pull request, are there any other fields in the interface we should change to a more generic type like the list of keys? What do you think

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


Comprehensive PR Review: Mapping Refactor

Overall Assessment: ✅ Excellent Work

This PR successfully implements the Liskov Substitution Principle by switching input parameters from dict[str, Any] to Mapping[str, Any] while keeping return types as dict[str, Any]. The implementation is thorough, consistent, and type-safe.

Type Checking: ✅ Passes (basedpyright shows 0 errors, 0 warnings)
Implementation Coverage: ✅ All 38 files updated correctly
Breaking Changes: ✅ None (backward compatible)


Key Findings

✅ What's Done Well

  1. Consistent application across the codebase: All protocols, stores, wrappers, and adapters updated systematically
  2. Proper dict() conversions: Correctly added conversions in encryption, compression, and limit_size wrappers where mutable dicts are needed
  3. ManagedEntry updates: Both the field type and serialization logic properly handle Mapping → dict conversion
  4. Type safety maintained: All changes pass strict type checking

🤔 Copilot's Feedback (Not Valid)

Copilot suggested that filtered_values: list[Mapping[str, Any]] should be list[dict[str, Any]] in limit_size/wrapper.py:97,100. This is incorrect. The type is correct because:

  • Input values are Sequence[Mapping[str, Any]]
  • We're filtering (not transforming) them
  • They're passed unchanged to the underlying store which accepts Sequence[Mapping[str, Any]]
  • The dict() conversion only happens in _within_size_limit() for size checking

Opportunities for Additional Generic Types

You asked: "Are there any other fields in the interface we should change to a more generic type like the list of keys?"

Recommendation: Yes, but with nuance

I identified two categories of methods that use list[str]:

1️⃣ Input Parameters (Keys) - Should Consider Changing

Methods that accept lists of keys as input:

  • get_many(keys: list[str]) - Currently line 60 in protocols
  • ttl_many(keys: list[str]) - Line 72
  • put_many(keys: list[str]) - Line 87
  • delete_many(keys: list[str]) - Line 104

Recommendation: Change to Sequence[str] for consistency with the Mapping pattern.

Rationale:

  • Follows same LSP principle: accept general, return specific
  • Users could pass tuples, frozen sets (as sequences), or other sequence types
  • Already using Sequence for values parameter: put_many(..., values: Sequence[Mapping[str, Any]])
  • Improves API flexibility without breaking changes

Example:

# Current
async def get_many(self, keys: list[str], ...) -> list[dict[str, Any] | None]:

# Proposed
async def get_many(self, keys: Sequence[str], ...) -> list[dict[str, Any] | None]:

2️⃣ Return Types - Should NOT Change

Methods that return lists of keys/collections:

  • keys(...) -> list[str] - Line 131
  • collections(...) -> list[str] - Line 143

Recommendation: Keep as list[str]

Rationale:

  • Return types should be concrete (LSP principle)
  • Callers expect mutable lists they can modify
  • Changing to Sequence[str] would force callers to convert back to list for common operations
  • The "get many values" methods already return concrete list[dict[str, Any] | None] for the same reason

Detailed Analysis: Should Keys Accept Sequence?

Pros ✅

  1. Consistency: Matches the existing pattern for values: Sequence[Mapping[str, Any]]
  2. Flexibility: Users can pass:
    • ['key1', 'key2'] (list - current behavior)
    • ('key1', 'key2') (tuple - immutable)
    • Custom sequence types
    • Result of some_dict.keys() might work more naturally
  3. No breaking changes: list is a Sequence, so all existing code continues to work
  4. Type safety: Communicates that the method doesn't mutate the input keys
  5. Python best practices: Accepting abstract types for inputs is standard

Cons / Considerations ⚠️

  1. Internal list conversions: Some wrappers create new lists from keys:

    new_keys: list[str] = [self._prefix_key(key=key) for key in keys]

    This works fine with Sequence input (no changes needed)

  2. Backend compatibility: Need to verify stores don't require lists:

    • Redis: Uses iteration (✅ works with Sequence)
    • DynamoDB: Likely iterates (✅ works with Sequence)
    • Elasticsearch: Need to verify
    • Most stores iterate over keys, so should be fine
  3. Scope: Would affect ~90+ method signatures across the codebase

Implementation Effort

If you decide to change keys to Sequence[str]:

Files to update (~same as this PR):

  • Protocols (2 files)
  • Base store (2 files)
  • All wrappers (34 files)
  • All adapters (4 files)
  • Tests (update mock signatures)

Internal methods that might need review:

  • _get_managed_entries(keys: list[str]) in BaseStore - could become Sequence[str]
  • _put_managed_entries(keys: list[str]) - could become Sequence[str]
  • Various wrapper internal methods

Type checking needed: Same process as this PR


My Opinion

Should you do it? Yes, I think so

The change from list[str]Sequence[str] for key input parameters:

  1. Follows the same principle you just successfully applied to values
  2. Improves API consistency: both keys and values use abstract input types
  3. Provides real user benefits: accepting tuples, generator sequences, etc.
  4. Low risk: Same backward-compatible approach
  5. Clean codebase: You're already in the refactoring mindset

Suggested Approach

Option 1: Separate PR (Recommended)

  • Keep this PR focused on Mapping
  • Create follow-up PR for Sequence[str] keys
  • Allows incremental review and testing
  • Easier to revert if issues found

Option 2: Expand this PR

  • Add the changes to this branch before merging
  • Single coherent "generic types" refactor
  • Might be easier to document as one change

Option 3: Don't do it

  • list[str] is more specific and clear
  • Users rarely need to pass non-list sequences of strings
  • Less API surface area to maintain

I lean toward Option 1 - do it, but in a separate PR. The current PR is already well-scoped and tested.


Other Observations

✅ Good Patterns Maintained

  1. Internal type conversions: You correctly add dict() only where needed:

    • encryption/base.py: dict(value) before encryption
    • compression/wrapper.py: dict(value) before compression
    • limit_size/wrapper.py: dict(v) for size checking
  2. Sync library architecture: Properly maintained auto-generation pattern

  3. Test updates: Mock stores updated to match new signatures

📝 Minor Notes

  1. SonarQube duplication warning: 7% duplication (limit is 3%)

    • This is expected in a wrapper-heavy architecture
    • Each wrapper has similar boilerplate
    • Not a blocker for this PR
  2. Docstring coverage: 22.22% vs 80% required

    • Pre-existing issue, not introduced by this PR
    • Could be addressed separately

Final Recommendation

For This PR

Approve and merge - the Mapping refactor is complete and correct

For Future Work

Consider a follow-up PR to change key input parameters from list[str] to Sequence[str] for:

  • get_many()
  • ttl_many()
  • put_many()
  • delete_many()

This would complete the "accept generic, return concrete" pattern across the entire API surface.


@strawgate strawgate merged commit 1c8bf6f into main Oct 26, 2025
94 of 101 checks passed
@strawgate strawgate deleted the claude/issue-94-20251026-0043 branch October 26, 2025 04:11
claude bot pushed a commit that referenced this pull request Oct 26, 2025
Switch all key input parameters in bulk operations from list[str] to
Sequence[str] while keeping return types as list[str]. This follows the
Liskov Substitution Principle - accept more general types as inputs,
return more specific types as outputs.

This change completes the pattern started in #103 (Mapping for values)
and improves the API's flexibility by accepting any sequence-like object
(list, tuple, etc.) as input while maintaining backward compatibility.
All existing code continues to work since list is a subclass of Sequence.

Changes:
- Updated AsyncKeyValueProtocol with Sequence for bulk operation keys
- Updated BaseStore and all wrapper implementations
- Updated adapters
- Updated shared utility functions
- Regenerated sync library

Fixes #116

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

Co-Authored-By: Claude <noreply@anthropic.com>
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Switch interface to mapping from dict

2 participants