Skip to content

Conversation

@strawgate
Copy link
Owner

@strawgate strawgate commented Oct 26, 2025

  • New Features

    • Added routing wrappers to route key-value operations to different backing stores (sync + async).
  • Bug Fixes

    • Prevented errors during Valkey store shutdown by guarding close when no client is connected.
  • Documentation

    • README updated with descriptions and examples for the new routing wrappers; AGENTS.md guidance clarified.
  • Tests

    • Added routing tests covering get/get_many, delete, and TTL across routed and default stores.
  • Chores

    • Improved adapter typing/behavior, cleaned up lint/type directives, updated devcontainer and sync tooling.

Add routing wrapper functionality that allows routing requests to different
stores based on a routing function. This enables flexible multi-backend
storage strategies.

Features:
- RoutingWrapper: Base wrapper with custom routing function support
- CollectionRoutingWrapper: Convenience wrapper for collection-based routing
- Comprehensive test coverage with 11 tests covering all operations
- Support for all AsyncKeyValue protocol methods (get, put, delete, ttl, and _many variants)

Use cases:
- Route different collections to different backends (e.g., sessions to Redis, users to DynamoDB)
- Cost optimization by routing hot/cold data to different storage tiers
- Compliance requirements (e.g., PII data to encrypted stores)
- Development/testing with selective mock stores

Closes #114

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

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

coderabbitai bot commented Oct 26, 2025

📝 Walkthrough

Walkthrough

Adds async and sync routing wrappers: RoutingWrapper (routes via a RoutingFunction with default fallback) and CollectionRoutingWrapper (maps collection names to stores). Exposes these in package init files, adds tests for routing behavior, and updates various lint/type-check annotations and minor store safeguards across the codebase.

Changes

