Lightweight helpers to build SQLAlchemy SELECT queries with common list-table features: search, sort, and *
pagination*.
Each tool accepts a sqlalchemy.Select and returns a modified Select. Designed to be composed — ideal for APIs, CRUD
endpoints and list pages.
Version: 0.1.0
License: MIT
- Purpose
- Install
- Quick example
- Public API
- Behavior details
- Examples
- Tests
- Notes / gotchas
- Contributing
- License
sqlalchemy-querytools provides three small, composable builders that make it easy to add common list-table features to
SQLAlchemy queries:
SearchQuery— buildsWHEREclauses for text/date searching across model fields.SortQuery— buildsORDER BYwith ASC/DESC and controls null placement.PaginationQuery— appliesLIMITandOFFSET.
Each builder returns a sqlalchemy.Select that you then execute with your session.
If published to PyPI:
pip install sqlalchemy-querytoolsOr install locally:
pip install -e .Requirements: SQLAlchemy (version compatible with your project). For running tests: pytest.
from sqlalchemy import select
from sqlalchemy.orm import sessionmaker
from sqlalchemy_querytools import SearchQuery, SortQuery, PaginationQuery
from sqlalchemy_querytools.types import SearchType, SortType
# assume engine, Session, and User model are already defined
session = Session()
table_fields = {
"name": User.name,
"email": User.email,
"created_at": User.created_at,
}
stmt = select(User)
# search -> sort -> paginate
stmt = SearchQuery(stmt, SearchType.ALL_FIELDS, table_fields, "alice").build()
stmt = SortQuery(stmt, SortType.ASC, table_fields, "name", null_last=False).build()
stmt = PaginationQuery(stmt, offset=0, limit=20).build()
users = session.execute(stmt).scalars().all()Exports (from __all__):
SearchQuery(stmt: Select, query_tool_type: SearchType, table_fields_relation: TableFields, search_value: str).build() -> Select
SortQuery(stmt: Select, query_tool_type: SortType, table_fields_relation: TableFields, sort_field: str, null_last: bool = False).build() -> Select
PaginationQuery(stmt: Select, offset: int, limit: int).build() -> Select
SearchType— enum values:ALL_FIELDS,DATE_FIELDSSortType— enum values:ASC,DESCTableFields—dict[str, InstrumentedAttribute](mapping names → SQLAlchemy attributes)
SearchQuery
SearchType.ALL_FIELDS- Builds
ilike('%value%')for each mapped field. - If a mapped field is non-text, the implementation casts to
Stringbefore applyingilike. - If
search_valueis empty or whitespace, no filters are added and the originalSelectis returned.
- Builds
SearchType.DATE_FIELDS- Expects
search_valueparseable viadatetime.fromisoformat(e.g."2020-01-01"), converts todateand compares equality againstDate/DateTimefields. - Invalid date string →
ValueErrorfromdatetime.fromisoformat.
- Expects
- Unknown
SearchType→TypeError("Uncorrected search type").
SortQuery
- Looks up
sort_fieldintable_fields_relation. Missing key →KeyError. SortType.ASC→asc(field);SortType.DESC→desc(field).null_last=Trueuses.nullslast(), otherwise.nullsfirst().- Unknown
SortType→TypeError("Uncorrected sort type").
PaginationQuery
- Applies
.limit(limit).offset(offset)on theSelect.
from sqlalchemy_querytools import SearchQuery
from sqlalchemy_querytools.types import SearchType
stmt = select(User)
search = SearchQuery(stmt, SearchType.ALL_FIELDS, {"name": User.name, "email": User.email}, "ann")
result = session.execute(search.build()).scalars().all()search = SearchQuery(select(User), SearchType.DATE_FIELDS, {"created_at": User.created_at}, "2020-01-01")
rows = session.execute(search.build()).scalars().all()
# Note: the date string must be ISO-like and parseable by datetime.fromisoformatfrom sqlalchemy_querytools import SortQuery
from sqlalchemy_querytools.types import SortType
# ascending, NULLs first (default)
q = SortQuery(select(User), SortType.ASC, {"name": User.name}, "name", null_last=False)
# descending, NULLs last
q2 = SortQuery(select(User), SortType.DESC, {"name": User.name}, "name", null_last=True)If sort_field is not in table_fields_relation, _use_extension() will raise KeyError — this makes invalid input
explicit.
from sqlalchemy_querytools import PaginationQuery
page = PaginationQuery(select(User), offset=20, limit=10)
stmt = page.build()
rows = session.execute(stmt).scalars().all()stmt = select(User)
stmt = SearchQuery(stmt, SearchType.ALL_FIELDS, table_fields, "alice").build()
stmt = SortQuery(stmt, SortType.ASC, table_fields, "name", null_last=True).build()
stmt = PaginationQuery(stmt, offset=0, limit=25).build()
rows = session.execute(stmt).scalars().all()Project includes pytest tests which demonstrate expected behaviors:
- Case-insensitive partial search
- Unicode / Cyrillic handling
- Special characters and emoji support
- Date searching behavior (including tests that skip if implementation raises
ValueError) - Sorting behavior with
NULLvalues
Run tests:
pip install -r requirements_dev.txt # sqlalchemy, pytest, etc.
pytest -qilikeand case-insensitivity depend on your DB backend and column collations.SearchQuerycasts non-text fields toStringforALL_FIELDSsearch; ensuretable_fields_relationlists only intended columns to search.- Date searching uses
datetime.fromisoformat; invalid input raisesValueError. Tests are aware of this behavior. - Tools return
Selectobjects; you must execute them with your session (e.g.session.execute(stmt).scalars().all()). SortQueryintentionally raisesKeyErrorfor missing fields to fail fast with user-provided sort parameters.
- Fork the repo.
- Create a branch for your change.
- Add tests for new behaviors or fixes.
- Open a pull request.
Please include tests for DB-sensitive behavior (e.g. collation, null ordering) where applicable.
This project is licensed under the MIT License — see LICENSE in the repository for the full text.
MIT License
Copyright (c) 2025
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...