Metadata-Version: 2.4
Name: iurban-tenancy
Version: 0.1.1
Summary: iUrban shared multi-tenancy SDK: SQLAlchemy Base + mixins + RLS session helper (ADR-006)
Author-email: iUrban <dev@iurban.es>
Requires-Python: >=3.12
Requires-Dist: sqlalchemy[asyncio]>=2.0
Provides-Extra: dev
Requires-Dist: fastapi>=0.111; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.111; extra == 'fastapi'
Description-Content-Type: text/markdown

# iurban-tenancy

Shared multi-tenancy data layer for iUrban microapps (ADR-006). Stops every microapp from copying
`tenancy.py` / reinventing RLS.

## What it gives you

- `Base` + `metadata` with a stable Alembic **naming convention** (reproducible migrations).
- Column mixins: `UuidPkMixin`, `TimestampMixin`, `TenantMixin` (`tenant_id`, indexed, not-null).
- `apply_tenant(session, tenant_id)` — the RLS helper (`SET LOCAL app.current_tenant`, **inside a txn**).
- `rls_ddl(table)` — SQL to enable+force RLS and add the tenant-isolation policy, for migrations.
- `iurban_tenancy.fastapi.make_tenant_session(sessionmaker, get_tenant_id)` — a tenant-scoped
  session dependency (decoupled from auth; the app wires `get_tenant_id` from `iurban-auth`).

## Use

```python
from iurban_tenancy import Base, UuidPkMixin, TenantMixin, TimestampMixin, rls_ddl

class Incident(UuidPkMixin, TenantMixin, TimestampMixin, Base):
    __tablename__ = "incidents"
    # ... your columns

# migration: op.execute(rls_ddl("incidents"))
```

```python
# app wiring (FastAPI + iurban-auth)
from fastapi import Depends, HTTPException
from iurban_auth import current_user
from iurban_tenancy.fastapi import make_tenant_session
from .db import async_session

def tenant_id(user = Depends(current_user)) -> str:
    if not user.tenant_id:
        raise HTTPException(403, "token carries no tenant_id")
    return user.tenant_id

tenant_session = make_tenant_session(async_session, tenant_id)
```

## Rules (ADR-006)

App connects as a **non-superuser** role; `apply_tenant` runs **inside a transaction** (safe with
PgBouncer transaction-pooling — a non-`LOCAL` `SET` would leak across tenants). Schema changes only
via Alembic migrations.