Cohort / File(s) Change Summary
Async Routing Core
key-value/key-value-aio/src/key_value/aio/wrappers/routing/__init__.py, key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py, key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py
Add RoutingFunction alias and RoutingWrapper class (async) that resolves a target AsyncKeyValue via a routing function with a default_store fallback; add CollectionRoutingWrapper that routes by a collection->store map; export public names in package __init__.
Sync Routing Core (codegen + public)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/..., key-value/key-value-sync/src/key_value/sync/wrappers/routing/__init__.py
Introduce code-generated sync equivalents: RoutingFunction, RoutingWrapper, CollectionRoutingWrapper; re-export them from the public sync routing package.
Async Routing Tests
key-value/key-value-aio/tests/stores/wrappers/test_routing.py
New tests exercising get/get_many, delete, ttl and routing across multiple stores/collections; fixture variant for CollectionRoutingWrapper.
Sync Routing Tests (codegen)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
New auto-generated sync tests mirroring async routing tests.
Test Base & Protocol Checks
key-value/key-value-aio/tests/stores/base.py, key-value/key-value-sync/tests/code_gen/stores/base.py
Add test_store to assert store implements the key-value protocol; remove inline pyright ignores from several test signatures and adjust timeouts.
Fallback Wrapper 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
Add @override to FailingStore.get/put and remove inline # noqa: ARG002 comments.
Pydantic Adapter fields
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py, key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
Introduce internal attributes (_key_value, _is_list_model, _type_adapter, _default_collection, _raise_on_validation_error) and adjust typing/validation/serialization handling; external APIs unchanged.
Lint/type-check cleanup (stores & codegen)
.../aio/stores/*, .../sync/code_gen/stores/* (disk, memory, dynamodb, base)
Remove or replace inline pyright: ignore directives, add # noqa where applicable, add/convert docstrings for some helpers; no runtime behavior changes.
Elasticsearch & Keyring tests
key-value/key-value-aio/tests/stores/elasticsearch/test_elasticsearch.py, key-value/key-value-sync/tests/code_gen/stores/elasticsearch/test_elasticsearch.py, key-value/key-value-aio/tests/stores/keyring/test_keyring.py, key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py
Replace hard-coded port mapping with ES_PORT variable and increase readiness wait time; remove inline pyright ignore comments in test signatures.
Build, tooling, docs, config
scripts/build_sync_library.py, pyproject.toml, Makefile, .devcontainer/devcontainer.json, .gitignore, README.md, AGENTS.md
async_to_sync now returns the AST; move ARG001/ARG002 to per-file ignores; adjust Makefile sync target to --group dev; add devcontainer features and docker socket mount; append .DS_Store to .gitignore; document new wrappers in README and expand AGENTS guidance.
Valkey store close guard
key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py, key-value/key-value-sync/src/key_value/sync/code_gen/stores/valkey/store.py
Add guard in _close to return early when no connected client to avoid NoneType access.
Memcached test refactor
key-value/key-value-aio/tests/stores/memcached/test_memcached.py
Introduce MEMCACHED_IMAGE/MEMCACHED_PORT constants, update container setup and ping logic, and adjust fixture lifecycle; other test changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RW as RoutingWrapper
    participant RF as routing_function
    participant Store as ResolvedStore
    participant Default as default_store

    Client->>RW: operation(key, collection)
    RW->>RF: routing_function(collection)
    alt RF returns a store
        RF-->>RW: Store
        RW->>Store: delegate operation(...)
        Store-->>RW: result
    else RF returns None
        RF-->>RW: None
        RW->>Default: delegate operation(...)
        Default-->>RW: result
    end
    RW-->>Client: result
Loading

Possibly related PRs

  • Add AGENTS.md file #130 — Updates AGENTS.md guidance about editing the sync directory; intersects the AGENTS.md changes included here.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning While the core routing wrapper feature is well-implemented and in-scope, the PR contains multiple out-of-scope changes beyond the stated objective to "add RoutingWrapper and CollectionRoutingWrapper". These include systematic code quality improvements (removal of pyright ignore comments across 10+ files, addition of @OverRide decorators), linting configuration changes (pyproject.toml), build tooling modifications (build_sync_library.py return value addition), infrastructure changes (devcontainer.json, .gitignore, Makefile), and unrelated code fixes (Valkey defensive null check in _close, Memcached test refactoring from set/get to stats(), test base infrastructure improvements). While individually beneficial, these changes dilute focus from the core feature and should typically be addressed in separate, focused PRs. Consider extracting the out-of-scope changes into separate PRs: (1) a code quality/linting cleanup PR for removing type-ignore comments and adding @OverRide decorators, (2) a configuration/infrastructure PR for devcontainer, Makefile, and pyproject.toml changes, and (3) a separate PR for the test infrastructure improvements and unrelated store fixes (Valkey, Memcached, Elasticsearch). This will keep the routing wrapper PR focused on its stated objective and make the change history clearer.
Docstring Coverage ⚠️ Warning Docstring coverage is 31.58% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat: add RoutingWrapper and CollectionRoutingWrapper" clearly and accurately describes the primary changes in the pull request. The title is concise, uses conventional commit format, and directly corresponds to the main features being introduced: the RoutingWrapper base class for flexible routing via custom functions and CollectionRoutingWrapper for collection-based routing. The title is specific enough for developers scanning history to understand the key change without being misleading or vague.
Linked Issues Check ✅ Passed The PR successfully implements all primary objectives from issue #114. RoutingWrapper provides the requested helper wrapper for routing requests to different stores using a routing function [#114], while CollectionRoutingWrapper delivers the built-in collection-to-store mapping strategy [#114]. Both wrappers support flexible multi-backend patterns with full AsyncKeyValue protocol method coverage (get, get_many, ttl, ttl_many, put, put_many, delete, delete_many). Both async and sync versions are implemented, comprehensive test coverage is provided (11+ tests), and public APIs are properly exported with documentation in README.md. All coding requirements from the linked issue are met.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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-114-20251026-0414

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

@strawgate
Copy link
Owner Author

@claude please fix up this pull request so it's ready to go

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


PR Ready for Review

I've successfully fixed the issues in this PR and verified everything works correctly.

Issues Found and Fixed

  1. Type signature mismatches - The routing wrapper was using outdated type signatures:

  2. Test compatibility - Updated test case at line 142-147 to use single TTL value instead of list

Verification Results

  • Linting: Passed (4 auto-fixes applied)
  • Tests: All 11 routing wrapper tests pass
  • ⚠️ Type checking: Pre-existing errors in sync library (unrelated to this PR)
  • ⏸️ Sync library generation: Requires Python 3.10 (not available in this environment)

Summary

The async routing wrapper implementation is now complete and correct:

  • All type signatures match the current AsyncKeyValue protocol
  • Comprehensive test coverage (11 tests) validates all operations
  • Code follows repository conventions and style guidelines

The synchronous wrapper will need to be generated separately using make codegen in a Python 3.10 environment (as documented in DEVELOPING.md). This is standard practice for this repository.

Changes Pushed

  • Commit: f1281f6 - "fix: update routing wrapper type signatures to match protocol changes"
  • Files modified:
    • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py
    • key-value/key-value-aio/tests/stores/wrappers/test_routing.py

--- |

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d790a6 and d707848.

📒 Files selected for processing (4)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/__init__.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/__init__.py (2)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (5-55)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1)
  • RoutingWrapper (12-115)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (5-55)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (9)
  • RoutingWrapper (12-115)
  • put (91-93)
  • get (71-73)
  • put_many (96-105)
  • get_many (76-78)
  • delete (108-110)
  • delete_many (113-115)
  • ttl (81-83)
  • ttl_many (86-88)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (2)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (12-115)
  • get (71-73)
🔇 Additional comments (2)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/__init__.py (1)

1-4: Public API export looks good.

key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1)

70-115: Delegation and error handling look correct.

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>
@strawgate
Copy link
Owner Author

@claude please consider the PR feedback and make revisions if necessary and then do a thorough line by line review

@claude
Copy link

claude bot commented Oct 26, 2025

Claude finished @strawgate's task —— View job


PR Review Complete

  • Get project context and PR details
  • Review CodeRabbit feedback and identify actionable items
  • Conduct thorough line-by-line code review
  • Implement necessary fixes
  • Run linting and tests
  • Push changes

Executive Summary

This PR successfully implements routing wrapper functionality for the py-key-value library. The implementation is solid and ready to merge with minor documentation and test improvements applied.

Changes Made

  • ✅ Fixed misleading docstring reference to "key patterns" (commit 11f21cd)
  • ✅ Strengthened TTL test assertions (commit 11f21cd)
  • ✅ All 11 tests passing
  • ✅ Linting passed
  • ✅ Type signatures match current protocol

CodeRabbit Feedback Assessment

I reviewed all 7 CodeRabbit suggestions and found that most were either incorrect or not aligned with this codebase's patterns:

❌ Rejected Suggestions

  1. Critical - pytest.mark.asyncio marker - INVALID

    • The repo has asyncio_mode = "auto" configured (pyproject.toml:54)
    • Explicit markers are not needed and would be redundant
  2. Nitpick - Avoid duplicating default fallback - INCORRECT

    • CodeRabbit misunderstood the design pattern
    • The current implementation is correct: routing function returns None for unmapped collections, base class handles fallback
    • Suggested change would break the fallback mechanism
  3. Nitpick - Broaden collection_map to Mapping - NOT NEEDED

    • Using dict is clearer and more explicit for this use case
    • Other wrappers in this codebase use specific types, not abstract Mappings
    • Would add complexity without meaningful benefit
  4. Nitpick - Freeze collection_map - NOT CODEBASE PATTERN

    • No other wrappers in this codebase freeze their parameters
    • Would be inconsistent with existing patterns (see FallbackWrapper, LoggingWrapper)
  5. Nitpick - Add edge test for None collection - ALREADY COVERED

    • Test already exists: test_collection_map_none_collection (lines 217-234)
    • Comprehensively validates None collection handling with default store

✅ Applied Suggestions

  1. Minor - Docstring mentions "key patterns" - FIXED

    • Removed misleading reference since routing function only receives collection name
    • Updated wrapper.py:17
  2. Nitpick - Strengthen TTL assertions - APPLIED

    • Added explicit validation that TTL values are not None and > 0
    • Improved test robustness in test_routing.py:154-157

Comprehensive Code Review

__init__.py (4 lines)

Perfect - Clean public API exports with proper __all__

wrapper.py (116 lines)

Excellent implementation with proper design patterns:

Lines 1-9: Clean imports, good type alias definition

  • RoutingFunction type alias is clear and well-documented

Lines 12-31: Well-documented class with practical example

  • Fixed: Removed misleading "key patterns" reference (line 17)

Lines 33-48: Constructor is simple and clear

  • Properly documents the None-return contract for routing_function
  • Clear documentation of default_store fallback behavior

Lines 50-68: _get_store method implements correct routing logic

  • ✅ Routing function called first
  • ✅ Falls back to default_store when routing returns None
  • ✅ Raises ValueError with clear message when no store found
  • This is the correct pattern - CodeRabbit's suggestion to change this was wrong

Lines 70-115: All protocol methods properly implemented

Architecture Note: This wrapper doesn't call super().__init__() which is correct - it completely overrides all AsyncKeyValue methods and doesn't use BaseWrapper's pass-through behavior.

collection_routing.py (56 lines)

Excellent convenience wrapper:

Lines 5-30: Clear docstring with practical example

  • Shows real-world usage pattern
  • Documents the default_store fallback

Lines 32-46: Simple, clean constructor

  • Stores collection_map as instance variable
  • Creates routing function inline

Lines 47-50: Routing function implementation

  • ✅ Correctly handles None collection → returns default_store
  • ✅ Uses dict.get() with default_store fallback
  • This delegates to parent's fallback mechanism correctly

Lines 52-55: Proper super().init() call

  • Passes both routing_function and default_store to parent

Note: CodeRabbit suggested changing lines 47-50, but the current implementation is intentionally designed this way. The routing function returns default_store directly for unmapped collections, which is then used by the parent class. This is cleaner than returning None and relying on parent's fallback.

test_routing.py (263 lines)

Comprehensive test coverage with 11 tests covering all scenarios:

Lines 8-73: RoutingWrapper tests

  • test_routing_function_basic: Validates basic multi-store routing
  • test_routing_with_default_store: Tests fallback to default store
  • test_routing_no_default_raises_error: Validates error handling

Lines 75-93: Batch operation tests

  • test_routing_get_many: Validates put_many and get_many routing

Lines 95-122: Delete operation tests

  • Tests both single and bulk delete operations

Lines 123-157: TTL operation tests

  • Validates TTL and ttl_many routing
  • Improved: Added explicit TTL value validation (lines 154-157)

Lines 158-263: CollectionRoutingWrapper tests

  • test_collection_map_routing: Basic collection-based routing
  • test_collection_map_with_default: Default store fallback
  • test_collection_map_no_default_raises: Error when no default
  • test_collection_map_none_collection: None collection handling ← This already covers CodeRabbit's suggestion Add memcached store implementation #7
  • test_collection_map_isolation: Validates complete store isolation

Test Quality: Excellent - tests are clear, focused, and cover both happy paths and error cases.


Additional Observations

Strengths

  1. Clean Architecture: Separation between base RoutingWrapper and CollectionRoutingWrapper is well-designed
  2. Comprehensive Tests: 11 tests covering all AsyncKeyValue protocol methods
  3. Good Documentation: Clear docstrings with practical examples
  4. Error Handling: Appropriate ValueError with descriptive message when no store found
  5. Type Safety: All type annotations are correct and match protocol

Design Patterns Followed

✅ Consistent with other wrappers (LoggingWrapper, FallbackWrapper)
✅ Proper use of BaseWrapper inheritance
✅ Override decorators on all protocol methods
✅ Keyword-only arguments for collection and ttl parameters

Integration

✅ Public API properly exported in __init__.py
✅ Type signatures match AsyncKeyValue protocol
✅ Compatible with existing store implementations
✅ Tests follow repository patterns (using asyncio_mode=auto)


Testing Results

✅ All 11 tests passed
✅ Linting passed (ruff format + ruff check)
✅ No type errors in new code

Recommendation

✅ APPROVE - Ready to Merge

This PR implements the requested routing wrapper functionality correctly and completely. The code is well-tested, properly documented, and follows the repository's existing patterns. The minor improvements I made (documentation clarity and test assertions) further strengthen an already solid implementation.

Notes for Maintainer

  • Sync library generation will be needed (standard for this repo)
  • Consider running @coderabbitai generate docstrings to address the 48% docstring coverage warning if desired
  • No breaking changes or backward compatibility concerns

| Latest commit

…rtions

- Remove misleading reference to 'key patterns' in routing wrapper docstring
  since the routing function only receives the collection name, not keys
- Add explicit TTL validation in ttl_many test to ensure returned values
  are not None and are positive
- Split compound assertions to comply with PT018 lint rule

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

coderabbitai bot commented Oct 26, 2025

Skipped: Cannot respond to another bot.

1 similar comment
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Skipped: Cannot respond to another bot.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1)

240-266: Add edge-case test: None collection without default should raise.

Covers the negative path when no default_store is configured.

 class TestCollectionRoutingWrapper:
@@
         assert await dynamo_store.get(key="id123", collection="sessions") is None
+
+    async def test_collection_map_none_without_default_raises(self):
+        wrapper = CollectionRoutingWrapper(collection_map={"sessions": MemoryStore()})
+        with pytest.raises(ValueError, match="No store found for collection"):
+            await wrapper.get(key="k", collection=None)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1281f6 and 11f21cd.

📒 Files selected for processing (2)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (5-55)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (9)
  • RoutingWrapper (12-115)
  • put (91-93)
  • get (71-73)
  • put_many (96-105)
  • get_many (76-78)
  • delete (108-110)
  • delete_many (113-115)
  • ttl (81-83)
  • ttl_many (86-88)
🔇 Additional comments (2)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1)

12-115: Routing and delegation look solid.

Store resolution, default fallback, and method delegation match AsyncKeyValue; error messaging is clear. Doc wording corrected.

key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1)

1-2: Review comment is incorrect: wrong pytest marker suggested.

The review recommends pytest.mark.anyio, but the project uses pytest-asyncio>=1.2.0 (confirmed in dependencies), not pytest-anyio. The correct marker should be pytest.mark.asyncio.

The suggestion should be:

 import pytest
+pytestmark = pytest.mark.asyncio

Not pytest.mark.anyio as currently stated.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (2)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1)

15-15: Doc fixes: function signature and exceptions.

  • The routing function only receives collection, not key.
  • _get_store never raises here; default_store is required and used as fallback.

Apply:

-    The routing function receives the collection name and key and returns the appropriate store.
+    The routing function receives the collection name and returns the appropriate store.
-        Raises:
-            ValueError: If no store is found for the collection and no default store is configured.
+        Raises:
+            None directly; underlying store methods may raise.

Also applies to: 57-59

key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1)

1-7: Add pytest.mark.asyncio module marker.

Per previous review feedback, the repository uses pytest-asyncio without asyncio_mode=auto configured. Async test methods require explicit markers. Add a module-level marker after the pytest import:

 import pytest
+
+pytestmark = pytest.mark.asyncio
+
 from dirty_equals import IsFloat
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11f21cd and 7b1fa21.

📒 Files selected for processing (14)
  • README.md (2 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-aio/tests/stores/base.py (2 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/__init__.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/wrappers/routing/__init__.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/base.py (2 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (1 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Markdown files must pass markdownlint with an 80-character line length limit (excluding code blocks and tables)

Files:

  • README.md
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow Ruff formatting and linting with a configured maximum line length of 140 characters (per pyproject.toml)
Use Basedpyright in strict mode for static type checking across the Python codebase

Files:

  • key-value/key-value-aio/tests/stores/base.py
  • key-value/key-value-aio/tests/stores/wrappers/test_fallback.py
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py
  • key-value/key-value-sync/tests/code_gen/stores/base.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/__init__.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
  • key-value/key-value-sync/src/key_value/sync/wrappers/routing/__init__.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py
key-value/key-value-sync/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Files:

  • key-value/key-value-sync/tests/code_gen/stores/base.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/__init__.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
  • key-value/key-value-sync/src/key_value/sync/wrappers/routing/__init__.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py
🧬 Code graph analysis (12)
key-value/key-value-aio/tests/stores/base.py (1)
key-value/key-value-sync/tests/code_gen/stores/base.py (3)
  • test_models_put_get (56-60)
  • store (27-27)
  • test_get_large_put_get (68-70)
key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (1)
key-value/key-value-aio/src/key_value/aio/wrappers/fallback/wrapper.py (3)
  • get (46-50)
  • put (74-81)
  • ttl (60-64)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (10)
  • RoutingWrapper (15-104)
  • _get_store (47-62)
  • get (65-67)
  • get_many (70-72)
  • ttl (75-77)
  • ttl_many (80-82)
  • put (85-87)
  • put_many (90-94)
  • delete (97-99)
  • delete_many (102-104)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (3)
  • default_store (30-31)
  • store (34-44)
  • store (121-128)
key-value/key-value-sync/tests/code_gen/stores/base.py (1)
key-value/key-value-aio/tests/stores/base.py (3)
  • test_models_put_get (58-62)
  • store (30-30)
  • test_get_large_put_get (70-72)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (2)
key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (2)
  • get (15-17)
  • put (19-21)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (3)
  • get (65-67)
  • put (85-87)
  • ttl (75-77)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (4)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/tests/stores/base.py (2)
  • store (30-30)
  • BaseStoreTests (24-239)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (8-58)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (6)
  • RoutingWrapper (12-110)
  • put (86-88)
  • get (66-68)
  • get_many (71-73)
  • delete (103-105)
  • ttl (76-78)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/__init__.py (2)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (11-54)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1)
  • RoutingWrapper (15-104)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (4)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-sync/tests/code_gen/stores/base.py (2)
  • store (27-27)
  • BaseStoreTests (21-237)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (11-54)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (6)
  • RoutingWrapper (15-104)
  • put (85-87)
  • get (65-67)
  • get_many (70-72)
  • delete (97-99)
  • ttl (75-77)
key-value/key-value-sync/src/key_value/sync/wrappers/routing/__init__.py (2)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (11-54)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1)
  • RoutingWrapper (15-104)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (2)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (15-104)
  • get (65-67)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (3)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (10)
  • RoutingWrapper (12-110)
  • _get_store (48-63)
  • get (66-68)
  • get_many (71-73)
  • ttl (76-78)
  • ttl_many (81-83)
  • put (86-88)
  • put_many (91-100)
  • delete (103-105)
  • delete_many (108-110)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (3)
  • default_store (33-34)
  • store (37-47)
  • store (118-121)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (2)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (12-110)
  • get (66-68)
⏰ 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). (6)
  • GitHub Check: test_quick (3.10, windows-2022, key-value/key-value-aio)
  • GitHub Check: test_quick (3.10, windows-latest, key-value/key-value-sync)
  • GitHub Check: test_quick (3.10, windows-latest, key-value/key-value-aio)
  • GitHub Check: test_quick (3.10, windows-2022, key-value/key-value-sync)
  • GitHub Check: test_quick (3.10, ubuntu-latest, key-value/key-value-aio)
  • GitHub Check: test_quick (3.10, ubuntu-22.04, key-value/key-value-aio)
🔇 Additional comments (20)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (1)

18-24: LGTM — cleaner signatures without inline suppressions.

Relying on project-level lint config/pyright is fine here.

key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/__init__.py (1)

1-7: LGTM! Auto-generated routing wrapper exports are correct.

The file correctly re-exports CollectionRoutingWrapper, RoutingFunction, and RoutingWrapper from the code-gen routing modules, establishing the public API surface for sync routing wrappers.

README.md (2)

299-299: LGTM! Clear documentation for CollectionRoutingWrapper.

The table entry effectively describes the collection-based routing pattern with a practical example showing how to map collections to different stores.


310-310: LGTM! Clear documentation for RoutingWrapper.

The table entry effectively describes the function-based routing pattern with a concise lambda example demonstrating custom routing logic.

key-value/key-value-aio/tests/stores/wrappers/test_routing.py (5)

24-52: LGTM! Well-structured test fixtures.

The fixtures correctly set up isolated store instances with a routing function that maps specific collections to dedicated stores and falls back to default_store for unmapped collections. The store_with_data fixture seeds appropriate test data across all three collections.


53-75: LGTM! Comprehensive routing isolation test.

The test thoroughly validates that routing directs operations to the correct stores, ensuring data isolation across collections. Both single-key and batch operations are verified for all three routing scenarios (mapped to first store, mapped to second store, and fallback to default).


76-100: LGTM! Thorough delete routing validation.

The test comprehensively verifies that delete operations are correctly routed to the appropriate stores. For each collection, it confirms the deletion affects only the targeted store and leaves other stores unchanged, ensuring proper isolation.


102-117: LGTM! TTL routing validation is correct.

The test verifies that TTL operations are properly routed to the correct stores and that TTL values are preserved approximately as expected. The use of IsFloat(approx=...) appropriately handles minor timing variations.


119-128: LGTM! Excellent test reuse pattern.

By inheriting from TestRoutingWrapper and overriding only the store fixture to use CollectionRoutingWrapper, this class efficiently validates that the collection-map-based routing behaves identically to the custom function-based routing. This ensures consistency between the two routing approaches.

key-value/key-value-sync/src/key_value/sync/wrappers/routing/__init__.py (1)

1-7: LGTM! Auto-generated sync wrapper exports are correct.

The file correctly re-exports routing wrappers from the code-gen path, mirroring the pattern used throughout the sync library to expose the public API.

key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (4)

1-6: LGTM! Clean imports.

The imports are minimal and appropriate: Mapping for flexible input types, MappingProxyType for immutability, and the necessary protocol and base wrapper classes.


8-33: LGTM! Excellent class documentation.

The docstring clearly explains the purpose of collection-based routing and provides a practical example demonstrating routing sessions to Redis, users to DynamoDB, cache to memory, with a disk fallback for unmapped collections.


35-46: LGTM! Well-documented constructor signature.

The constructor accepts Mapping[str, AsyncKeyValue] (broad input type) and requires a default_store. The docstring clearly explains each parameter's role.


47-58: LGTM! Correct implementation addressing past feedback.

The implementation properly:

  • Freezes collection_map using MappingProxyType to prevent accidental mutation (line 47)
  • Defines route_by_collection to return only mapped stores or None (lines 49-53)
  • Delegates default fallback logic to the base RoutingWrapper (lines 55-58)

All past review suggestions have been correctly incorporated.

key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (6)

1-10: LGTM! Properly generated sync test imports.

The auto-generated test file correctly imports from the sync code-gen path and includes all necessary test dependencies.


27-55: LGTM! Well-structured sync test fixtures.

The fixtures correctly set up isolated synchronous store instances with appropriate routing logic. The store_with_data fixture properly seeds test data across all three collections without async/await (correctly generated for sync).


56-74: LGTM! Comprehensive sync routing test.

The test thoroughly validates routing behavior across three stores, ensuring proper data isolation. The # pyright: ignore[reportUnusedParameter] comment on line 58 is appropriate—the default_store fixture must be injected for proper test setup even though it's not directly referenced in the test body.


75-100: LGTM! Thorough sync delete routing test.

The test comprehensively verifies that delete operations correctly route to the appropriate stores and maintain isolation across collections.


101-114: LGTM! Correct sync TTL routing test.

The test verifies TTL operations are properly routed with appropriate approximate matching for TTL values using IsFloat(approx=...).


116-121: LGTM! Effective test inheritance pattern.

The TestCollectionRoutingWrapper class efficiently reuses the parent test suite by overriding only the store fixture, ensuring consistency between custom function-based and collection-map-based routing.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1)

1-4: Add pytest.mark.asyncio module marker (confirmed missing in previous review).

Per the past review, pytest-asyncio requires explicit markers when asyncio_mode=auto is not configured. Add a module-level marker so async tests execute properly:

 import pytest
+pytestmark = pytest.mark.asyncio
+
 from dirty_equals import IsFloat
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b1fa21 and 5ad8474.

📒 Files selected for processing (3)
  • key-value/key-value-aio/pyproject.toml (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow Ruff formatting and linting with a configured maximum line length of 140 characters (per pyproject.toml)
Use Basedpyright in strict mode for static type checking across the Python codebase

Files:

  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
key-value/key-value-sync/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Files:

  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
🧬 Code graph analysis (2)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (4)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/tests/stores/base.py (2)
  • store (30-30)
  • BaseStoreTests (24-239)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (8-58)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (6)
  • RoutingWrapper (12-110)
  • put (86-88)
  • get (66-68)
  • get_many (71-73)
  • delete (103-105)
  • ttl (76-78)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (4)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-sync/tests/code_gen/stores/base.py (2)
  • store (27-27)
  • BaseStoreTests (21-237)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (11-54)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (6)
  • RoutingWrapper (15-104)
  • put (85-87)
  • get (65-67)
  • get_many (70-72)
  • delete (97-99)
  • ttl (75-77)
🪛 GitHub Actions: Run Tests
key-value/key-value-aio/tests/stores/wrappers/test_routing.py

[error] 56-56: uv run basedpyright .: Method "test_not_unbounded" overrides class "BaseStoreTests" in an incompatible manner. Parameter 2 type mismatch: base parameter is type "BaseStore", override parameter is type "RoutingWrapper". (reportIncompatibleMethodOverride)

key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py

[error] 59-59: Method "test_not_unbounded" overrides class "BaseStoreTests" in an incompatible manner. Parameter 2 type mismatch: base parameter is type "BaseStore", override parameter is type "RoutingWrapper". "BaseStore" is not assignable to "RoutingWrapper" (reportIncompatibleMethodOverride).

🪛 GitHub Check: static_analysis (key-value/key-value-aio)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py

[failure] 56-56:
Method "test_not_unbounded" overrides class "BaseStoreTests" in an incompatible manner
  Parameter 2 type mismatch: base parameter is type "BaseStore", override parameter is type "RoutingWrapper"
    "BaseStore" is not assignable to "RoutingWrapper" (reportIncompatibleMethodOverride)

🪛 GitHub Check: static_analysis (key-value/key-value-sync)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py

[failure] 59-59:
Method "test_not_unbounded" overrides class "BaseStoreTests" in an incompatible manner
  Parameter 2 type mismatch: base parameter is type "BaseStore", override parameter is type "RoutingWrapper"
    "BaseStore" is not assignable to "RoutingWrapper" (reportIncompatibleMethodOverride)

🔇 Additional comments (12)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (7)

1-11: LGTM! File header and imports are correct.

The auto-generated warning is clear, and all necessary imports are present for testing the routing wrappers.


13-26: LGTM! Test constants are well-organized.

The constants clearly define test data for three routing scenarios (first collection, second collection, and unmapped collection), which aligns with the routing test coverage.


28-56: LGTM! Fixtures are well-structured.

The fixtures properly set up isolated test stores and implement collection-based routing logic. The store_with_data fixture correctly seeds data across all three routing scenarios.


61-79: LGTM! Comprehensive routing validation.

The test thoroughly validates that routing correctly directs operations to the appropriate underlying stores. The fixture dependency pattern (where store_with_data triggers setup even without direct reference) is a standard pytest approach.


80-105: LGTM! Delete operations are thoroughly tested.

The test validates that delete operations correctly route to the target store and maintains isolation across stores. Each routing scenario (first, second, and unmapped collections) is tested independently.


106-119: LGTM! TTL routing is properly tested.

The test validates that TTL operations route correctly and uses appropriate floating-point comparison with IsFloat(approx=...) for TTL values. Different TTL values for each key ensure routing independence.


121-126: LGTM! CollectionRoutingWrapper test reuses logic effectively.

The subclass correctly tests CollectionRoutingWrapper by inheriting all test methods and only overriding the fixture to use the collection-map-based wrapper. This validates that both routing implementations behave identically.

key-value/key-value-aio/tests/stores/wrappers/test_routing.py (5)

10-23: LGTM: Clear test constants.

The test constants are well-organized with clear naming patterns, making the test assertions easy to follow.


25-53: LGTM: Well-structured test fixtures.

The fixtures properly set up the routing infrastructure with distinct stores for different collections and pre-populate test data across all routing paths.


81-106: LGTM: Comprehensive delete routing validation.

The test properly verifies that delete operations route to the correct store and that data isolation is maintained across stores.


107-122: LGTM: TTL routing validation is correct.

The test properly validates that TTL operations route to the appropriate stores and return expected TTL values using appropriate fuzzy matching.


124-133: LGTM: Excellent test reuse pattern.

The subclass approach allows full test coverage of CollectionRoutingWrapper by inheriting all parent tests while only changing the fixture implementation. This validates that both routing implementations provide identical behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (1)

1-3: Do not edit this generated file directly.

This file is auto-generated from the source file in key-value/key-value-aio/. According to the coding guidelines and the warning at the top of this file, changes should be made to the original source file and then regenerated using make codegen, not edited here directly. Any changes made to this file will be overwritten during the next code generation run.

Please revert the changes to this file and ensure the corresponding changes are applied to the source file at key-value/key-value-aio/src/key_value/aio/stores/memory/store.py instead, then regenerate this sync version.

Based on coding guidelines.

scripts/build_sync_library.py (1)

125-129: Consider removing the unused filepath parameter or document its purpose.

The filepath parameter is suppressed with # noqa: ARG001 because it's never used in the function body. While the return value addition is correct, the unused parameter should be addressed:

  • If filepath is reserved for future use (e.g., error messages or debugging), document this intent in a comment.
  • Otherwise, remove the parameter to simplify the interface.

Regarding the # noqa: RET504 on line 129, you could avoid this suppression by refactoring to return directly without the assignment:

 def async_to_sync(tree: ast.AST, filepath: Path | None = None) -> ast.AST:  # noqa: ARG001
     tree = BlanksInserter().visit(tree)
     tree = RenameAsyncToSync().visit(tree)
-    tree = AsyncToSync().visit(tree)
-    return tree  # noqa: RET504
+    return AsyncToSync().visit(tree)

However, the current pattern is consistent and acceptable if you prefer the step-by-step assignment style.

key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/store.py (1)

1-3: Critical: Do not edit auto-generated files directly.

This file is auto-generated (as stated in the header) and must not be edited directly. The coding guidelines explicitly state: "Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen."

The change removing the type-ignore directive should only be made in the async source file (key-value/key-value-aio/src/key_value/aio/stores/disk/store.py), then this sync version should be regenerated using make codegen.

As per coding guidelines.

Revert this manual change and regenerate the file:

#!/bin/bash
# Description: Revert manual changes and regenerate sync version from async source

# Navigate to project root
cd key-value

# Regenerate the sync code from async source
make codegen

Also applies to: 91-91

key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1)

58-64: Replace pyright ignore with a precise cast

Avoid # pyright: ignore[reportAny] under strict mode. Cast the return to the expected shape instead.

Apply this diff to the changed line:

-            return self._type_adapter.dump_python(value, mode="json")  # pyright: ignore[reportAny]
+            return cast(dict[str, Any], self._type_adapter.dump_python(value, mode="json"))

And add cast to imports (outside this hunk):

-from typing import Any, Generic, SupportsFloat, TypeVar, get_origin
+from typing import Any, Generic, SupportsFloat, TypeVar, get_origin, cast

As per coding guidelines (Basedpyright strict).

key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (1)

58-64: Fix the pyright ignore in the AIO source and regenerate

The source file at key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py line 63 contains the # pyright: ignore[reportAny] comment. Replace it with a typed cast and add the import:

  1. Update imports at line 2:
-from typing import Any, Generic, SupportsFloat, TypeVar, get_origin
+from typing import Any, Generic, SupportsFloat, TypeVar, get_origin, cast
  1. Update line 63:
-            return self._type_adapter.dump_python(value, mode="json")  # pyright: ignore[reportAny]
+            return cast(dict[str, Any], self._type_adapter.dump_python(value, mode="json"))
  1. Run make codegen to regenerate the sync version.
key-value/key-value-aio/src/key_value/aio/stores/dynamodb/store.py (2)

37-37: Remove unused _session class attribute.

The _session attribute is declared but never assigned or used anywhere in the code. The session is created as a local variable on line 104 and the client is stored in _raw_client instead. This is dead code that should be removed to maintain code clarity.

-    _session: aioboto3.Session  # pyright: ignore[reportAny]
     _table_name: str
     _endpoint_url: str | None
     _raw_client: Any  # DynamoDB client from aioboto3

104-109: Remove unused _session attribute and add missing type stub dependency.

Two critical issues need fixing:

  1. Line 37: Remove the unused _session class attribute entirely. It's declared but never assigned or referenced anywhere in the code.

  2. Line 104 & dependencies: Removing the # pyright: ignore[reportAny] comment requires the types-aioboto3 package for proper type checking. Add "types-aioboto3" to the dynamodb extra in pyproject.toml:

    dynamodb = ["aioboto3>=13.3.0", "types-aiobotocore-dynamodb>=2.16.0", "types-aioboto3"]
    

    Without this dependency, Basedpyright in strict mode will still report reportAny errors on the aioboto3.Session() call since aioboto3 lacks native type annotations.

♻️ Duplicate comments (4)
key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (1)

15-23: LGTM: Override decorators added as previously requested.

The @override decorators on FailingStore.get and FailingStore.put make the overrides explicit and enable proper type checking in strict Basedpyright mode.

key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (1)

38-60: Fix type incompatibility in the original source file.

The test_not_unbounded method declares parameter type store: BaseStore (line 60), but the fixture at line 39 returns RoutingWrapper. RoutingWrapper is not assignable to BaseStore, causing pipeline failures flagged in past reviews.

Apply this fix in the original async source file key-value/key-value-aio/tests/stores/wrappers/test_routing.py:

     @override
     @pytest.mark.skip(reason="RoutingWrapper is unbounded")
-    def test_not_unbounded(self, store: BaseStore): ...
+    def test_not_unbounded(self, store): ...

Removing the type annotation allows pytest to inject the fixture without type checking conflicts. As per coding guidelines.

key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1)

47-62: Fix misleading docstring in the original source file.

The docstring at lines 56-58 claims "_get_store" raises a ValueError when no store is found, but the implementation at lines 59-62 silently returns default_store instead. Since default_store is required in __init__ (line 36), this exception can never occur.

Update the docstring in the original async source file (this is auto-generated) to accurately reflect the behavior:

     def _get_store(self, collection: str | None) -> KeyValue:
         """Get the appropriate store for the given collection.

         Args:
             collection: The collection name to route.

         Returns:
             The KeyValue store to use for this collection.
-
-        Raises:
-            ValueError: If no store is found for the collection and no default store is configured.
         """
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1)

1-10: Add pytest.mark.asyncio module marker to enable async test execution.

The repository uses pytest-asyncio without asyncio_mode=auto configured. Async test methods require explicit markers to run. Add a module-level marker at the top of the file:

 import pytest
+
+pytestmark = pytest.mark.asyncio
+
 from dirty_equals import IsFloat

As per past review comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ad8474 and fb752f4.

📒 Files selected for processing (29)
  • .devcontainer/devcontainer.json (2 hunks)
  • .gitignore (1 hunks)
  • Makefile (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (2 hunks)
  • key-value/key-value-aio/src/key_value/aio/stores/base.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/stores/disk/store.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/stores/dynamodb/store.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (2 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-aio/tests/stores/base.py (3 hunks)
  • key-value/key-value-aio/tests/stores/elasticsearch/test_elasticsearch.py (1 hunks)
  • key-value/key-value-aio/tests/stores/keyring/test_keyring.py (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (1 hunks)
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py (1 hunks)
  • key-value/key-value-shared/src/key_value/shared/code_gen/gather.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (2 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/store.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (2 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/base.py (3 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/elasticsearch/test_elasticsearch.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py (1 hunks)
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (1 hunks)
  • pyproject.toml (2 hunks)
  • scripts/build_sync_library.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow Ruff formatting and linting with a configured maximum line length of 140 characters (per pyproject.toml)
Use Basedpyright in strict mode for static type checking across the Python codebase

Files:

  • key-value/key-value-aio/tests/stores/wrappers/test_fallback.py
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py
  • key-value/key-value-aio/src/key_value/aio/stores/disk/store.py
  • key-value/key-value-aio/tests/stores/wrappers/test_routing.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
  • key-value/key-value-shared/src/key_value/shared/code_gen/gather.py
  • key-value/key-value-aio/tests/stores/elasticsearch/test_elasticsearch.py
  • key-value/key-value-aio/tests/stores/base.py
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py
  • key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/store.py
  • key-value/key-value-aio/src/key_value/aio/stores/base.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py
  • key-value/key-value-aio/src/key_value/aio/stores/dynamodb/store.py
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py
  • key-value/key-value-sync/tests/code_gen/stores/base.py
  • key-value/key-value-sync/tests/code_gen/stores/elasticsearch/test_elasticsearch.py
  • key-value/key-value-aio/tests/stores/keyring/test_keyring.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py
  • scripts/build_sync_library.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
  • key-value/key-value-aio/src/key_value/aio/stores/memory/store.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py
key-value/key-value-sync/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Files:

  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py
  • key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/store.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py
  • key-value/key-value-sync/tests/code_gen/stores/base.py
  • key-value/key-value-sync/tests/code_gen/stores/elasticsearch/test_elasticsearch.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
  • key-value/key-value-sync/tests/code_gen/stores/wrappers/test_fallback.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py
key-value/key-value-aio/src/key_value/aio/stores/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

key-value/key-value-aio/src/key_value/aio/stores/**/*.py: Async store implementations must implement the AsyncKeyValue protocol from key_value/aio/protocols/key_value.py
Store backends must store and retrieve ManagedEntry wrappers (with value, creation timestamp, and TTL), not raw values

