Skip to content

Commit e58b615

Browse files
authored
docs: overhaul docs (#696)
- Modularized Modeling and Repositories sections into nested directories. - Added new guides for SQLAlchemy Inheritance Patterns (STI, JTI, CTI). - Added new guide for SQLModel compatibility. - Updated documentation for v1.9.0b2 features: NullFilter, NotNullFilter, with_for_update, and recursive model_from_dict. - Integrated Sybil for automated testing of documentation code snippets.
1 parent 4f29b5a commit e58b615

34 files changed

Lines changed: 3227 additions & 2448 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,4 @@ GEMINI.md
189189
.agent/
190190
tools/scripts/detect_mcp_tools.py
191191
.geminiignore
192+
.agents/

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ repos:
2222
- id: unasyncd
2323
additional_dependencies: ["ruff"]
2424
- repo: https://github.com/charliermarsh/ruff-pre-commit
25-
rev: "v0.15.5"
25+
rev: "v0.15.7"
2626
hooks:
2727
# Run the linter.
2828
- id: ruff

Makefile

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ SHELL := /bin/bash
1010
.EXPORT_ALL_VARIABLES:
1111
MAKEFLAGS += --no-print-directory
1212

13+
DOC_TEST_FILES := \
14+
docs/usage/modeling/basics.rst \
15+
docs/usage/modeling/inheritance.rst \
16+
docs/usage/modeling/sqlmodel.rst \
17+
docs/usage/modeling/types.rst \
18+
docs/usage/repositories/advanced.rst \
19+
docs/usage/repositories/basics.rst \
20+
docs/usage/repositories/filtering.rst \
21+
docs/usage/database_seeding.rst \
22+
docs/usage/services.rst
23+
1324
# -----------------------------------------------------------------------------
1425
# Display Formatting and Colors
1526
# -----------------------------------------------------------------------------
@@ -142,16 +153,22 @@ clean: ## Cleanup temporary build a
142153
# Testing and Quality Checks
143154
# =============================================================================
144155

156+
.PHONY: docs-test
157+
docs-test: ## Run executable documentation examples
158+
@echo "${INFO} Running executable documentation examples... 📚"
159+
@uv run pytest $(DOC_TEST_FILES) --quiet
160+
@echo "${OK} Documentation examples passed ✨"
161+
145162
.PHONY: test
146163
test: ## Run the tests
147164
@echo "${INFO} Running test cases... 🧪"
148-
@uv run pytest --dist "loadgroup" -m "" tests -n 2 --quiet
165+
@uv run pytest --dist "loadgroup" -m "" -n 2 --quiet
149166
@echo "${OK} Tests passed ✨"
150167

151168
.PHONY: coverage
152169
coverage: ## Run tests with coverage report
153170
@echo "${INFO} Running tests with coverage... 📊"
154-
@uv run pytest tests --dist "loadgroup" -m "" --cov=advanced_alchemy --cov-report=xml -n 2 --quiet
171+
@uv run pytest --dist "loadgroup" -m "" --cov=advanced_alchemy --cov-report=xml -n 2 --quiet
155172
@uv run coverage html >/dev/null 2>&1
156173
@echo "${OK} Coverage report generated ✨"
157174

docs/PYPI_README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ offering:
3030
- Integration with major web frameworks including Litestar, Starlette, FastAPI, Sanic
3131
- Custom-built alembic configuration and CLI with optional framework integration
3232
- Utility base classes with audit columns, primary keys and utility functions
33+
- [SQLModel](https://sqlmodel.tiangolo.com/) compatibility — use `SQLModel` `table=True` models directly with repositories and services
34+
- Composite primary key support — work with multi-column primary keys across repositories, services, and bulk operations
35+
- Read/write replica routing with automatic query routing, round-robin/random replica selection, and sticky-primary mode
36+
- Dogpile caching integration for query result caching
3337
- Built in `File Object` data type for storing objects:
3438
- Unified interface for various storage backends ([`fsspec`](https://filesystem-spec.readthedocs.io/en/latest/) and [`obstore`](https://developmentseed.org/obstore/latest/))
3539
- Optional lifecycle event hooks integrated with SQLAlchemy's event system to automatically save and delete files as records are inserted, updated, or deleted.
@@ -43,7 +47,7 @@ offering:
4347
- Synchronous and asynchronous repositories featuring:
4448
- Common CRUD operations for SQLAlchemy models
4549
- Bulk inserts, updates, upserts, and deletes with dialect-specific enhancements
46-
- Integrated counts, pagination, sorting, filtering with `LIKE`, `IN`, and dates before and/or after.
50+
- Integrated counts, pagination, sorting, filtering with `LIKE`, `IN`, `IS NULL`/`IS NOT NULL`, and dates before and/or after.
4751
- Tested support for multiple database backends including:
4852
- SQLite via [aiosqlite](https://aiosqlite.omnilib.dev/en/stable/) or [sqlite](https://docs.python.org/3/library/sqlite3.html)
4953
- Postgres via [asyncpg](https://magicstack.github.io/asyncpg/current/) or [psycopg3 (async or sync)](https://www.psycopg.org/psycopg3/)

docs/changelog.rst

Lines changed: 226 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,228 @@
33
1.x Changelog
44
=============
55

6+
.. changelog:: 1.9.0
7+
:date: 2026-03-24
8+
9+
.. change:: add SQLModel compatibility
10+
:type: feature
11+
:pr: 686
12+
13+
SQLModel ``table=True`` models now work with Advanced Alchemy repositories and
14+
services without requiring Advanced Alchemy base classes. This release also adds
15+
``model_to_dict()`` and updates schema detection so mapped SQLModel objects are
16+
handled as ORM models instead of transfer schemas.
17+
18+
.. change:: add pre-release version support
19+
:type: feature
20+
:pr: 678
21+
22+
Adds PEP 440 pre-release support to the release workflow. ``bump-my-version``,
23+
``make pre-release``, and ``tools/prepare_release.py`` now understand alpha,
24+
beta, and release-candidate versions and can mark GitHub draft releases as
25+
prereleases.
26+
27+
.. change:: add composite primary key support
28+
:type: feature
29+
:pr: 640
30+
:issue: 189
31+
32+
Adds composite primary key support throughout the repository and service layers.
33+
Tuple and mapping primary-key inputs are now supported for lookup and delete
34+
operations, including MSSQL-compatible filtering for bulk operations.
35+
36+
Closes #189
37+
38+
.. change:: refactor serializers & code cleanup
39+
:type: feature
40+
:pr: 661
41+
:issue: 606, 651
42+
43+
Refactors repository and cache serialization helpers into shared utilities,
44+
reduces duplication in the repository layer, and fixes related issues around
45+
exception classification, optional Alembic imports, and DTO descriptor
46+
inspection.
47+
48+
.. change:: add support .csv files for open_fixture
49+
:type: feature
50+
:pr: 615
51+
:issue: 536
52+
53+
Add comprehensive CSV support to ``open_fixture()`` and
54+
``open_fixture_async()``, expanding fixture loading beyond JSON to include
55+
comma-separated value files.
56+
57+
Closes #536
58+
59+
.. change:: initial support for dogpile caching
60+
:type: feature
61+
:pr: 636
62+
63+
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.
64+
65+
66+
.. change:: add read/write replica routing support
67+
:type: feature
68+
:pr: 635
69+
70+
Adds automatic query routing between primary and replica database
71+
connections. ``RoutingSession`` and ``RoutingAsyncSession`` now route
72+
reads to replicas, keep writes on the primary, support explicit
73+
``primary_context()`` and ``replica_context()`` overrides, and integrate
74+
with the Litestar, FastAPI, Starlette, Flask, and Sanic extensions.
75+
76+
.. change:: add NullFilter/NotNullFilter and with_for_update to get_one methods
77+
:type: feature
78+
:pr: 638
79+
:issue: 187, 488, 623
80+
81+
Adds ``NullFilter`` and ``NotNullFilter`` for ``IS NULL`` and ``IS NOT
82+
NULL`` conditions, and extends ``get_one()`` and ``get_one_or_none()``
83+
in the repository and service layers with ``with_for_update`` support
84+
for API parity with ``get()``.
85+
86+
Closes #488
87+
Closes #623
88+
89+
.. change:: add `was_attribute_set()` guard to relationship loop in `update()`
90+
:type: bugfix
91+
:pr: 685
92+
93+
Fixes partial updates with model instances where SQLAlchemy-initialized
94+
relationship attributes could be mistaken for explicit values and
95+
written back to the database. The relationship update loop now uses the
96+
same ``was_attribute_set()`` guard as the column loop so only
97+
explicitly assigned relationship values are copied during ``update()``.
98+
99+
Closes #684
100+
101+
102+
.. change:: nullable relationship detection and FileObject nested metadata
103+
:type: bugfix
104+
:pr: 679
105+
:issue: 227, 676
106+
107+
Fixes nullable one-to-one DTO detection by correctly handling inverse
108+
scalar relationships, and fixes ``FileObject`` uploads by JSON
109+
serializing non-string obstore metadata values before they are passed to
110+
``put()``.
111+
112+
Closes #227
113+
Closes #676
114+
115+
.. change:: make `model_from_dict` model parameter positional-only
116+
:type: bugfix
117+
:pr: 673
118+
:issue: 668
119+
120+
Makes the ``model`` parameter positional-only in ``model_from_dict()``
121+
so payloads containing a ``"model"`` key do not conflict with the
122+
function signature. Service-layer call sites now use the positional
123+
form.
124+
125+
Closes #668
126+
127+
.. change:: use typing.List to avoid list() method shadowing on Python 3.14
128+
:type: bugfix
129+
:pr: 674
130+
:issue: 659
131+
132+
Replaces bare ``list[...]`` annotations with ``typing.List[...]`` in
133+
classes that also define a ``list()`` method. This avoids Python 3.14
134+
lazy annotation evaluation resolving ``list`` to the method instead of
135+
the builtin type.
136+
137+
Closes #659
138+
139+
.. change:: isolate in-filter query params for multi-field depende…
140+
:type: bugfix
141+
:pr: 667
142+
:issue: 666
143+
144+
Fixes a Litestar dependency collision where multiple ``*In`` query
145+
parameters generated by ``create_service_dependencies()`` could
146+
overwrite each other. Each generated in/not-in filter provider now
147+
binds its own query parameter name so fields like ``firstNameIn`` and
148+
``lastNameIn`` remain independent.
149+
150+
.. change:: Ensure ORM descriptor fields are not evaluated during DTO creation
151+
:type: bugfix
152+
:pr: 664
153+
:issue: 578, 646
154+
155+
Fix #646.
156+
157+
When processing property fields on SQLAlchemy models for the Litestar DTO, there was sometimes unexpected behaviour caused by the inspecting code evaluating the ORM descriptors such as `hybrid_property`.
158+
159+
Fix this behaviour by skipping all known ORM fields (as reported by the ORM), and not using `inspect.getmembers`.
160+
161+
.. change:: recursively convert nested dicts in model_from_dict
162+
:type: bugfix
163+
:pr: 637
164+
:issue: 556
165+
166+
Fixes a regression where ``service.create()`` failed when relationship
167+
data was provided as nested dictionaries. ``model_from_dict()`` now
168+
detects relationship attributes and recursively converts nested
169+
dictionaries or lists of dictionaries into the appropriate related model
170+
instances while preserving existing non-nested behavior.
171+
172+
Closes #556
173+
174+
.. change:: add click compatibility layer for CLI alias support
175+
:type: bugfix
176+
:pr: 645
177+
:issue: 644
178+
179+
Adds a Click compatibility layer so CLI alias support works with plain
180+
Click and with older ``rich-click`` versions that do not accept the
181+
``aliases`` parameter on command groups. The new helper provides
182+
``AliasedGroup`` and wrapper decorators used across the core, FastAPI,
183+
Flask, and Litestar CLI integrations.
184+
185+
.. change:: resolve session lifecycle timing with generator dependencies
186+
:type: bugfix
187+
:pr: 648
188+
:issue: 647
189+
190+
Fixes the FastAPI and Starlette session lifecycle conflict that could
191+
leave asyncpg connections unreturned to the pool when generator-based
192+
``provide_service()`` dependencies were cleaned up after middleware had
193+
already closed the session. Generator-managed sessions are now marked so
194+
middleware records response status but skips cleanup, allowing the
195+
generator to handle commit, rollback, and close at the correct time.
196+
197+
Closes #647
198+
199+
.. change:: complete SQLAlchemy inheritance pattern support (STI, JTI, CTI)
200+
:type: bugfix
201+
:pr: 611
202+
203+
Completes SQLAlchemy inheritance handling in ``CommonTableAttributes`` so
204+
single-table, joined-table, and concrete-table inheritance patterns are
205+
all supported correctly. The implementation now detects STI subclasses,
206+
suppresses table generation for them when appropriate, and preserves
207+
proper table naming behavior for joined and concrete inheritance models.
208+
209+
Supersedes PR #600
210+
211+
.. change:: add a call `set_async_context` to `_get_session_from_request` in Sanic extension
212+
:type: bugfix
213+
:pr: 643
214+
215+
Sanic now mirrors the other framework extensions by calling
216+
``set_async_context()`` in ``_get_session_from_request()`` after loading
217+
or creating the session. This keeps async-context detection consistent
218+
across integrations.
219+
220+
.. change:: linting changes related to latest Starlette
221+
:type: bugfix
222+
:pr: 634
223+
224+
Update the codebase to align with the latest changes in Starlette, ensuring compatibility and addressing type checking issues.
225+
226+
227+
6228
.. changelog:: 1.8.2
7229
:date: 2025-12-12
8230

@@ -113,8 +335,6 @@
113335

114336
Adds support for SQLAlchemy func() expressions in filter classes to eliminate type checker errors when using database functions like `func.random()` or `func.lower()`
115337

116-
**Changes**
117-
118338
- Updated `OrderBy`, `BeforeAfter`, `OnBeforeAfter`, `CollectionFilter`, `NotInCollectionFilter`, `ComparisonFilter`
119339
- Enhanced `_get_instrumented_attr()` to handle new types
120340
- Mock repositories extract field names from `InstrumentedAttribute`, raise helpful error for func expressions (can't execute SQL in-memory)
@@ -487,9 +707,10 @@
487707
:pr: 450
488708
:issue: 449
489709

490-
## fixes #449 relationship updated on models:
491-
- AuthorModel
492-
- BookModel
710+
Updates the ``litestar_service.py`` example models to correctly handle
711+
relationship updates for ``AuthorModel`` and ``BookModel``.
712+
713+
Fixes #449
493714

494715
.. change:: `create_service_provider` supports any configuration now
495716
:type: bugfix

docs/conftest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from collections.abc import AsyncGenerator
2+
3+
import pytest
4+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
5+
from sqlalchemy.orm import sessionmaker
6+
from sybil import Sybil
7+
from sybil.parsers.rest import PythonCodeBlockParser
8+
9+
EXECUTABLE_DOCS = (
10+
"usage/modeling/basics.rst",
11+
"usage/modeling/inheritance.rst",
12+
"usage/modeling/sqlmodel.rst",
13+
"usage/modeling/types.rst",
14+
"usage/repositories/advanced.rst",
15+
"usage/repositories/basics.rst",
16+
"usage/repositories/filtering.rst",
17+
"usage/database_seeding.rst",
18+
"usage/services.rst",
19+
)
20+
21+
NON_EXECUTABLE_DOCS = (
22+
"usage/caching.rst",
23+
"usage/cli.rst",
24+
"usage/frameworks/fastapi.rst",
25+
"usage/frameworks/flask.rst",
26+
"usage/frameworks/litestar.rst",
27+
"usage/frameworks/sanic.rst",
28+
"usage/frameworks/starlette.rst",
29+
"usage/routing.rst",
30+
)
31+
32+
33+
@pytest.fixture(name="engine")
34+
async def engine_fixture() -> AsyncGenerator[AsyncEngine, None]:
35+
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
36+
yield engine
37+
await engine.dispose()
38+
39+
40+
@pytest.fixture(name="db_session")
41+
async def db_session_fixture(engine: AsyncEngine) -> AsyncGenerator[AsyncSession, None]:
42+
async_session_factory: sessionmaker[AsyncSession] = sessionmaker(
43+
engine, class_=AsyncSession, expire_on_commit=False
44+
)
45+
async with async_session_factory() as session:
46+
yield session
47+
48+
49+
pytest_collect_file = Sybil(
50+
parsers=[PythonCodeBlockParser()],
51+
patterns=EXECUTABLE_DOCS,
52+
fixtures=["db_session", "engine"],
53+
).pytest()

docs/getting-started.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Getting Started
44

55
Advanced Alchemy is a carefully crafted, thoroughly tested, optimized companion library for :doc:`SQLAlchemy <sqlalchemy:index>`.
66

7-
It provides :doc:`base classes <reference/base>`, :doc:`mixins <reference/mixins/index>`, :doc:`custom column types <usage/types>`,
8-
and implementations of the :doc:`repository <usage/repositories>` and :doc:`service layer <usage/services>` patterns
7+
It provides :doc:`base classes <reference/base>`, :doc:`mixins <reference/mixins/index>`, :doc:`custom column types <usage/modeling/types>`,
8+
and implementations of the :doc:`repository <usage/repositories/index>` and :doc:`service layer <usage/services>` patterns
99
to simplify your database operations.
1010

1111
.. seealso:: It is built on:

0 commit comments

Comments
 (0)