diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md index e332c92..d361e05 100644 --- a/ATTRIBUTIONS.md +++ b/ATTRIBUTIONS.md @@ -360,7 +360,7 @@ SOFTWARE. ``` -## aignostics-foundry-core (0.11.0) - MIT License +## aignostics-foundry-core (0.12.0) - MIT License 🏭 Foundational infrastructure for Foundry components. diff --git a/README.md b/README.md index 6da1d96..ffd7f74 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,13 @@ Or follow the [installation guide](https://mise.jdx.dev/getting-started.html) fo ## Usage -### Initialise the context at application startup +### Quickstart -Call `set_context()` once, before any library code runs, then call `boot()` to -initialise logging, the SSL trust chain, and optional Sentry integration: +`FoundryContext` is the single source of truth for all project-specific values. One call at +application startup makes everything available library-wide — logging, Sentry, database settings, +and more all derive from it automatically. + +#### Initialise and boot ```python # main.py @@ -42,32 +45,43 @@ set_context(FoundryContext.from_package("myproject")) boot() ``` -`FoundryContext.from_package()` derives everything from package metadata and -environment variables: +`FoundryContext.from_package("myproject")` reads package metadata and environment variables to +populate every field: - `name`, `version`, `version_full` — from `importlib.metadata` -- `environment` — from `MYPROJECT_ENVIRONMENT` → `ENV` → `VERCEL_ENV` → - `RAILWAY_ENVIRONMENT` → `"local"` +- `environment` — resolved from env vars in priority order (see [Configuration reference](#configuration-reference) below) +- `env_prefix` (`"MYPROJECT_"`) — used by every settings class; all env vars for this project share this prefix - `is_container`, `is_cli`, `is_test`, `is_library` — detected automatically -- `env_prefix` (`"MYPROJECT_"`) — used by every settings class in the library -### Access the context from any module +`boot()` initialises logging (loguru), amends the SSL trust chain (truststore + certifi), and +optionally starts Sentry — all in one call. + +#### Env file search order + +Settings are loaded from the environment **and** from env files. Highest priority first: + +1. `.env.{environment}` +2. `.env` +3. `{MYPROJECT_ENV_FILE}` (optional extra file; when the variable is set) +4. `~/.myproject/.env.{environment}` +5. `~/.myproject/.env` + +#### Access the context from any module ```python from aignostics_foundry_core.foundry import get_context ctx = get_context() print(f"Running {ctx.name} v{ctx.version_full} in {ctx.environment}") -# → Running myproject v1.2.3+main---run.12345 in staging +# → Running myproject v1.2.3+main-abc1234---run.12345---build.42 in staging ``` -`get_context()` raises `RuntimeError` with a clear message if `set_context()` -was never called. +`get_context()` raises `RuntimeError` with a clear message if `set_context()` was never called. -### Pass context explicitly in tests +#### Testing pattern -Never call `set_context()` in tests. Pass a `FoundryContext` directly to each -function via its optional `context` parameter instead: +Never call `set_context()` in tests. Pass a `FoundryContext` directly to functions via their +optional `context` parameter: ```python from aignostics_foundry_core.foundry import FoundryContext @@ -77,14 +91,93 @@ ctx = FoundryContext(name="myproject", version="0.0.0", version_full="0.0.0", en logging_initialize(context=ctx) ``` -All public library functions (`logging_initialize`, `sentry_initialize`, `boot`, -`load_modules`, etc.) accept an optional `context` keyword argument and fall -back to `get_context()` when it is `None`. +All public library functions (`logging_initialize`, `sentry_initialize`, `boot`, `load_modules`, +etc.) accept an optional `context` keyword argument and fall back to `get_context()` when it is +`None`. + +--- + +### Configuration reference + +All settings classes read from environment variables prefixed with `{PREFIX}` where +`{PREFIX}` = `MYPROJECT_` for a package named `myproject`. + +#### Context & deployment environment -### Database +Read directly by `FoundryContext.from_package()` — no settings class. -Once a context is configured via `set_context()`, all database functions work -with no arguments — the URL and pool settings are read from the context: +| Variable | Default | Description | +|---|---|---| +| `{PREFIX}ENVIRONMENT` | `"local"` | Deployment environment name. Highest priority. | +| `ENV` | — | Fallback environment (lower priority than `{PREFIX}ENVIRONMENT`). | +| `VERCEL_ENV` | — | Vercel deployment environment (lower priority). | +| `RAILWAY_ENVIRONMENT` | — | Railway deployment environment (lower priority). | +| `{PREFIX}RUNNING_IN_CONTAINER` | unset | Set to any non-empty value to mark `is_container = True`. | +| `{PREFIX}ENV_FILE` | unset | Path to an additional env file, inserted between the home-dir files and the local `.env`. | + +#### Build metadata + +Read by `FoundryContext.from_package()` to build `version_full` and `version_with_vcs_ref`. All +optional; most useful in CI. + +| Variable | Default | Description | +|---|---|---| +| `VCS_REF` | read from `.git/HEAD` | Branch name or commit SHA. Falls back to reading `.git/HEAD` when project path is found. | +| `COMMIT_SHA` | `"unknown"` | Full commit SHA; first 7 chars used. | +| `BUILD_DATE` | `"unknown"` | Build date string. | +| `CI_RUN_ID` | `"unknown"` | CI system run ID. | +| `CI_RUN_NUMBER` | `"unknown"` | CI system build number. | +| `BUILDER` | `"uv"` | Build tool name. | + +When any of these variables is set, `version_full` gains a `+…` suffix, e.g. +`1.2.3+main-abc1234---run.12345---build.42`. + +#### Logging (`{PREFIX}LOG_`) + +Settings class: `LogSettings` + +| Variable | Default | Description | +|---|---|---| +| `{PREFIX}LOG_LEVEL` | `INFO` | Log level: `CRITICAL`, `ERROR`, `WARNING`, `SUCCESS`, `INFO`, `DEBUG`, or `TRACE`. | +| `{PREFIX}LOG_STDERR_ENABLED` | `true` | Enable logging to stderr. | +| `{PREFIX}LOG_FILE_ENABLED` | `false` | Enable logging to a file. | +| `{PREFIX}LOG_FILE_NAME` | platform log dir | Path to the log file (validated on startup when `FILE_ENABLED` is true). | +| `{PREFIX}LOG_REDIRECT_LOGGING` | `true` | Redirect stdlib `logging` to loguru via `InterceptHandler`. | + +#### Sentry (`{PREFIX}SENTRY_`) + +Settings class: `SentrySettings`. Sentry is only initialised when `ENABLED=true` **and** `DSN` is +set. + +| Variable | Default | Description | +|---|---|---| +| `{PREFIX}SENTRY_ENABLED` | `false` | Enable Sentry error and performance monitoring. | +| `{PREFIX}SENTRY_DSN` | unset | Sentry DSN (must be an HTTPS URL with a valid `ingest.*.sentry.io` domain). | +| `{PREFIX}SENTRY_DEBUG` | `false` | Enable Sentry SDK debug mode. | +| `{PREFIX}SENTRY_SEND_DEFAULT_PII` | `false` | Include personally-identifiable information in events. | +| `{PREFIX}SENTRY_MAX_BREADCRUMBS` | `50` | Maximum breadcrumbs stored per event. | +| `{PREFIX}SENTRY_SAMPLE_RATE` | `1.0` | Error event sample rate (0.0–1.0). | +| `{PREFIX}SENTRY_TRACES_SAMPLE_RATE` | `0.1` | Transaction/trace sample rate. | +| `{PREFIX}SENTRY_PROFILES_SAMPLE_RATE` | `0.1` | Profiler sample rate. | +| `{PREFIX}SENTRY_PROFILE_SESSION_SAMPLE_RATE` | `0.1` | Profile session sample rate. | +| `{PREFIX}SENTRY_PROFILE_LIFECYCLE` | `"trace"` | Profile lifecycle mode: `"trace"` or `"manual"`. | +| `{PREFIX}SENTRY_ENABLE_LOGS` | `true` | Forward log records to Sentry. | + +#### Database (`{PREFIX}DB_`) + +Settings class: `DatabaseSettings`. Database configuration is only activated when `{PREFIX}DB_URL` +is present (in the environment or in an env file). + +| Variable | Required | Default | Description | +|---|---|---|---| +| `{PREFIX}DB_URL` | to activate | — | Full async database connection URL (e.g. `postgresql+asyncpg://user:pass@host/db`). Always access via `DatabaseSettings.get_url()`. | +| `{PREFIX}DB_POOL_SIZE` | no | `10` | SQLAlchemy connection pool size. | +| `{PREFIX}DB_POOL_MAX_OVERFLOW` | no | `10` | Max connections above pool size. | +| `{PREFIX}DB_POOL_TIMEOUT` | no | `30.0` | Seconds to wait for a pool connection. | +| `{PREFIX}DB_NAME` | no | unset | Override the database name in the URL path at runtime. | + +Once a context is configured via `set_context()`, all database functions work with no arguments — +the URL and pool settings are read from the context: ```python from aignostics_foundry_core.database import init_engine, cli_run_with_db, with_engine @@ -106,17 +199,6 @@ async def my_job(): ... async def my_other_job(): ... ``` -`FoundryContext.from_package()` activates database configuration automatically -when the following environment variables are present: - -| Variable | Required | Description | -|---|---|---| -| `{PREFIX}DB_URL` | yes (to activate) | Full database connection URL | -| `{PREFIX}DB_POOL_SIZE` | no | Connection pool size (default `10`) | -| `{PREFIX}DB_MAX_OVERFLOW` | no | Max pool overflow (default `10`) | -| `{PREFIX}DB_POOL_TIMEOUT` | no | Pool wait timeout in seconds (default `30.0`) | -| `{PREFIX}DB_NAME` | no | Override database name in the URL path | - In tests, construct `DatabaseSettings` directly instead of setting env vars: ```python @@ -126,13 +208,23 @@ from tests.conftest import make_context ctx = make_context(database=DatabaseSettings(_env_prefix="TEST_DB_", url="sqlite+aiosqlite:///test.db")) ``` -### Health API +#### Authentication (`{PREFIX}AUTH_`) -```python -from aignostics_foundry_core.health import Health, HealthStatus +Settings class: `AuthSettings`. Both fields are required — no defaults. Only needed when using +`aignostics_foundry_core.api.auth` dependencies. -health = Health(status=HealthStatus.UP) -``` +| Variable | Required | Description | +|---|---|---| +| `{PREFIX}AUTH_INTERNAL_ORG_ID` | yes | Auth0 organization ID identifying the internal org (used by `require_internal`). | +| `{PREFIX}AUTH_AUTH0_ROLE_CLAIM` | yes | JWT claim name containing the user's role (e.g. `https://myapp.example.com/roles`). | + +#### Console + +Read directly from the environment — no settings class. + +| Variable | Default | Description | +|---|---|---| +| `{PREFIX}CONSOLE_WIDTH` | auto-detect | Override Rich console width (integer, characters). Defaults to terminal width or 80 in non-TTY environments. | ## Further Reading