Files:

  • key-value/key-value-aio/src/key_value/aio/stores/disk/store.py
  • key-value/key-value-aio/src/key_value/aio/stores/base.py
  • key-value/key-value-aio/src/key_value/aio/stores/dynamodb/store.py
  • key-value/key-value-aio/src/key_value/aio/stores/memory/store.py
🧠 Learnings (1)
📚 Learning: 2025-10-26T13:44:25.428Z
Learnt from: CR
PR: strawgate/py-key-value#0
File: AGENTS.md:0-0
Timestamp: 2025-10-26T13:44:25.428Z
Learning: Applies to key-value/key-value-aio/src/key_value/aio/stores/**/*.py : Async store implementations must implement the AsyncKeyValue protocol from key_value/aio/protocols/key_value.py

Applied to files:

  • key-value/key-value-aio/tests/stores/base.py
  • key-value/key-value-sync/tests/code_gen/stores/base.py
🧬 Code graph analysis (19)
key-value/key-value-aio/tests/stores/wrappers/test_fallback.py (3)
key-value/key-value-aio/src/key_value/aio/stores/base.py (3)
  • get (111-137)
  • put (193-204)
  • ttl (148-157)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (3)
  • get (9-24)
  • put (39-49)
  • ttl (26-37)
key-value/key-value-aio/src/key_value/aio/wrappers/fallback/wrapper.py (3)
  • get (46-50)
  • put (74-81)
  • ttl (60-64)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (1)
