fix: type the ids filter from its configured id type

advanced_alchemy annotates the `ids` query parameter as list[str] whatever the
config says, so a bigint primary key was compared against strings and Postgres
refused. Nothing had called ?ids= until now.
This commit is contained in:
2026-08-16 20:20:44 -04:00
parent 2e0d556c33
commit a48be517e4
2 changed files with 40 additions and 0 deletions
@@ -127,6 +127,31 @@ def create_book_filter_dependencies(
# Get base filters first # Get base filters first
filters = create_filter_dependencies(config, dep_defaults) filters = create_filter_dependencies(config, dep_defaults)
# OVERRIDE: id filter typed by the configured id type, not always `str`
#
# advanced_alchemy's `provide_id_filter` annotates `ids` as `list[str]` and
# ignores `config["id_filter"]` entirely, so `?ids=12` reaches the database as
# the string "12" and Postgres refuses `bigint = character varying`. Nothing
# called `?ids=` until the duplicates screen needed to fetch a handful of books
# by id, which is why it went unnoticed.
if id_type := config.get("id_filter"):
id_field = config.get("id_field", "id")
def provide_typed_id_filter(
ids=Parameter(query="ids", default=None, required=False),
) -> CollectionFilter:
return CollectionFilter(field_name=id_field, values=ids)
# Attached as a type object rather than written as an annotation: this module
# has `from __future__ import annotations`, so a written one is stored as the
# string "Optional[list[id_type]]" and resolved against module globals, where
# a local named `id_type` does not exist.
provide_typed_id_filter.__annotations__["ids"] = Optional[list[id_type]]
filters[dep_defaults.ID_FILTER_DEPENDENCY_KEY] = Provide(
provide_typed_id_filter, sync_to_thread=False
)
# OVERRIDE: Custom search filter with trigram search # OVERRIDE: Custom search filter with trigram search
if config.get("search"): if config.get("search"):
search_fields = config.get("search") search_fields = config.get("search")
+15
View File
@@ -209,6 +209,21 @@ async def test_get_book_file(
assert downloaded_content == file_content assert downloaded_content == file_content
async def test_list_books_by_id(populated_authenticated_client: AsyncClient) -> None:
"""
`?ids=` has to reach the database as integers.
advanced_alchemy's stock id filter annotates the parameter as `list[str]` whatever
the configured id type, so the ids arrived as strings and Postgres refused to
compare a bigint primary key against them. Nothing called it until a screen needed
to fetch a handful of books by id.
"""
response = await populated_authenticated_client.get("/books?ids=1&ids=2&pageSize=10")
assert response.status_code == 200
assert sorted(book["id"] for book in response.json()["items"]) == [1, 2]
async def test_get_book_by_id(populated_authenticated_client: AsyncClient) -> None: async def test_get_book_by_id(populated_authenticated_client: AsyncClient) -> None:
"""Test retrieving a specific book by ID.""" """Test retrieving a specific book by ID."""