Metadata-Version: 2.5
Name: postgres-component
Version: 0.1.0
Summary: Async Postgres connection-lifecycle component for python-components
Project-URL: Homepage, https://github.com/fabriciooml/postgres-component
Project-URL: Repository, https://github.com/fabriciooml/postgres-component
Project-URL: Issues, https://github.com/fabriciooml/postgres-component/issues
Project-URL: Changelog, https://github.com/fabriciooml/postgres-component/blob/main/CHANGELOG.md
Author: Fabricio Lima
License-Expression: MIT
License-File: LICENSE
Keywords: asyncio,asyncpg,postgres,postgresql,python-components,sqlalchemy
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: asyncpg<1,>=0.29
Requires-Dist: fastapi>=0.115
Requires-Dist: python-components<0.5,>=0.4.0
Requires-Dist: sqlalchemy[asyncio]<3,>=2.0
Description-Content-Type: text/markdown

# postgres-component

Async Postgres connection-lifecycle component for [python-components](https://github.com/lucassant95/python-components).

## Install

```bash
uv add postgres-component
```

## Requirements

- Python >= 3.11
- A running Postgres server

## Usage

```python
import asyncio

from python_components import System
from postgres_component import PostgresComponent

database = PostgresComponent(url="postgresql+asyncpg://user:pass@localhost:5432/mydb")

system = System({"database": database})

async def main():
    async with system:
        async with database.session() as session:
            await session.execute(...)  # commits on success, rolls back on exception

asyncio.run(main())
```

`PostgresComponent` can also be built from individual kwargs instead of a URL:

```python
PostgresComponent(host="localhost", port=5432, user="user", password="pass", database="mydb")
```

`url` takes priority over the kwargs when both are given.

### Health route

```python
from fastapi_component import create_app

app = create_app(system)  # RouteProvider discovery adds GET /health automatically
```

### Migrations

`PostgresComponent` never runs migrations itself. `migration_wiring()` hands your
own Alembic `env.py` the same connection config so it doesn't have to be
re-derived:

```python
# alembic/env.py
from postgres_component import create_migration_engine

engine = create_migration_engine(database.migration_wiring())
# use `engine` with Alembic's async run_sync() pattern
```

## Semantics and caveats

- **Fail-fast startup.** `start()` runs an explicit `SELECT 1` before considering itself connected — a dead database makes `start()` raise rather than "starting" successfully and failing later on first query.
- **Managed Session is a unit of work.** `database.session()` begins a transaction, commits on clean exit, and rolls back on exception — one call is one unit of work. For manual control (long-lived reads, streaming), use the raw `database.session_factory` directly.
- **Errors propagate raw.** SQLAlchemy exceptions (`OperationalError`, `IntegrityError`, etc.) are never wrapped; `get_status()["last_error"]` is updated as a string for introspection, but the original exception type always reaches the caller.
- **No ORM, no migrations.** The component owns connection lifecycle only. Models and Alembic setup belong to the consuming app; `migration_wiring()` only removes the boilerplate of re-deriving connection config.

## Development

```bash
uv sync --all-groups
uv run pytest
uv run ruff format --check .
uv run ruff check .
```

Integration tests spin up a real Postgres instance via [testcontainers](https://testcontainers.com/modules/postgres/). For local development against a persistent instance instead, run `docker compose up -d`.

## License

MIT