key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (3)
  • _memory_cache_ttu (46-55)
  • MemoryCacheEntry (25-43)
  • _memory_cache_getsizeof (58-60)
key-value/key-value-aio/src/key_value/aio/stores/disk/store.py (2)
key-value/key-value-aio/src/key_value/aio/stores/base.py (1)
  • get (111-137)
key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (1)
  • get (84-92)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (5)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/tests/stores/base.py (3)
  • store (31-31)
  • BaseStoreTests (25-243)
  • test_not_unbounded (200-208)
key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (5)
  • MemoryStore (105-180)
  • put (94-95)
  • get (84-92)
  • keys (100-102)
  • delete (97-98)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (8-49)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (6)
  • RoutingWrapper (12-110)
  • put (86-88)
  • get (66-68)
  • get_many (71-73)
  • delete (103-105)
  • ttl (76-78)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (5)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-sync/tests/code_gen/stores/base.py (3)
  • store (28-28)
  • BaseStoreTests (22-240)
  • test_not_unbounded (197-205)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (5)
  • MemoryStore (100-169)
  • put (89-90)
  • get (79-87)
  • keys (95-97)
  • delete (92-93)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)
  • CollectionRoutingWrapper (11-45)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (6)
  • RoutingWrapper (15-104)
  • put (85-87)
  • get (65-67)
  • get_many (70-72)
  • delete (97-99)
  • ttl (75-77)
