Skip to main content

pgjinja

pgjinja is a Python library for running PostgreSQL queries from Jinja2 SQL templates while keeping results type-safe with Pydantic models. It exists to separate SQL from application logic, reduce query string duplication, and provide a single sync/async API with connection pooling through psycopg3.

Tech Stack

  • Language: Python 3.11+
  • Build backend: Hatchling
  • Core libraries: jinjasql2>=0.1.11, psycopg[binary,pool]>=3.2.6, pydantic>=2.10.6
  • Dev tools: pytest, pytest-asyncio, pytest-cov, ruff, uv

How It Works

PgJinja and PgJinjaAsync are the two main clients. Both accept a PgJinjaSettings object that stores PostgreSQL credentials, template directory, and pool sizing. The sync client uses ConnectionPool and the async client uses AsyncConnectionPool.

When you call query(template, params, model), the library reads the SQL template file from template_dir, renders it with JinjaSql, and executes the prepared SQL with psycopg. For read queries, it returns tuples by default, or Pydantic model instances if you pass a model class.

If a model is provided, pgjinja injects _model_fields_ into template context so templates can select model-defined columns directly. This keeps SQL column lists aligned with your Pydantic schema without hand-maintaining SELECT fields in multiple places.

Both clients lazily open their connection pool on first use and retry failed query execution attempts (default: 2 attempts total), logging pool stats to help diagnose failures.

Getting Started

Prerequisites

  • Python >=3.11
  • [uv](https://docs.astral.sh/uv/)
  • PostgreSQL (local or remote)

Installation

git clone https://github.com/tungph/pgjinja.git
cd pgjinja
uv sync

To install from PyPI instead:

pip install pgjinja

Configuration

The library is configured in Python through PgJinjaSettings:

Field Required Default Description
user Yes - PostgreSQL username
password Yes - PostgreSQL password (SecretStr)
host No localhost PostgreSQL host
port No 5432 PostgreSQL port
dbname No postgres Database name
template_dir No current directory Directory containing SQL template files
min_size No 4 Minimum pool size
max_size No None Maximum pool size (None = unlimited)
application_name No pgjinja Label for PostgreSQL connection logs

Example setup:

from pathlib import Path
from pydantic import SecretStr
from pgjinja import PgJinjaSettings, PgJinjaAsync

settings = PgJinjaSettings(
    user="postgres",
    password=SecretStr("postgres"),
    host="localhost",
    dbname="postgres",
    template_dir=Path("./templates"),
)

client = PgJinjaAsync(settings)

Application lifetime (singletons)

If you keep a single PgJinja or PgJinjaAsync instance for the whole process (typical for web apps and workers), its connection pool opens lazily on the first query. You should close the pool when the application shuts down so connections are released cleanly:

  • Async: await client.close() in your teardown path (for example ASGI lifespan shutdown, or a finally block in asyncio.run()).
  • Sync: client.close() on process exit or in your framework’s shutdown hook.

If the pool was opened and the client is destroyed without closing, you may see a log warning asking you to use await client.close() for async clients.

Example (FastAPI-style lifespan):

from contextlib import asynccontextmanager

from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.db = PgJinjaAsync(settings)
    yield
    await app.state.db.close()

app = FastAPI(lifespan=lifespan)

Running Locally

make run-example

examples/merchant_example.py expects a local examples/config.ini file with a [database] section (user, password, host, dbname) and uses SQL templates in examples/template/.

Verifying It Works

Run tests:

make test

Or run lint + formatting checks:

make lint

External Dependencies

Name Purpose Local Setup Env Vars
PostgreSQL Executes rendered SQL templates via psycopg3 pool connections Run PostgreSQL locally (for example via Docker or system package) and create a database/user reachable by PgJinjaSettings None required by library itself (credentials provided in PgJinjaSettings; example script reads examples/config.ini)

Project Structure

.
├── src/pgjinja/
│   ├── pgjinja.py                  # Sync client (ConnectionPool + template execution)
│   ├── pgjinja_async.py            # Async client (AsyncConnectionPool + template execution)
│   ├── schemas/pgjinja_settings.py # Typed DB and pool configuration model
│   └── shared/
│       ├── common.py               # Template reading and model-field helpers
│       └── execution.py            # Shared query prep and result mapping helpers
├── tests/                          # Unit/integration-style test suite
├── examples/
│   ├── merchant_example.py         # End-to-end async usage example
│   └── template/                   # Example SQL Jinja templates
├── pyproject.toml                  # Packaging, dependencies, pytest/ruff config
└── Makefile                        # Common development and release commands

Common Tasks

Task Command
Install dependencies make install
Run tests make test
Lint + format make lint
Format only make format
Build package make build
Run example app make run-example
Publish to PyPI make publish

Maintainer

@tungph

Metadata

Release files for pgjinja 3.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pgjinja 3.2.0
File Size Uploaded
pgjinja-3.2.0.tar.gz 9.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pgjinja 3.2.0
File Interpreter ABI Platform
pgjinja-3.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 20.1 kB

Release files / pgjinja-3.2.0.tar.gz

Download URL pgjinja-3.2.0.tar.gz
Size 9.3 kB
Tags Source
SHA-256 checksum
How to use checksums
9de14304a0849d9730d69c7a16b748d43d2a4c27cf9c8160341bfbc095e3cfda
BLAKE2b-256 checksum
How to use checksums
a4e2c9b82be018f2018cd82a3338cc2fd178333de5de0d15cf402d2d7e60edd8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","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}

Release files / pgjinja-3.2.0-py3-none-any.whl

Download URL pgjinja-3.2.0-py3-none-any.whl
Size 10.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5c4d5a1f19387ddbcf0f831ac5e1a27d27c32eae94ad7ad39f9e1d9786475c2d
BLAKE2b-256 checksum
How to use checksums
4224486edb86912da4adae1ef62a837b68cb5c2ed8e84e163149f3f283a6a213
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","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}

Release history Release notifications | RSS feed

This release

3.2.0 This release

2 release files

3.1.0

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.1.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.4.4

2 release files

1.4.3

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page