Merged
Conversation
…n StatementFilters
…lter.like_pattern
#429 Wraps up the cluster of issues filed alongside this branch: - #425: SearchFilter/NotInSearchFilter now raise TypeError for unsupported field_name types (was silently dropping the WHERE clause). The same dispatch path now also accepts exp.Expression, finishing the type-widening promised in #427 for the LIKE-bearing filters. - #426: QueryBuilder.order_by(sql.raw("COL DESC")) emits ORDER BY ... DESC by unwrapping a trailing exp.Alias whose alias name is asc/desc into an exp.Ordered with the right direction. - #428: Adds SearchFilter.escape_like_value() static helper for safely escaping %, _, and \ in user-supplied LIKE input (opt-in; like_pattern itself stays unchanged for backwards compatibility). - #429: Fleshes out SQLSpecAsyncService / SQLSpecSyncService with get_or_404 (raising sqlspec.exceptions.NotFoundError), explicit begin/commit/rollback methods, and a begin_transaction async/sync context manager. paginate now uses driver.find_filter to recover limit/offset, matching the docs recipe. Both classes are re-exported from sqlspec.extensions.{litestar,fastapi,starlette,sanic} (flask re-exports the sync class only).
…Flask
- _common.py: switch `import abc` to `from collections import abc` so the
`abc.Sequence` annotation actually resolves (mypy CI failure).
- core/filters.py: annotate `parsed = exp.maybe_parse(...)` so mypy can
narrow the union (was reported as `var-annotated`).
- service.py: import `StatementParameters` from `sqlspec.typing` (the real
home) rather than the non-existent `sqlspec.core.parameters`. Add a
`Yields:` section to the begin_transaction docstrings on both bases.
- extensions/{litestar,fastapi}/providers.py: type the SearchFilter
field-name set as `set[str | exp.Expression]` so the variance widening
introduced by issue #427 lines up with the providers.
- extensions/flask: re-export `SQLSpecAsyncService` alongside the sync
base. Flask's plugin can portal an async driver, so async-service
consumers need the symbol from the framework namespace they already
import from.
The previous push only ran the type-checkers against `sqlspec/`, missing
tests/, where most of the failures lived. Running `make lint` (the
project's canonical pre-merge gate) surfaced 17 pyright errors and 5 mypy
errors. This commit closes them all:
- tests/unit/core/test_filters.py: hoist `from sqlglot import exp` and
`from typing import Any` to module scope; type
`set[str | exp.Expression]` for the multi-field SearchFilter test;
switch the expression-mode tests from `exp.func("LOWER", ...)` (whose
`Func` return type is reported as not assignable to `Expression`) to the
concrete `exp.Lower` / `exp.Coalesce` / `exp.Upper` constructors;
annotate the invalid-type fixtures with `Any` so the deliberate
TypeError-trigger calls type-check.
- tests/unit/builder/test_select_builder.py: `cast` `sql.raw(...)` to
`exp.Ordered` (the contract `order_by` accepts) and add `expr is not
None` guards before reading the optional builder expression.
- tests/integration/extensions/litestar/test_in_fields_filters.py:
guard `paths["/ordered"].get` (Operation | None) and switch the
parameter lookup to `getattr(p, "name", None)` so the OpenAPI Reference
branch of `Parameter | Reference` doesn't trip pyright.
ec2c743 to
8b8cdf3
Compare
…otFoundError 404 handler in Litestar SQLSpecAsyncService / SQLSpecSyncService were typed against an unbounded DriverT, forcing `session: Any = self.session` escape hatches in 12 places. Bound the TypeVars to AsyncDriverAdapterBase / SyncDriverAdapterBase with PEP 696 defaults so driver methods (find_filter, select_with_total, select_one_or_none, begin/commit/rollback) type-check directly. Renamed `get_or_404` to `get_one` (hard rename — never released). The `_404` suffix promised HTTP semantics the framework-agnostic core could not deliver: nothing translated NotFoundError to a 404 response, even on Litestar. Restored the implied behavior in the Litestar plugin only: on_app_init now setdefaults a NotFoundError -> NotFoundException(detail=str(exc)) handler on app_config.exception_handlers, so user-supplied handlers win and the standard Litestar exception-handler chain (including any RFC 7807 customization) renders the 404. Added unit tests covering the registration, override precedence, and end-to-end 404 response. Bumps version to 0.45.0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes a cluster of filter-correctness issues filed 2026-04-26 and adds the
first-party service base classes that downstream consumers have been
re-implementing.
StatementFilters now emit qualified columns; dottedfield_name="users.name"works withoutAmbiguousColumnErroron joinedSELECTs and the count-query path.
SearchFilter/NotInSearchFilternow raiseTypeErrorfor unsupported
field_nametypes instead of silently dropping the WHEREclause, and accept
exp.Expression(e.g.LOWER(name)) as a sort/searchkey — finishing the type widening started for Feature: OrderByFilter accepts sqlglot expressions for sort key (e.g. COALESCE) #427.
QueryBuilder.order_by(sql.raw("COL DESC"))correctlyemits
ORDER BY ... DESCinstead of treatingDESCas a column alias.OrderByFilter.field_name(andSearchFilter.field_name)accept
exp.Expressionfor computed-column ordering/search; the wiresurface (
?orderBy=) staysstr-only.SearchFilter.like_patternand addsSearchFilter.escape_like_value()so consumers bypassing the standardfilter pipeline can opt into safe
%/_escaping.SQLSpecAsyncService/SQLSpecSyncServiceinsqlspec/service.py(
paginate,get_or_404,exists,begin/commit/rollback,begin_transaction) and re-exports them from every frameworkextension package. Flask re-exports the sync class only.