key-value/key-value-aio/tests/stores/elasticsearch/test_elasticsearch.py (2)
key-value/key-value-shared/src/key_value/shared/stores/wait.py (1)
  • async_wait_for_true (7-15)
key-value/key-value-sync/tests/code_gen/stores/elasticsearch/test_elasticsearch.py (1)
  • ping_elasticsearch (29-33)
key-value/key-value-aio/tests/stores/base.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (3)
  • AsyncKeyValueProtocol (6-115)
  • put (39-49)
  • get (9-24)
key-value/key-value-sync/tests/code_gen/stores/base.py (2)
  • test_store (31-33)
  • store (28-28)
key-value/key-value-aio/src/key_value/aio/stores/base.py (3)
  • BaseStore (28-280)
  • put (193-204)
  • get (111-137)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (10)
  • RoutingWrapper (15-104)
  • _get_store (47-62)
  • get (65-67)
  • get_many (70-72)
  • ttl (75-77)
  • ttl_many (80-82)
  • put (85-87)
  • put_many (90-94)
  • delete (97-99)
  • delete_many (102-104)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (3)
  • default_store (32-33)
  • store (36-46)
  • store (127-134)
key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py (1)
key-value/key-value-sync/tests/code_gen/stores/base.py (2)
  • test_get_large_put_get (71-73)
  • store (28-28)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/store.py (2)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (1)
  • get (114-135)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (1)
  • get (79-87)
key-value/key-value-aio/src/key_value/aio/stores/base.py (7)
key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (1)
  • _setup_collection (129-130)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (1)
  • _setup_collection (77-78)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (1)
  • _setup_collection (124-125)
key-value/key-value-aio/src/key_value/aio/stores/disk/multi_store.py (1)
  • _setup_collection (107-108)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/multi_store.py (1)
  • _setup_collection (110-111)
