Metadata-Version: 2.5
Name: csrd-models
Version: 0.8.0
Summary: Base Pydantic models and settings
Project-URL: Repository, https://github.com/csrd-api/fastapi-common
Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/models
Project-URL: Changelog, https://github.com/csrd-api/fastapi-common/blob/main/CHANGELOG.md
License: MIT
Requires-Python: >=3.12
Requires-Dist: pydantic-settings<3,>=2.10
Requires-Dist: pydantic<3,>=2.10
Description-Content-Type: text/markdown

# csrd-models

Shared Pydantic models, settings, and parsing utilities for FastAPI microservices.

**Package**: `csrd.models` · **Import**: `from csrd.models import BaseModel, BaseSettings`

## What's included

- `BaseModel` / `BaseSettings` with camelCase alias config and `populate_by_name=True`
- `ModelParserMixin` — response parsing mixin for delegates and repositories
- `PayloadExtractor` / `DefaultExtractor` — pluggable payload extraction
- `UserClaims` — JWT user claims dataclass
- `APIErrorResponse`, `ErrorMeta`, `Error`, `APIVersion` — structured error response models
- `Secret` / `InternalToken` — settings field marker that refuses to boot on empty, short or placeholder secrets
- `app_env` / `is_production` on `BaseSettings`, `ProductionRequired`, `Secret(production_only=True)` — production-boot checks

## Secret settings

```python
from typing import Annotated

from csrd.models import BaseSettings, InternalToken, Secret


class Settings(BaseSettings):
    internal_token: InternalToken = ""  # Annotated[str, Secret()] — min 32 chars
    db_password: Annotated[str, Secret(min_length=16, placeholders=("dev-password",))] = "change_me"
```

`Secret(min_length=32, placeholders=(), markers=DEFAULT_PLACEHOLDER_MARKERS)`
rejects empty/whitespace values, values containing a marker (`change_me`,
`changeme`, `change-me`, `<generate`; case-insensitive), any declared
placeholder, and values shorter than `min_length`. It works on `str` and
`SecretStr`; `None` passes through for `str | None` fields. Validation runs when
settings load (pydantic-settings validates defaults), and a single
`ValidationError` lists every offending field. `BaseSettings` sets
`hide_input_in_errors=True`, so rejected values never appear in the error.

## Production checks

```python
from typing import Annotated

from csrd.models import BaseSettings, InternalToken, ProductionRequired, Secret


class Settings(BaseSettings):
    # app_env: str = "development"  — inherited; env var APP_ENV, lower-cased
    internal_token: InternalToken = ""                                          # always checked
    db_password: Annotated[str, Secret(production_only=True)] = "change_me"     # checked in production
    cookie_secure: Annotated[bool, ProductionRequired(True)] = False            # must be True in production
    public_base_url: Annotated[str, ProductionRequired()] = ""                  # must be set in production
```

- `BaseSettings.app_env` (env `APP_ENV`, default `"development"`; any name,
  stripped and lower-cased) and `settings.is_production` (`app_env == "production"`).
- `ProductionRequired(value)`: with `APP_ENV=production` the field must equal
  `value`; `ProductionRequired()` only requires it to be set (not `None`,
  empty string or empty collection). Error type `production_required`.
- `Secret(production_only=True)`: the usual secret checks, but only when
  `APP_ENV=production`; development defaults such as `change_me` are tolerated
  elsewhere.
- Everything runs as field validation when the settings object is built, so
  web apps and workers (`Settings()`) are covered with no extra call. One
  `ValidationError` lists every failing field — always-on secrets and
  production checks together — and never includes the rejected values.
- The production checks read `app_env` from fields validated before them;
  `BaseSettings` declares it first (a subclass may redeclare it, e.g. to
  change the default). On plain `BaseModel`s without `app_env` they are skipped.

## Installation

```bash
uv pip install "csrd-models @ git+ssh://git@github.com/csrd-api/fastapi-common.git#subdirectory=packages/models"
```

## Dependencies

None (Tier 1 — standalone)
