Skip to content

feat: initial support for dogpile caching#636

Merged
cofin merged 10 commits into
litestar-org:mainfrom
cofin:feat/dogpile
Jan 18, 2026
Merged

feat: initial support for dogpile caching#636
cofin merged 10 commits into
litestar-org:mainfrom
cofin:feat/dogpile

Conversation

@cofin

@cofin cofin commented Dec 15, 2025

Copy link
Copy Markdown
Member

Introduce support for dogpile caching, including a new caching configuration and a null region implementation for scenarios where caching is disabled or dogpile.cache is not installed. Add unit tests to validate the caching behavior and configuration options.

@codecov-commenter

codecov-commenter commented Dec 15, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.21901% with 327 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.09%. Comparing base (298b4ce) to head (f76ebe3).
⚠️ Report is 34 commits behind head on main.

Files with missing lines Patch % Lines
advanced_alchemy/repository/_sync.py 45.79% 117 Missing and 12 partials ⚠️
advanced_alchemy/cache/manager.py 65.64% 82 Missing and 8 partials ⚠️
advanced_alchemy/repository/_async.py 66.80% 52 Missing and 27 partials ⚠️
advanced_alchemy/_listeners.py 78.49% 13 Missing and 7 partials ⚠️
advanced_alchemy/cache/serializers.py 94.25% 3 Missing and 2 partials ⚠️
advanced_alchemy/cache/_null.py 81.81% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #636      +/-   ##
==========================================
- Coverage   80.92%   79.09%   -1.83%     
==========================================
  Files          94       99       +5     
  Lines        6977     7861     +884     
  Branches      910     1065     +155     
==========================================
+ Hits         5646     6218     +572     
- Misses       1042     1301     +259     
- Partials      289      342      +53     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

cofin added 3 commits January 17, 2026 21:59
- Remove banned `from __future__ import annotations` imports
- Fix union syntax to use Optional/Union instead of X | Y
- Add proper docstrings replacing inline comments
- Move non-circular imports to top-level
- Add cache module documentation (usage guide and API reference)
- Quote forward references for TYPE_CHECKING imports
- Add missing use_cache parameter to get() method signature
- Add bind_group documentation to list() and list_and_count() docstrings
- Add missing TYPE_CHECKING imports for AsyncSession, scoped_session,
  async_scoped_session, and CacheManager in _listeners.py
- Add __init__.py to tests/unit/test_cache/ directory
cofin added 7 commits January 17, 2026 22:32
- Add SyncCacheRegionProtocol for sync cache region interface
- Add AsyncCacheRegionProtocol for async cache region interface
- Add NO_VALUE sentinel to _null.py exports
- Add _cache_manager class attribute to repository classes
- Fix pyright/mypy ignore comment placement on dogpile imports
- Update manager.py to use SyncCacheRegionProtocol
… operations and introduce integration tests.
…` imports and reformat long lines in cache integration tests for readability.
…e` extra, including its transitive dependencies `decorator` and `stevedore`.
@cofin
cofin merged commit 8916358 into litestar-org:main Jan 18, 2026
19 checks passed
cofin pushed a commit to hasansezertasan/advanced-alchemy that referenced this pull request May 23, 2026
Closes litestar-org#730.

