Helpers for searching, sorting and pagination for SQLAlchemy SELECT queries
Project description
sqlalchemy-querytools
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
Table of contents
- Purpose
- Install
- Quick example
- Public API
- Behavior details
- Examples
- Tests
- Notes / gotchas
- Contributing
- License
Purpose
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.
Install
If published to PyPI:
pip install sqlalchemy-querytools
Or install locally:
pip install -e .
Requirements: SQLAlchemy (version compatible with your project). For running tests: pytest.
Quick example
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()
Public API
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)
Behavior details
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.
Examples
Search (ALL_FIELDS)
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 (DATE_FIELDS)
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.fromisoformat
Sort (ASC / DESC) and null placement
from 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.
Pagination
from sqlalchemy_querytools import PaginationQuery
page = PaginationQuery(select(User), offset=20, limit=10)
stmt = page.build()
rows = session.execute(stmt).scalars().all()
Compose: search + sort + paginate
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()
Tests
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 -q
Notes / gotchas
ilikeand 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.
Contributing
- 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.
License
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...
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sqlalchemy_querytools-0.1.2.tar.gz.
File metadata
- Download URL: sqlalchemy_querytools-0.1.2.tar.gz
- Upload date:
- Size: 6.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91cff67caf5cce35dc95797ebcf229a52880c4acd5425f7c0fd699802415b639
|
|
| MD5 |
ea904122b9a64bd790e863c82851754b
|
|
| BLAKE2b-256 |
30448e13de1ee71a232e51b5284b6606b95c1e1ae85032efe8a77b377ffa3318
|
File details
Details for the file sqlalchemy_querytools-0.1.2-py3-none-any.whl.
File metadata
- Download URL: sqlalchemy_querytools-0.1.2-py3-none-any.whl
- Upload date:
- Size: 8.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8254f5961ec61ecdcc601aca69c92cef9b02b5abdd09f9dadb7993d88e3924bc
|
|
| MD5 |
7cbac0d64d454084c5b33bb0fdc2446f
|
|
| BLAKE2b-256 |
c1b4effe48b77b4e2f5a0277753b8b693ec767e43b6b620a57bbe9246c3b220a
|