key-value/key-value-aio/src/key_value/aio/stores/mongodb/store.py (1)
  • _setup_collection (123-138)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/mongodb/store.py (1)
  • _setup_collection (130-145)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (3)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (10)
  • RoutingWrapper (12-110)
  • _get_store (48-63)
  • get (66-68)
  • get_many (71-73)
  • ttl (76-78)
  • ttl_many (81-83)
  • put (86-88)
  • put_many (91-100)
  • delete (103-105)
  • delete_many (108-110)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (3)
  • default_store (35-36)
  • store (39-49)
  • store (124-127)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (12-110)
  • get (66-68)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (2)
  • CollectionRoutingWrapper (11-45)
  • route_by_collection (39-43)
key-value/key-value-sync/tests/code_gen/stores/base.py (2)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValueProtocol (9-108)
key-value/key-value-aio/tests/stores/base.py (2)
  • test_store (34-36)
  • store (31-31)
key-value/key-value-sync/tests/code_gen/stores/elasticsearch/test_elasticsearch.py (2)
key-value/key-value-shared/src/key_value/shared/stores/wait.py (1)
  • wait_for_true (18-26)
key-value/key-value-aio/tests/stores/elasticsearch/test_elasticsearch.py (1)
  • ping_elasticsearch (26-30)
key-value/key-value-aio/tests/stores/keyring/test_keyring.py (1)
key-value/key-value-aio/tests/stores/base.py (2)
  • test_get_large_put_get (74-76)
  • store (31-31)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/base.py (3)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (1)
  • _setup_collection (124-125)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/disk/multi_store.py (1)
  • _setup_collection (110-111)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/mongodb/store.py (1)
  • _setup_collection (130-145)
key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (1)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py (3)
  • _memory_cache_ttu (45-54)
  • MemoryCacheEntry (28-42)
  • _memory_cache_getsizeof (57-59)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (3)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (15-104)
  • get (65-67)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (2)
  • CollectionRoutingWrapper (8-49)
  • route_by_collection (40-44)
🔇 Additional comments (38)
.gitignore (1)

9-11: LGTM! MacOS-specific artifacts are now properly ignored.

The addition of .DS_Store is a sensible hygiene improvement. The placement and formatting are consistent with the rest of the file.

Makefile (1)

72-80: Inconsistency between PROJECT-specific and all-packages sync commands.

Line 75 uses --locked --group dev, but line 78 uses only --group dev. This discrepancy could lead to different dependency resolutions across the two code paths:

  • The --locked flag ensures reproducible lockfile-based syncing and should typically be consistent across both paths.
  • If other dependency groups (e.g., main, optional) exist, restricting only to --group dev may skip essential dependencies.

Please verify:

  1. Should --locked be added to line 78 for reproducibility consistency?
  2. Is restricting to --group dev intentional, or should all dependency groups be included?

For reference, the PROJECT-specific path (line 75) is: @cd $(PROJECT) && uv sync --locked --group dev

pyproject.toml (1)

70-71: ✓ Per-file ignores correctly scoped to tests.

ARG001 and ARG002 are now scoped to test files only, removing the overly broad global suppression. This aligns with the earlier feedback and ensures production code maintains stricter linting enforcement.

key-value/key-value-shared/src/key_value/shared/code_gen/gather.py (1)

13-13: The review comment is incorrect—the noqa suppression is correct as-is.

The distinction between ARG001 and ARG002 is based on function type, not argument kind. An unused **kwargs in a free function reports ARG001, while an unused **kwargs in an instance method reports ARG002. Since gather() is a plain function, the # noqa: ARG001 comment correctly suppresses the diagnostic for the unused **kwargs parameter. No change is needed.

key-value/key-value-aio/tests/stores/elasticsearch/test_elasticsearch.py (2)

42-42: Good refactoring to use ES_PORT constant.

Using the ES_PORT variable instead of the hard-coded port number improves maintainability and consistency.


44-44: Improved test reliability with longer wait time.

Increasing the wait time to 2 seconds helps ensure Elasticsearch is fully ready before running tests, reducing potential flakiness.

.devcontainer/devcontainer.json (2)

8-9: Verify the connection between devcontainer infrastructure changes and the routing wrapper PR.

The PR summary describes routing wrapper functionality for key-value stores, but the devcontainer changes (github-cli, git, --network=host, Docker socket mount) suggest infrastructure or CI/integration testing improvements. The relationship between these changes and the PR objectives is unclear.

Please clarify:

  • Are these devcontainer changes necessary to support the routing wrapper implementation or its tests?
  • Should these infrastructure changes be documented in the PR description or moved to a separate PR?
  • Are there specific testing or integration scenarios that require network host access and Docker socket mounting?

14-16: Docker socket mount is necessary and justified—integration tests require it.

The mounting of /var/run/docker.sock is essential for this codebase's test suite. Multiple integration tests spawn external service containers (Redis, MongoDB, Elasticsearch, Vault, Valkey) using the Docker Python SDK. Tests properly skip when Docker is unavailable.

The concern about privileged access is valid, but the necessity is clear and intentional. However, adding an inline comment to .devcontainer/devcontainer.json explaining this dependency would improve maintainability and clarify the purpose to future maintainers.

key-value/key-value-aio/src/key_value/aio/stores/disk/store.py (1)

88-88: Type-ignore removal is sound—diskcache-stubs are available.

The removal of the pyright ignore directive is justified. The codebase includes diskcache-stubs>=5.6.3.6.20240818 in dependencies, which provides proper type annotations for the tuple unpacking at line 88. The code structure is correct: expire_epoch is pre-declared with type float | None on line 86, matching the return type of self._cache.get(key=combo_key, expire_time=True).

Verify locally that Basedpyright (strict mode) passes without errors on this file:

basedpyright key-value/key-value-aio/src/key_value/aio/stores/disk/store.py
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1)

42-42: Good: explicit TypeAdapter annotation clarifies intent

The explicit _type_adapter: TypeAdapter[T] improves readability and type safety. LGTM.

key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (1)

42-42: Good: mirrors AIO adapter’s typed TypeAdapter

Keeping _type_adapter: TypeAdapter[T] consistent across AIO/Sync is correct.

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

74-75: LGTM: Pyright suppression correctly removed.

The _setup_collection method is abstract and the collection parameter is used by all subclass implementations (MemoryStore, DiskStore, MongoDB), so the pyright ignore directive was unnecessary.

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

77-78: LGTM: Docstring added correctly.

The docstring clarifies the purpose of _setup_collection. Since this is auto-generated from the async source, the change correctly propagates from the original file.

key-value/key-value-aio/src/key_value/aio/stores/memory/store.py (2)

46-55: LGTM: Pyright suppression removal is appropriate.

The _memory_cache_ttu function properly uses all its parameters, so the removal of the pyright ignore directive is correct.


58-60: LGTM: Linting directive switched to Ruff.

The switch from pyright: ignore[reportUnusedParameter] to # noqa: ARG001 aligns with the project's use of Ruff for linting. The suppression is appropriate since _memory_cache_getsizeof intentionally ignores the value parameter per the cachetools API.

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

33-36: LGTM: Protocol compliance test added.

The test_store method verifies that store implementations correctly implement AsyncKeyValueProtocol, which is a useful validation for the new routing wrappers and existing stores.


62-74: LGTM: Pyright suppressions removed.

The inline # pyright: ignore[reportUnusedParameter, reportUnusedParameter] comments (with duplicate codes) have been removed. The test parameters are consumed by pytest's parametrize decorator, so they only appear unused to static analysis.

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

18-26: LGTM: Override decorators correctly generated.

The @override decorators on the sync FailingStore methods correctly mirror the async implementation, maintaining consistency across code-gen and async versions.

key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (1)

62-120: LGTM: Routing tests comprehensively verify delegation.

The tests verify that:

  • Operations route to the correct backing stores based on collection name
  • Unmapped collections fall back to the default store
  • Delete operations propagate correctly
  • TTL operations preserve expiration across routing
key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py (1)

35-36: Verify change was made to the original source file.

The removal of the pyright: ignore[reportUnusedParameter] comment is correct since all parameters are used in the super() call. However, per the file header and coding guidelines, this is auto-generated code and should not be edited directly.

Ensure the corresponding change was made to the original source file that generates this code.

As per coding guidelines.

key-value/key-value-aio/tests/stores/wrappers/test_routing.py (7)

11-24: LGTM!

Test constants are well-defined with clear naming that makes the test intent obvious. The separation of mapped and unmapped collections facilitates comprehensive routing tests.


27-53: LGTM!

Fixtures are well-structured with proper isolation. The routing function correctly returns None for unmapped collections, delegating default store fallback to the base RoutingWrapper implementation.


55-57: LGTM!

The method signature now correctly uses BaseStore type, matching the base class signature. This resolves the previous reportIncompatibleMethodOverride error.


59-81: LGTM!

Test comprehensively validates routing behavior by confirming each backing store receives only its routed collections. The default_store parameter is correctly used in assertions (lines 77-80), and the previously flagged unnecessary pyright ignore comment has been removed.


82-106: LGTM!

Delete test comprehensively validates that delete operations route to the correct store and maintain isolation across stores.


108-122: LGTM!

TTL test validates that TTL operations route correctly and preserve TTL semantics across different stores. The use of IsFloat(approx=...) appropriately handles minor timing variations.


125-134: LGTM!

Excellent test reuse via inheritance. This validates that CollectionRoutingWrapper provides the same routing semantics as the custom routing_function approach in the parent test class.

key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (2)

8-24: LGTM!