The dogpile.cache integration (litestar-org#636) added the `advanced_alchemy.cache`
package, listeners, and repository wiring, but never extended
`SQLAlchemyAsyncConfig` / `SQLAlchemySyncConfig` with the `cache_config`
kwarg that the caching guide promotes. Every example in
`docs/usage/caching.rst` raised `TypeError` because the field did not
exist.

This adds `cache_config: Optional[CacheConfig]` and
`cache_manager: Optional[CacheManager]` to `GenericSQLAlchemyConfig`.
`__post_init__` builds a `CacheManager` from `cache_config` when one is
not supplied explicitly, then publishes it to sessions via
`session_config.info["cache_manager"]` so repositories pick it up
through the existing `session.info.get("cache_manager")` path.

`session_config` and its `info` dict are now shallow-copied during
`__post_init__` so two configs constructed with the same
`session_config` instance no longer alias each other's cache_manager or
`file_object_raise_on_error` flag. `__hash__` also includes
`id(cache_manager)` so configs that differ only by cache region remain
distinguishable in the class-level registries.

Imports of `CacheConfig` / `CacheManager` are lazy: only typed under
TYPE_CHECKING at module load, and only imported at runtime inside
`__post_init__` when `cache_config` is set, so dogpile.cache probing
is still avoided for configs that don't opt in.
cofin added a commit that referenced this pull request May 23, 2026
Closes #730.

## Summary

- `SQLAlchemyAsyncConfig` and `SQLAlchemySyncConfig` now accept
`cache_config: Optional[CacheConfig]` and `cache_manager:
Optional[CacheManager]` kwargs, closing the documentation/implementation
gap from #636.
- When `cache_config` is set, `__post_init__` instantiates a
`CacheManager` (unless one was passed explicitly) and publishes it to
sessions via `session_config.info["cache_manager"]`. Repositories pick
it up automatically through the existing
`session.info.get("cache_manager")` lookup in `repository/_async.py` and
`repository/_sync.py`.
- `session_config` and its `info` dict are shallow-copied during
`__post_init__`, so two configs constructed with the same
`session_config` instance no longer alias each other's `cache_manager`
or `file_object_raise_on_error` flag.
- `__hash__` now includes `id(cache_manager)` so configs that differ
only by cache region remain distinguishable in the class-level
engine/session registries.
- `CacheConfig` / `CacheManager` stay behind `TYPE_CHECKING` at module
load and are imported at runtime only when `cache_config` is set, so
dogpile.cache probing is still avoided for users who don't opt in.

After this change the "Quick Start" and "Repository Integration"
examples in `docs/usage/caching.rst` work as documented:

```python
db_config = SQLAlchemyAsyncConfig(
    connection_string="sqlite+aiosqlite:///app.db",
    cache_config=CacheConfig(
        backend="dogpile.cache.memory",
        expiration_time=300,
    ),
)
async with db_config.get_session() as session:
    repo = UserRepository(session=session, auto_expunge=True)
    user = await repo.get(user_id)  # DB + cache populate
    user = await repo.get(user_id)  # cache hit (fresh deserialized instance)
```

> [!NOTE]
> `auto_expunge=True` on the repository is required for cache hits to
round-trip cleanly across consecutive calls — without it, a cached
entity attached to a closed session triggers `MissingGreenlet` on
attribute refresh in async usage. This matches the existing "Recommended
with caching" guidance in the caching guide; the Quick Start example in
the docs should likely be updated to show it inline (follow-up).

## Test plan

- [x] Repro from the issue body runs without raising on both async and
sync configs.
- [x] New unit tests in
`tests/unit/test_config/test_{async,sync}_config.py`:
- `cache_config` builds a manager and propagates it to
`session_config.info`.
- An explicit `cache_manager` overrides `cache_config` and is preserved
by identity.
  - Without `cache_config`, no `cache_manager` key is published.
- Two configs sharing a `session_config` do not clobber each other's
`cache_manager` / `file_object_raise_on_error` and keep user-supplied
`info` keys.
- `__hash__` distinguishes configs that differ only by `cache_manager`.
- [x] New integration tests in
`tests/integration/test_cache_repository.py` covering both sync and
async paths:
- `config.get_session()` + repository, populate cache, delete the
underlying DB row, second `repo.get()` in a fresh session succeeds —
proving the served value comes from the cache region rather than the
database.
- Documented Quick Start flow: two consecutive `repo.get()` calls in one
session, second call is served from cache (fresh deserialized instance,
identity differs).
- [x] Existing cache/config/routing suites: 253 passed, 1 skipped
(pre-existing `dogpile.cache`-absent gate).

<!-- docs-preview -->

<hr>
📚 Documentation preview: <a
href="https://litestar-org.github.io/advanced-alchemy-docs-preview/731">https://litestar-org.github.io/advanced-alchemy-docs-preview/731</a>

---------

Co-authored-by: Cody Fincher <cody@litestar.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants