Metadata-Version: 2.4
Name: core-framework
Version: 3.3.0
Summary: Core framework package (import as core_framework)
Project-URL: Homepage, https://github.com/NepNepFFXIV/core-framework
Project-URL: Repository, https://github.com/NepNepFFXIV/core-framework
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.14
Requires-Dist: aiofiles>=25.1.0
Requires-Dist: alembic>=1.18.5
Requires-Dist: arq>=0.28.0
Requires-Dist: asyncer>=0.0.18
Requires-Dist: asyncpg>=0.31.0
Requires-Dist: asyncssh>=2.24.0
Requires-Dist: brotli-asgi>=1.6.0
Requires-Dist: cashews[redis]>=7.5.0
Requires-Dist: device-detector>=6.2.0
Requires-Dist: fastapi[standard]>=0.139.0
Requires-Dist: firebase-admin>=7.5.0
Requires-Dist: httpx[http2]>=0.28.1
Requires-Dist: itsdangerous>=2.2.0
Requires-Dist: mashumaro[orjson]>=3.22
Requires-Dist: opentelemetry-api<1.44,>=1.43.0
Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.44,>=1.43.0
Requires-Dist: opentelemetry-instrumentation-asyncpg<0.65,>=0.64b0
Requires-Dist: opentelemetry-instrumentation-fastapi<0.65,>=0.64b0
Requires-Dist: opentelemetry-instrumentation-httpx<0.65,>=0.64b0
Requires-Dist: opentelemetry-instrumentation-redis<0.65,>=0.64b0
Requires-Dist: opentelemetry-sdk<1.44,>=1.43.0
Requires-Dist: opentelemetry-semantic-conventions<0.65,>=0.64b0
Requires-Dist: orjson>=3.11.9
Requires-Dist: pillow-heif>=1.4.0
Requires-Dist: pillow>=12.3.0
Requires-Dist: pyinstrument>=5.1.2
Requires-Dist: python-ulid>=3.1.0
Requires-Dist: resend[async]>=2.34.0
Requires-Dist: structlog>=26.1.0
Requires-Dist: tenacity>=9.1.4
Provides-Extra: testing
Requires-Dist: asgi-lifespan>=2.1.0; extra == 'testing'
Requires-Dist: pytest-xdist>=3.8.0; extra == 'testing'
Requires-Dist: pytest>=9.1.1; extra == 'testing'
Requires-Dist: testcontainers[postgres,redis]>=4.13.2; extra == 'testing'
Description-Content-Type: text/markdown

# Core framework

Install from PyPI as **`core-framework`**, import **`core_framework`**. MIT — [LICENSE](LICENSE). [Repository](https://github.com/NepNepFFXIV/core-framework), [PyPI](https://pypi.org/project/core-framework/).

## Documentation

| Doc                                                        | Purpose                                                   |
| ---------------------------------------------------------- | --------------------------------------------------------- |
| [docs/README.md](docs/README.md)                           | Doc map (platform, design, library, domains, deployments) |
| [docs/library/package-api.md](docs/library/package-api.md) | Host import surface and upgrade tables                    |
| [CHANGELOG.md](CHANGELOG.md)                               | Release notes (SemVer)                                    |

## Install

```bash
uv add core-framework
```

Pin hosts to a locked version. On each bump, read **[CHANGELOG.md](CHANGELOG.md)** and the host-upgrade tables in **[package-api.md](docs/library/package-api.md)**.

## Local development

Use **Python 3.14**. Install **uv** and **Docker**.

Tracked **`config.toml`** at the repo root is the local-dev settings file (dummy values; no production secrets). Production renders from **`config.toml.template`** via deploy **`envsubst`** — see [architecture decisions](docs/platform/architecture-decisions.md).

```bash
docker compose -f deploy/app/docker-compose.dev.yaml up -d
uv sync --locked --all-extras --dev
uv run cf-alembic
make run
```

**Firebase:** copy **`firebase_config.example.json`** to **`firebase_config.json`** at the repo root and replace with your Firebase project’s **service account** JSON from the console.

For **`make test`**, use the Firebase CLI (Auth emulator). Stop DB/Redis: **`docker compose -f deploy/app/docker-compose.dev.yaml down`**.

**Pre-commit:** `uv sync --dev` installs **pre-commit**. Enable hooks with **`uv run pre-commit install`** (ruff, mdformat, uv-lock, and other checks in **`.pre-commit-config.yaml`**).

## Hosts

In your host app, load **`Settings`** or a **`Settings`** subclass from **`core_framework.core.settings`**, pass it to **`init_app`** from **`core_framework.main`**, then wire host-specific dependencies, exception handlers, and routers.

```python
from core_framework.main import init_app
from fastapi import FastAPI

from myapp.settings import HostSettings, load_default_settings


def build_app(settings: HostSettings | None = None) -> FastAPI:
    resolved = settings if settings is not None else load_default_settings()
    app = init_app(resolved)

    from myapp.bootstrap import configure_dependencies
    from myapp.exception_handlers import setup_exception_handlers

    configure_dependencies(runtime=app.state.core_runtime)
    setup_exception_handlers(app)

    from myapp.api.router import router

    app.include_router(router)
    return app
```

From the host repository root, run **`uv run cf-alembic`**. See [core-framework-migration](docs/library/core-framework-migration.md).

**Worker:** production also runs the ARQ worker (`arq core_framework.worker.main.WorkerSettings`, or a host composition that calls the same startup path). Schedules and tasks: [workers.md](docs/library/workers.md). Composition details: [package-api.md](docs/library/package-api.md) (Default deployment entrypoints).

**Observability:** OpenTelemetry OTLP (not Logfire). Under **`[observability]`**, set **`enabled`** and **`otlp_endpoint`** (required when enabled outside **`local`**); drop **`logfire_token`** / **`LOGFIRE_TOKEN`**. Same-host Grafana LGTM stack: **`deploy/observability/`** and [observability-lgtm-setup.md](docs/deployments/guides/setup/observability-lgtm-setup.md). Host upgrade table: [package-api.md — observability OTLP](docs/library/package-api.md).

## Host pytest

In your host repository, add the **`core-framework[testing]`** extra to dev or test dependencies. In **`tests/conftest.py`**, register **`pytest_core_framework_config`** (returns **`TestConfig`**) and a session-scoped **`anyio_backend`** fixture that returns **`"asyncio"`**.

```python
import pytest

from core_framework.testing import TestConfig

from myapp.settings import load_default_settings


def pytest_core_framework_config() -> TestConfig:
    from myapp.main import build_app

    return TestConfig(
        settings_loader=load_default_settings,
        app_factory=build_app,
    )


@pytest.fixture(scope="session")
def anyio_backend() -> str:
    return "asyncio"
```

See [Package API — Testing](docs/library/package-api.md#testing-pytest-plugin) and [testing plugin design](docs/library/testing-plugin-design.md).