Class documentation clearly explains the collection-based routing pattern with a helpful example.


26-49: LGTM!

Implementation correctly:

  • Accepts any Mapping type for flexibility
  • Freezes the collection map with MappingProxyType to prevent mutation
  • Delegates default store fallback to the base RoutingWrapper by returning None for unmapped collections
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)

11-45: LGTM!

Auto-generated sync implementation correctly mirrors the async version, using KeyValue protocol instead of AsyncKeyValue. The implementation properly uses MappingProxyType for immutability and delegates default fallback to the base wrapper.

key-value/key-value-sync/tests/code_gen/stores/base.py (3)

17-17: LGTM!

Import addition supports the new protocol compliance test.


30-33: LGTM!

Protocol compliance test with appropriate timeout ensures stores correctly implement the KeyValueProtocol interface.


59-71: LGTM!

Removal of pyright ignore comments is correct since all parameters are used in the test methods. Ensure corresponding changes were made to the original source file, as this is auto-generated code.

As per coding guidelines.

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

9-9: LGTM!

Clear type alias that documents the routing function contract: takes a collection name and returns either a store or None.


12-31: LGTM!

Class documentation accurately describes collection-based routing with a clear example. Previous concern about implying key-based routing has been addressed.


33-46: LGTM!

Clean initialization that stores the routing function and default store. Type checking ensures the routing function signature is correct.


65-110: LGTM!

All AsyncKeyValue protocol methods correctly delegate to the resolved store with consistent patterns:

  1. Resolve store via _get_store(collection)
  2. Delegate operation with all original parameters
  3. Return/await the result

The @override decorator ensures signatures stay in sync with the protocol.

key-value/key-value-aio/tests/stores/keyring/test_keyring.py (1)

32-33: Removal of unnecessary ignore directive is correct.

The json parameter is forwarded to the parent method on line 33, so the reportUnusedParameter ignore directive was unnecessary. This cleanup appropriately aligns with the PR's type-checking directive refactoring.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (2)

43-47: Same origin/issubclass guard needed — fix upstream and regenerate

This mirrors the AIO logic; apply the AIO fix in the source adapter/template and re-run codegen. Do not edit this file directly.

After updating the source, ensure make codegen (or your generation step) reproduces the change here.


69-69: Replace suppression with cast in source; regenerate

As with AIO, prefer typing.cast over pyright ignore. Make the change in the original file so codegen reflects it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb752f4 and edf6f23.

📒 Files selected for processing (2)
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow Ruff formatting and linting with a configured maximum line length of 140 characters (per pyproject.toml)
Use Basedpyright in strict mode for static type checking across the Python codebase

Files:

  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py
key-value/key-value-sync/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Files:

  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
🧬 Code graph analysis (2)
key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (1)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
🔇 Additional comments (5)
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (3)

18-22: Private attributes declaration looks good

Explicit, private fields improve strict type checking and readability. No behavior change.


49-50: Defaults wired correctly

_default_collection and _raise_on_validation_error are set plainly; no issues.


69-69: Add cast import and replace pyright ignore with explicit type cast

The suggested fix is correct. The import statement at line 2 currently lacks cast, and using cast(dict[str, Any], ...) is the appropriate way to handle the Any return type from Pydantic's dump_python() method while satisfying strict type checking. Apply the provided diff as-is. Note: the sync adapter will automatically receive the same fix when regenerated, as indicated by its auto-generation notice.

key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (2)

21-26: Generated fields look correct

Private attributes mirror the AIO version; consistent and type-friendly.


49-50: Defaults assigned correctly in generated code

Matches AIO; no issues.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (1)

1-166: CRITICAL: Do not modify generated code directly.

This file is auto-generated (line 1 warning) and changes here will be overwritten by make codegen. All modifications must be made to the original source file that generates this sync version.

As per coding guidelines:

Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Please revert changes to this file and apply them to the source file instead (likely in the async/aio version or the generation script).

♻️ Duplicate comments (1)
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1)

46-46: Past review concern has been correctly addressed

The isinstance(origin, type) guard has been properly added to prevent TypeError when get_origin() returns non-class types like typing.Union or types.UnionType. The fix matches the recommended solution.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edf6f23 and 95e2e56.

