Skip to main content

Don't fear the SQL

Project description

fear-of-sql

sqlx-inspired query validation for PostgreSQL in Python. Validate your SQL against a real database schema at startup, not at runtime. Supports t-string interpolation (Python 3.14+).

Overview

import fear_of_sql as fos

fear = fos.FearOfSQL()

# t-string query with parameters (requires python 3.14+)
@fear.query
def get_user(user_id: int) -> fos.Query[User]:
    return fos.Query(
        t"SELECT id, name, email FROM users WHERE id = {user_id}",
        result_type=User,
    )

# string SQL query with positional parameters (pre-3.14 or optional for 3.14+)
@fear.query
def get_user(user_id: int) -> fos.Query[User]:
    return fos.Query(
        "SELECT id, name, email FROM users WHERE id = $1",
        User,
        user_id,
    )

# validates all decorated queries against your DB
fear.validate_all("postgresql://localhost/mydb")

# execution helpers, fetch_one, fetch_all, fetch_optional, execute
user = await get_user(user_id=42).fetch_one(pool)
users = await list_users().fetch_all(pool)
maybe_user = await find_user("foo").fetch_optional(pool)
await delete_user(user_id=1).execute(pool)

Or validate raw SQL strings directly:

with fos.connect("postgresql://localhost/mydb") as conn:
    errors = fos.collect_errors(conn, "SELECT id, name FROM users", User)

Catch errors at startup, not at runtime:

# wrong type — id is an integer in the database
>>> fos.collect_errors(conn, "SELECT id FROM flashcards", str)
> TypeMismatchError("column 'id': expected ['str'], got int")

# wrong field type in a dataclass (pydantic also supported)
@dataclass
class Flashcard:
    id: int
    front: str
    back: int  # actually text in the database

>>> fos.collect_errors(conn, "SELECT id, front, back FROM flashcards", Flashcard)
> TypeMismatchError("column 'back': expected ['int'], got str")

# table doesn't exist
>>> fos.collect_errors(conn, "SELECT id FROM not_a_table", int)
> DatabaseError: relation "not_a_table" does not exist

Drivers

Validation

Validation requires pg8000.native as it exposes the column metadata (table_oid, column_attrnum) needed for nullability inference via pg_catalog. DB-API 2.0 compatible drivers do not expose this information.

Additional driver support

asyncpg is used by default for async execution helpers.

psycopg is supported, but optional:

pip install fear-of-sql[psycopg]

Execution

Driver Async Sync
asyncpg yes
psycopg yes yes
pg8000 (DB-API) yes

Queries can use either $1 or %s parameter style, or t-string interpolation on supported Python versions (3.14+).

Nullability overrides

Nullability is inferred automatically from pg_catalog and EXPLAIN plan analysis (including joins). For cases the inference can't handle, override with ! or ? column aliases:

# count(*) is always non-null, but Postgres can't prove it — force not-null
Query('SELECT count(*) AS "count!" FROM users', result_type=int)

# force a NOT NULL column to be treated as nullable
Query(r'SELECT id AS "id?" FROM flashcards', result_type=int | None)

Same convention as sqlx.

Supported Types

PostgreSQL Python mapping
bool bool
int2, int4, int8 int
float4, float8 float
numeric Decimal
text, varchar, char, name str
bytea bytes
uuid uuid.UUID
date datetime.date
time datetime.time
timestamp, timestamptz datetime.datetime
interval datetime.timedelta
json, jsonb object
money str
oid int
All above as arrays list[T]

Known Limitations

Unsupported PostgreSQL Types

The following built-in types are not yet supported and will raise UnsupportedTypeError during validation:

  • Network types: inet, cidr, macaddr
  • Geometric types: point, line, box, lseg, path, polygon, circle
  • Range types: int4range, int8range, numrange, daterange, tsrange, tstzrange
  • Bit string types: bit, varbit
  • Full-text search: tsvector, tsquery
  • XML: xml

To work around these issues, do not decorate the query functions (if using them), or do not pass them into the query validation.

User-Defined Types

Custom enums, composite types, and domain types use dynamically-assigned OIDs and require catalog lookup at validation time. Not yet implemented, but supported by the validation architecture.

Extension Types

Types from extensions (hstore, ltree, citext, etc.) are not supported.

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

fear_of_sql-0.1.0.tar.gz (417.2 kB view details)

Uploaded Source

Built Distribution

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

fear_of_sql-0.1.0-py3-none-any.whl (532.5 kB view details)

Uploaded Python 3

File details

Details for the file fear_of_sql-0.1.0.tar.gz.

File metadata

  • Download URL: fear_of_sql-0.1.0.tar.gz
  • Upload date:
  • Size: 417.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fear_of_sql-0.1.0.tar.gz
Algorithm Hash digest
SHA256 46220a030fa1b5453fd536b04771d0a2cabdd7e0d7fe12c0db8aa28ca5203de6
MD5 f2ffc10bcb3cb340ffba3e40e64f817c
BLAKE2b-256 6e8854384eed3134e58a52e708603ce5abcd16a0eaeadf5324a0816573339ec1

See more details on using hashes here.

File details

Details for the file fear_of_sql-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fear_of_sql-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 532.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fear_of_sql-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a06b39892a8cb8ba70d1654cf4a37235c0d30df9da435f9f99e19cb8283f75bb
MD5 c619ee12a2900865214a770d54047fa3
BLAKE2b-256 99f784c150105c13ff0bc0f828c62bd9f2266fec7ca1133319afd37395574393

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