Skip to main content

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

sqlalchemy-querytools provides three small, composable builders that make it easy to add common list-table features to SQLAlchemy queries:

  • SearchQuery — builds WHERE clauses for text/date searching across model fields.
  • SortQuery — builds ORDER BY with ASC/DESC and controls null placement.
  • PaginationQuery — applies LIMIT and OFFSET.

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_FIELDS
  • SortType — enum values: ASC, DESC
  • TableFieldsdict[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 String before applying ilike.
    • If search_value is empty or whitespace, no filters are added and the original Select is returned.
  • SearchType.DATE_FIELDS
    • Expects search_value parseable via datetime.fromisoformat (e.g. "2020-01-01"), converts to date and compares equality against Date/DateTime fields.
    • Invalid date string → ValueError from datetime.fromisoformat.
  • Unknown SearchTypeTypeError("Uncorrected search type").

SortQuery

  • Looks up sort_field in table_fields_relation. Missing key → KeyError.
  • SortType.ASCasc(field); SortType.DESCdesc(field).
  • null_last=True uses .nullslast(), otherwise .nullsfirst().
  • Unknown SortTypeTypeError("Uncorrected sort type").

PaginationQuery

  • Applies .limit(limit).offset(offset) on the Select.

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 NULL values

Run tests:

pip install -r requirements_dev.txt  # sqlalchemy, pytest, etc.
pytest -q

Notes / gotchas

  • ilike and case-insensitivity depend on your DB backend and column collations.
  • SearchQuery casts non-text fields to String for ALL_FIELDS search; ensure table_fields_relation lists only intended columns to search.
  • Date searching uses datetime.fromisoformat; invalid input raises ValueError. Tests are aware of this behavior.
  • Tools return Select objects; you must execute them with your session (e.g. session.execute(stmt).scalars().all()).
  • SortQuery intentionally raises KeyError for missing fields to fail fast with user-provided sort parameters.

Contributing

  1. Fork the repo.
  2. Create a branch for your change.
  3. Add tests for new behaviors or fixes.
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sqlalchemy_querytools-0.1.1.tar.gz (6.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sqlalchemy_querytools-0.1.1-py3-none-any.whl (8.2 kB view details)

Uploaded Python 3

File details

Details for the file sqlalchemy_querytools-0.1.1.tar.gz.

File metadata

  • Download URL: sqlalchemy_querytools-0.1.1.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

Hashes for sqlalchemy_querytools-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a6be0a28c00b576ca7a8b2d7ca34983abdfff43f0208440fe4f28cae9ace4679
MD5 961428bdaadda03bc3c78a7cbaa792d8
BLAKE2b-256 ea6f7f6d5919a349ee145fd2512ccea1df5543f327d0c8a13333acf289bb1475

See more details on using hashes here.

File details

Details for the file sqlalchemy_querytools-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for sqlalchemy_querytools-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 397fd4ae3f5db0a8f75688aab3d87a9bc5aa9c1f6098282b2f32dbce983d4f64
MD5 e5feb3e567e3ed121fb420fb3c3a611e
BLAKE2b-256 7a4a0d9cf456784ce0f4f91f60a3bb93c38568ac35c9e0bd774a41dfc3decf08

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page