📒 Files selected for processing (9)
  • AGENTS.md (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (3 hunks)
  • key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (3 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/valkey/store.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1 hunks)
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow Ruff formatting and linting with a configured maximum line length of 140 characters (per pyproject.toml)
Use Basedpyright in strict mode for static type checking across the Python codebase

Files:

  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/valkey/store.py
  • key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py
  • key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py
  • key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py
key-value/key-value-sync/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Files:

  • key-value/key-value-sync/src/key_value/sync/code_gen/stores/valkey/store.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py
  • key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Markdown files must pass markdownlint with an 80-character line length limit (excluding code blocks and tables)

Files:

  • AGENTS.md
key-value/key-value-aio/src/key_value/aio/stores/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

key-value/key-value-aio/src/key_value/aio/stores/**/*.py: Async store implementations must implement the AsyncKeyValue protocol from key_value/aio/protocols/key_value.py
Store backends must store and retrieve ManagedEntry wrappers (with value, creation timestamp, and TTL), not raw values

Files:

  • key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py
🧠 Learnings (2)
📚 Learning: 2025-10-26T13:44:25.428Z
Learnt from: CR
PR: strawgate/py-key-value#0
File: AGENTS.md:0-0
Timestamp: 2025-10-26T13:44:25.428Z
Learning: Applies to key-value/key-value-sync/** : Do not edit files in key-value/key-value-sync/ directly; this package is generated and will be overwritten by make codegen

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-26T13:44:25.428Z
Learnt from: CR
PR: strawgate/py-key-value#0
File: AGENTS.md:0-0
Timestamp: 2025-10-26T13:44:25.428Z
Learning: Develop async-first: make changes in the async package and regenerate the sync package with make codegen

Applied to files:

  • AGENTS.md
🧬 Code graph analysis (8)
key-value/key-value-sync/src/key_value/sync/code_gen/stores/valkey/store.py (1)
key-value/key-value-aio/src/key_value/aio/stores/dynamodb/store.py (1)
  • _connected_client (133-137)
key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py (1)
key-value/key-value-aio/src/key_value/aio/stores/dynamodb/store.py (1)
  • _connected_client (133-137)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (4)
key-value/key-value-aio/src/key_value/aio/stores/base.py (9)
  • get (111-137)
  • get_many (140-145)
  • keys (290-296)
  • ttl (148-157)
  • ttl_many (160-175)
  • put (193-204)
  • put_many (222-239)
  • delete (258-262)
  • delete_many (265-270)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (10)
  • RoutingWrapper (15-104)
  • _get_store (50-62)
  • get (65-67)
  • get_many (70-72)
  • ttl (75-77)
  • ttl_many (80-82)
  • put (85-87)
  • put_many (90-94)
  • delete (97-99)
  • delete_many (102-104)
key-value/key-value-aio/tests/stores/wrappers/test_routing.py (3)
  • default_store (32-33)
  • store (36-46)
  • store (127-134)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (3)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (12-110)
  • get (66-68)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (2)
  • CollectionRoutingWrapper (11-47)
  • route_by_collection (41-45)
key-value/key-value-sync/src/key_value/sync/code_gen/adapters/pydantic/adapter.py (1)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (2)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (2)
  • RoutingWrapper (15-104)
  • get (65-67)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (2)
  • CollectionRoutingWrapper (8-51)
  • route_by_collection (42-46)
key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (3)
key-value/key-value-sync/src/key_value/sync/code_gen/protocols/key_value.py (1)
  • KeyValue (169-174)
key-value/key-value-aio/src/key_value/aio/wrappers/routing/wrapper.py (10)
  • RoutingWrapper (12-110)
  • _get_store (51-63)
  • get (66-68)
  • get_many (71-73)
  • ttl (76-78)
  • ttl_many (81-83)
  • put (86-88)
  • put_many (91-100)
  • delete (103-105)
  • delete_many (108-110)
key-value/key-value-sync/tests/code_gen/stores/wrappers/test_routing.py (3)
  • default_store (35-36)
  • store (39-49)
  • store (124-127)
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1)
key-value/key-value-aio/src/key_value/aio/protocols/key_value.py (1)
  • AsyncKeyValue (176-181)
🪛 LanguageTool
AGENTS.md

[style] ~109-~109: Consider shortening or rephrasing this to strengthen your wording.
Context: ...nerate. Always run make codegen after making changes to the async package. You will need to inc...

(MAKE_CHANGES)

🔇 Additional comments (8)
key-value/key-value-aio/src/key_value/aio/adapters/pydantic/adapter.py (1)

18-22: LGTM: Explicit attribute declarations improve type safety

These explicit private attribute declarations enable strict type checking and prevent attribute-not-found errors in Basedpyright strict mode.

key-value/key-value-aio/src/key_value/aio/wrappers/routing/collection_routing.py (1)

1-51: LGTM! Clean implementation with past feedback addressed.

The CollectionRoutingWrapper implementation correctly:

  • Accepts Mapping[str, AsyncKeyValue] for flexibility (addresses past feedback)
  • Wraps the collection map in MappingProxyType for defensive immutability (addresses past feedback)
  • Returns None from the routing function for unmapped collections, letting the base RoutingWrapper handle the default store fallback (addresses past feedback per commit 7b1fa21)
  • Follows the same pattern as the sync version

The implementation is type-safe, defensive, and provides a clear convenience layer over the base RoutingWrapper.

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

1-9: LGTM! Clear imports and type alias.

The RoutingFunction type alias clearly documents the routing function signature, making the API easier to understand and use.


12-49: LGTM! Clear class design with past feedback addressed.

The class docstring and constructor are well-documented. The docstring correctly describes collection-based routing without implying key-based routing capabilities (addresses past feedback per commit 11f21cd). The constructor cleanly stores the routing function and default store without unnecessary validation.


51-63: LGTM! Clean routing resolution logic.

The _get_store helper correctly resolves the target store using the routing function with a fallback to the default store. The docstring accurately describes the behavior without the previously mentioned ValueError (addresses past feedback).


65-110: LGTM! Complete and correct protocol implementation.

All AsyncKeyValue protocol methods are properly implemented with:

  • Consistent @override decorators
  • Correct await usage on all async store operations
  • Proper parameter forwarding including collection and ttl
  • Accurate return type handling for each operation

The delegation pattern is clean and straightforward, ensuring all operations are routed to the appropriate store without transformation or interference.

key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/collection_routing.py (1)

1-47: Auto-generated sync code is correct.

This file is correctly auto-generated from the async source. The implementation properly adapts CollectionRoutingWrapper for synchronous use: it wraps the collection map in an immutable MappingProxyType and defines routing logic that delegates to the base RoutingWrapper. The example correctly shows synchronous usage without await.

As per coding guidelines, any future changes must be made to the upstream async source file and regenerated via make codegen.

key-value/key-value-sync/src/key_value/sync/code_gen/wrappers/routing/wrapper.py (1)

1-104: Auto-generated sync code is correct.

This file is correctly auto-generated from the async source. The RoutingWrapper implementation properly:

  • Defines RoutingFunction type alias for routing logic
  • Stores routing function and default store
  • Implements _get_store helper with fallback to default_store when routing returns None
  • Delegates all KeyValue protocol methods to the selected store with proper parameter forwarding

The docstring correctly documents that the routing function receives only the collection name (line 18), matching the type signature, and the fallback behavior to default_store is intentional per the constructor documentation (line 45).

As per coding guidelines, any future changes must be made to the upstream async source file and regenerated via make codegen.

Comment on lines +109 to +112
async package and regenerate. Always run `make codegen` after making changes
to the async package. You will need to include the generated code in your pull
request. Nobody will generate it for you. This also means pull requests will contain
two copies of your changes, this is intentional!
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix line length constraint violation in Markdown.

Line 111 exceeds the 80-character line length limit (currently 85 characters). Additionally, the phrasing can be tightened to avoid redundancy per LanguageTool's suggestion on line 109.

Refactor the paragraph to respect the 80-character constraint while strengthening wording:

 ### Sync Package is Generated
 
 **Never edit files in `key-value/key-value-sync/` directly**. Any changes
 will be overwritten when `make codegen` runs. Always make changes in the
-async package and regenerate. Always run `make codegen` after making changes
-to the async package. You will need to include the generated code in your pull
-request. Nobody will generate it for you. This also means pull requests will contain
-two copies of your changes, this is intentional!
+async package and run `make codegen` to regenerate. Include the generated
+code in your pull request—it will not be generated for you. PRs will
+intentionally contain two copies of your changes.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async package and regenerate. Always run `make codegen` after making changes
to the async package. You will need to include the generated code in your pull
request. Nobody will generate it for you. This also means pull requests will contain
two copies of your changes, this is intentional!
async package and run `make codegen` to regenerate. Include the generated
code in your pull request—it will not be generated for you. PRs will
intentionally contain two copies of your changes.
🧰 Tools
🪛 LanguageTool

[style] ~109-~109: Consider shortening or rephrasing this to strengthen your wording.
Context: ...nerate. Always run make codegen after making changes to the async package. You will need to inc...

(MAKE_CHANGES)

🤖 Prompt for AI Agents
In AGENTS.md around lines 109 to 112, the paragraph about regenerating the async
package exceeds the 80-char limit and is slightly redundant; reword and wrap the
text into shorter lines so no line is over 80 characters, tighten phrasing by
removing redundancy (e.g., replace the current three lines with a concise
sentence such as: after making changes to the async package run `make codegen`
and include the generated code in your PR; pull requests will intentionally
contain two copies of your changes), and ensure line breaks occur at or before
80 characters.

Comment on lines +119 to 121
if self._connected_client is None:
return
await self._client.close()
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM! Good defensive guard to prevent close errors.

The None check correctly prevents attempting to close an uninitialized client, which could happen if _close() is called before _setup() or if the store is torn down without ever connecting.

Optional style improvement:

For consistency, consider calling close() directly on _connected_client instead of going through the _client property, since you've already verified it's not None:

 async def _close(self) -> None:
     if self._connected_client is None:
         return
-    await self._client.close()
+    await self._connected_client.close()

This makes the code more direct and maintains consistency with the guard check.

🤖 Prompt for AI Agents
In key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py around lines
119 to 121, the guard correctly checks self._connected_client is not None but
then calls await self._client.close(); update the call to await
self._connected_client.close() to be consistent with the guard and avoid any
indirection, making sure the attribute exists and is awaited the same way as
before.

Comment on lines 114 to 117
def _close(self) -> None:
if self._connected_client is None:
return
self._client.close()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Do not edit generated files directly.

This file is auto-generated (as stated in lines 1-3) and located in key-value/key-value-sync/, which the coding guidelines explicitly prohibit editing directly. Any changes made here will be overwritten by make codegen.

The safe-close guard logic is correct, but this change must be applied to the original source file (likely in key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py) and then regenerated using the build process.

As per coding guidelines.

🤖 Prompt for AI Agents
In key-value/key-value-sync/src/key_value/sync/code_gen/stores/valkey/store.py
around lines 114 to 117, do not edit this generated file directly; the change
(ensure safe-close uses self._connected_client guard before calling
self._client.close()) must be applied in the original source (likely
key-value/key-value-aio/src/key_value/aio/stores/valkey/store.py) — update the
corresponding _close implementation there to include the guard, then run the
project codegen (make codegen) to regenerate the sync file so the fix is
preserved by the build process.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
key-value/key-value-aio/tests/stores/memcached/test_memcached.py (2)

22-32: Narrow exception scope in ping_memcached to avoid masking real errors.

Catching Exception can hide non-connectivity bugs. Limit to connection/timeout errors.

 async def ping_memcached() -> bool:
     client = Client(host=MEMCACHED_HOST, port=MEMCACHED_PORT)
     try:
         await client.stats()
-    except Exception:
+    except (OSError, TimeoutError):
         return False
     else:
         return True
     finally:
         with contextlib.suppress(Exception):
             await client.close()

43-49: Replace magic number with defined constant for consistency.

The constant WAIT_FOR_MEMCACHED_TIMEOUT = 30 is already defined at line 19; use it instead of the hardcoded tries=30 on line 45. The port mapping {"11211": MEMCACHED_PORT} is correct per docker-py semantics and requires no changes.

-            if not await async_wait_for_true(bool_fn=ping_memcached, tries=30, wait_time=1):
+            if not await async_wait_for_true(
+                bool_fn=ping_memcached, tries=WAIT_FOR_MEMCACHED_TIMEOUT, wait_time=1
+            ):
                 msg = "Memcached failed to start"
                 raise MemcachedFailedToStartError(msg)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95e2e56 and 6de4d12.

📒 Files selected for processing (1)
  • key-value/key-value-aio/tests/stores/memcached/test_memcached.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow Ruff formatting and linting with a configured maximum line length of 140 characters (per pyproject.toml)
Use Basedpyright in strict mode for static type checking across the Python codebase

Files:

  • key-value/key-value-aio/tests/stores/memcached/test_memcached.py

# Memcached test configuration
MEMCACHED_HOST = "localhost"
MEMCACHED_PORT = 11211
MEMCACHED_IMAGE = "memcached:1.6"
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Pin the Memcached image (or allow override) to stabilize CI.

memcached:1.6 floats to the latest 1.6.x, which can change behavior over time. Pin a patch version or digest, and optionally allow an env override for local runs.

+import os
@@
-MEMCACHED_IMAGE = "memcached:1.6"
+# Allow overriding in CI/local; default remains a stable tag (adjust as needed).
+MEMCACHED_IMAGE = os.getenv("TEST_MEMCACHED_IMAGE", "memcached:1.6")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
MEMCACHED_IMAGE = "memcached:1.6"
import os
# ... (other imports and code)
# Allow overriding in CI/local; default remains a stable tag (adjust as needed).
MEMCACHED_IMAGE = os.getenv("TEST_MEMCACHED_IMAGE", "memcached:1.6")
🤖 Prompt for AI Agents
In key-value/key-value-aio/tests/stores/memcached/test_memcached.py around line
17, the test sets MEMCACHED_IMAGE = "memcached:1.6" which floats to the latest
1.6.x; change this to a pinned image (either a specific patch tag like
"memcached:1.6.17" or a digest like "memcached@sha256:...") and add an optional
environment variable override (e.g., read MEMCACHED_IMAGE from os.environ if
present) so CI is stable but local runs can override the image.

@strawgate strawgate merged commit 8151b4f into main Oct 26, 2025
79 of 80 checks passed
@strawgate strawgate deleted the claude/issue-114-20251026-0414 branch October 26, 2025 16:57
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.

2 participants