Metadata-Version: 2.4
Name: fastfoundry-postgres
Version: 1.0.0
Summary: PostgreSQL connection building blocks for the fastfoundry ecosystem.
Project-URL: Homepage, https://github.com/Drozdetskiy/fastfoundry-postgres
Project-URL: Source, https://github.com/Drozdetskiy/fastfoundry-postgres
Project-URL: Changelog, https://github.com/Drozdetskiy/fastfoundry-postgres/releases
Project-URL: Issues, https://github.com/Drozdetskiy/fastfoundry-postgres/issues
Author-email: Mikhail Drozdetskiy <m.drozdetskiy@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: async,database,fastapi,fastfoundry,postgres,postgresql
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: asyncpg>=0.30
Requires-Dist: pydantic>=2
Requires-Dist: sqlalchemy[asyncio]>=2.0
Description-Content-Type: text/markdown

# fastfoundry-postgres

PostgreSQL connection building blocks for the **fastfoundry** ecosystem.

Part of the `fastfoundry.*` namespace (`fastfoundry-settings`, `fastfoundry-redis`, …).

An async [SQLAlchemy](https://www.sqlalchemy.org/) engine and session lifecycle for a
single PostgreSQL database (via [asyncpg](https://github.com/MagicStack/asyncpg)), a
declarative model base, and a configuration model that plugs into `fastfoundry.settings`.

## Install

```bash
pip install fastfoundry-postgres
# or
uv add fastfoundry-postgres
```

Requires Python 3.12+.

## Configuration

`DatabaseCfg` holds the connection parameters. It is designed to nest inside a
`fastfoundry.settings.BaseAppSettings` subclass, so the values load from the environment
with the rest of your service configuration, then get handed to `database_ctx`:

```python
from fastfoundry.settings import BaseAppSettings
from fastfoundry.postgres import DatabaseCfg


class Settings(BaseAppSettings):
    database: DatabaseCfg


settings = Settings()
```

With the `FF_` prefix and `__` nested delimiter that `BaseAppSettings` applies, the
fields are read from the environment as `FF_DATABASE__HOST`, `FF_DATABASE__PORT`,
`FF_DATABASE__USER`, and so on.

## Engine & session lifecycle

Open `database_ctx(settings.database)` once for the application lifetime — it builds the
engine and session factory, publishes them for ambient access (`get_engine()` and
`db_cfg()`), and disposes the engine on exit. For FastAPI, wire it into the `lifespan`:

```python
from contextlib import asynccontextmanager
from collections.abc import AsyncGenerator

from fastapi import FastAPI
from fastfoundry.postgres import database_ctx

from app.settings import settings


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
    async with database_ctx(settings.database):
        yield


app = FastAPI(lifespan=lifespan)
```

Acquire a session with `database_session`, and open transactions explicitly with
`transaction` (sessions use `autobegin=False`, so nothing starts a transaction for you —
even read-only work should be wrapped, as SQLAlchemy recommends). A nested `transaction`
becomes a `SAVEPOINT`:

```python
from fastfoundry.postgres import database_session, transaction


async with database_session() as session:
    async with transaction(session):
        session.add(user)
```

## Models

Subclass `BaseDBModel`. The table name defaults to the lower-cased class name with a
trailing `s`; set `__table_name__` to override it:

```python
from sqlalchemy.orm import Mapped, mapped_column

from fastfoundry.postgres import BaseDBModel


class User(BaseDBModel):          # -> table "users"
    id: Mapped[int] = mapped_column(primary_key=True)


class Account(BaseDBModel):
    __table_name__ = "accounts"   # explicit table name
    id: Mapped[int] = mapped_column(primary_key=True)
```

## Development

```bash
mise run install     # uv sync --dev
mise run lint        # ruff check
mise run typecheck   # mypy --strict
mise run test        # pytest
mise run build       # uv build (sdist + wheel)
```

## License

MIT
