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.2.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.2-py3-none-any.whl (8.2 kB view details)

Uploaded Python 3

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

Hashes for sqlalchemy_querytools-0.1.2.tar.gz
Algorithm Hash digest
SHA256 91cff67caf5cce35dc95797ebcf229a52880c4acd5425f7c0fd699802415b639
MD5 ea904122b9a64bd790e863c82851754b
BLAKE2b-256 30448e13de1ee71a232e51b5284b6606b95c1e1ae85032efe8a77b377ffa3318

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for sqlalchemy_querytools-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8254f5961ec61ecdcc601aca69c92cef9b02b5abdd09f9dadb7993d88e3924bc
MD5 7cbac0d64d454084c5b33bb0fdc2446f
BLAKE2b-256 c1b4effe48b77b4e2f5a0277753b8b693ec767e43b6b620a57bbe9246c3b220a

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