Skip to main content

A Python package that seamlessly integrates PostgreSQL, Jinja templating, and Pydantic for type-safe database queries

Project description

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
  • 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 public 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

shawn@vinovoss.com

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

pgjinja-3.1.0.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

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

pgjinja-3.1.0-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file pgjinja-3.1.0.tar.gz.

File metadata

  • Download URL: pgjinja-3.1.0.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using 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":true}

File hashes

Hashes for pgjinja-3.1.0.tar.gz
Algorithm Hash digest
SHA256 d9ba669c796fc6f505ea7b416abeec20b83e47bb8974b8c2e410b4cc8fbcf8cb
MD5 ad87471ba828ab72c4a3ffd2a3562466
BLAKE2b-256 9556aabd7384d4b190fbec72085f202387ed07d5ed4f6072e0dbb9f69c9d3c7b

See more details on using hashes here.

File details

Details for the file pgjinja-3.1.0-py3-none-any.whl.

File metadata

  • Download URL: pgjinja-3.1.0-py3-none-any.whl
  • Upload date:
  • Size: 10.9 kB
  • Tags: Python 3
  • Uploaded using 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":true}

File hashes

Hashes for pgjinja-3.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5d9f11541eb56cf4b97e9b55b0caf223b06ef1f320cde0c8a7c8906f29e4071f
MD5 9ef7a6d522821b6227fa0b54a4d4867d
BLAKE2b-256 f7b674d2daa03e7ca62168112af96a4b2d943c04822a8682790a1d922499a609

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