Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ATTRIBUTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ SOFTWARE.

```

## aignostics-foundry-core (0.7.0) - MIT License
## aignostics-foundry-core (0.7.1) - MIT License

🏭 Foundational infrastructure for Foundry components.

Expand Down
21 changes: 15 additions & 6 deletions src/aignostics_foundry_core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This file provides an overview of all modules in `aignostics_foundry_core`, thei
| **user_agent** | Parameterised HTTP user-agent string builder | `user_agent(project_name, version, repository_url)` — builds `{project_name}-python-sdk/{version} (…)` string including platform info, current test, and GitHub Actions run URL |
| **gui** | NiceGUI page helpers, auth decorators, and nav builder | `GUINamespace` (configurable page decorator namespace), `gui` (default singleton), `page_public/authenticated/admin/internal/internal_admin` decorators, `get_gui_user`, `require_gui_user`, `BaseNavBuilder`, `NavItem`, `NavGroup`, `gui_get_nav_groups(*, context=None)`, `BasePageBuilder`, `gui_register_pages(*, context=None)`, `gui_run(*, context=None, …)`; constants `WINDOW_SIZE`, `BROWSER_RECONNECT_TIMEOUT`, `RESPONSE_TIMEOUT` |
| **console** | Themed terminal output | Module-level `console` object (Rich `Console`) with colour theme and `_get_console()` factory |
| **foundry** | Project context injection | `FoundryContext`, `FoundryContext.from_package()`, `set_context()`, `get_context()` — centralised project-specific values (name, version, `version_full`, `version_with_vcs_ref`, environment, env files, URLs, `python_version`, runtime mode flags `is_container`, `is_cli`, `is_test`, `is_library`, `database: DatabaseSettings \| None`) derived from package metadata and environment variables; `from_package()` populates `database` from `{env_prefix}DB_*` env vars when `{env_prefix}DB_URL` is present |
| **foundry** | Project context injection | `PackageMetadata`, `FoundryContext`, `FoundryContext.from_package()`, `set_context()`, `get_context()` — centralised project-specific values (name, version, `version_full`, `version_with_vcs_ref`, environment, env files, URLs, `python_version`, `metadata: PackageMetadata` (description, author_name, author_email), runtime mode flags `is_container`, `is_cli`, `is_test`, `is_library`, `database: DatabaseSettings \| None`) derived from package metadata and environment variables; `from_package()` populates `metadata` from `importlib.metadata` and `database` from `{env_prefix}DB_*` env vars when `{env_prefix}DB_URL` is present |
| **di** | Dependency injection | `locate_subclasses(cls, *, context=None)`, `locate_implementations(cls, *, context=None)`, `load_modules(*, context=None)`, `discover_plugin_packages`, `clear_caches`, `PLUGIN_ENTRY_POINT_GROUP` for plugin and subclass discovery |
| **health** | Service health checks | `Health` model and `HealthStatus` enum for tree-structured health status |
| **settings** | Pydantic settings loading | `OpaqueSettings`, `load_settings`, `strip_to_none_before_validator`, `UNHIDE_SENSITIVE_INFO` for env-based settings with secret masking and user-friendly validation errors; `console`, `Panel`, and `Text` are imported lazily inside `load_settings` (error path only) |
Expand All @@ -41,16 +41,25 @@ This file provides an overview of all modules in `aignostics_foundry_core`, thei
application startup makes the context available everywhere in the library without threading values
through call sites. Tests pass an explicit context override and never touch global state.
- **Key Features**:
- `PackageMetadata(BaseModel)` — frozen; fields: `description: str = ""`,
`author_name: str | None = None`, `author_email: str | None = None`,
`repository_url: str = ""`, `documentation_url: str = ""`.
Constructor: `PackageMetadata.from_name(package_name)` — reads all five fields from
`importlib.metadata` (``Summary``, ``Author-email``, ``Project-URL``).
Defaults to empty values for direct construction (e.g. in tests).
- `FoundryContext(BaseModel)` — frozen; fields: `name`, `version`, `version_full`, `version_with_vcs_ref`, `environment`,
`env_file: list[Path]`, `repository_url`, `documentation_url`, `python_version` (Python runtime
version string, e.g. `"3.11.9"`), plus four runtime mode bool flags: `is_container`, `is_cli`,
`env_file: list[Path]`, `env_prefix`, `python_version` (Python runtime version string,
e.g. `"3.11.9"`), `metadata: PackageMetadata` (all package-derived fields: description,
author, URLs; populated by `from_package()` via `PackageMetadata.from_name()`; defaults to
empty `PackageMetadata()`), plus four runtime mode bool flags: `is_container`, `is_cli`,
`is_test`, `is_library` (all default `False`), and `database: DatabaseSettings | None`
(populated by `from_package()` when `{env_prefix}DB_URL` is set; `None` otherwise).
- `FoundryContext.from_package(package_name)` — classmethod that derives all values from
`importlib.metadata` and environment variables (`{NAME}_ENVIRONMENT`, `VCS_REF`, `COMMIT_SHA`,
`BUILDER`, `BUILD_DATE`, `CI_RUN_ID`, `CI_RUN_NUMBER`, `{NAME}_ENV_FILE`,
`{NAME}_RUNNING_IN_CONTAINER`, `PYTEST_RUNNING_{NAME}`). Environment fallback chain:
`{NAME}_ENVIRONMENT` → `ENV` → `VERCEL_ENV` → `RAILWAY_ENVIRONMENT` → `"local"`.
Calls `PackageMetadata.from_name(package_name)` to populate `ctx.metadata`.
Also checks `{NAME}_DB_URL`: when present, constructs `DatabaseSettings(_env_prefix="{NAME}_DB_")`
and stores it in `ctx.database`; otherwise `ctx.database` is `None`.
- `set_context(ctx)` — installs *ctx* as the process-level singleton.
Expand Down Expand Up @@ -126,10 +135,10 @@ This file provides an overview of all modules in `aignostics_foundry_core`, thei
- `API_TAG_PUBLIC`, `API_TAG_AUTHENTICATED`, `API_TAG_ADMIN`, `API_TAG_INTERNAL`, `API_TAG_INTERNAL_ADMIN` — string constants for OpenAPI tagging
- `create_public_router(module_tag, *, version, prefix, …)` — public (unauthenticated) router
- `create_authenticated_router`, `create_admin_router`, `create_internal_router`, `create_internal_admin_router` — router factories that inject the appropriate `require_*` dependency from `api.auth`
- `build_api_metadata(title, description, author_name, author_email, repository_url, documentation_url, version)` — returns a `dict` suitable for `FastAPI(**metadata)`
- `build_versioned_api_tags(version_name, repository_url)` — OpenAPI tags for a single versioned sub-app
- `build_api_metadata(version=None, *, context=None)` — returns a `dict` suitable for `FastAPI(**metadata)`; derives title, description, author, and URLs from *context* (falls back to global context)
- `build_versioned_api_tags(version_name, *, context=None)` — OpenAPI tags for a single versioned sub-app; reads `repository_url` from *context*
- `build_root_api_tags(base_url, versions)` — OpenAPI tags for the root app linking to each version's docs
- `get_versioned_api_instances(versions, build_metadata=None, *, context=None)` — loads project modules (resolved via context), creates one `FastAPI` per version, routes registered `VersionedAPIRouter` instances to the matching version
- `get_versioned_api_instances(versions, *, context=None)` — loads project modules (resolved via context), calls `build_api_metadata(context=ctx)` to configure each `FastAPI` instance, routes registered `VersionedAPIRouter` instances to the matching version
- `init_api(root_path, lifespan, exception_handler_registrations, versions=None, version_exception_handler_registrations=None, **fastapi_kwargs)` — creates a `FastAPI` with the standard Foundry exception handlers (`ApiException`, `RequestValidationError`, `ValidationError`, `Exception`) pre-registered; when *versions* is supplied, calls `get_versioned_api_instances` internally, optionally applies *version_exception_handler_registrations* to each sub-app, and mounts them at `/{version}` on the root app
- **Location**: `aignostics_foundry_core/api/core.py`
- **Dependencies**: `fastapi>=0.110,<1` (mandatory); `aignostics_foundry_core.di` (`load_modules`)
Expand Down
57 changes: 26 additions & 31 deletions src/aignostics_foundry_core/api/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,58 +262,53 @@ def create_internal_admin_router(
return cast("APIRouter", VersionedAPIRouter(version, prefix=actual_prefix, tags=tags, dependencies=dependencies))


def build_api_metadata( # noqa: PLR0913, PLR0917
title: str,
description: str = "",
author_name: str = "",
author_email: str = "",
repository_url: str = "",
documentation_url: str = "",
version: str | None = None,
) -> dict[str, Any]:
def build_api_metadata(version: str | None = None, *, context: FoundryContext | None = None) -> dict[str, Any]:
"""Build a metadata dictionary suitable for passing to a FastAPI instance.

All fields (title, description, author, URLs) are derived from *context*.

Args:
title: The API title (project name).
description: Human-readable description of the API.
author_name: Contact person or team name.
author_email: Contact email address.
repository_url: URL to the source repository.
documentation_url: URL to the documentation or terms of service.
version: Optional API version string.
context: Project context supplying the title, description, author, and URLs.
When ``None``, the global context installed via
:func:`aignostics_foundry_core.foundry.set_context` is used.

Returns:
Dictionary containing FastAPI metadata keys.
"""
ctx = context or get_context()
metadata: dict[str, Any] = {
"title": title,
"description": description,
"title": ctx.name,
"description": ctx.metadata.description,
"contact": {
"name": author_name or "Unknown",
"email": author_email,
"url": repository_url,
"name": ctx.metadata.author_name or "Unknown",
"email": ctx.metadata.author_email or "",
"url": ctx.metadata.repository_url,
},
"terms_of_service": documentation_url,
"terms_of_service": ctx.metadata.documentation_url,
"license_info": {
"name": "Aignostics Commercial License",
"url": f"{repository_url}/blob/main/LICENSE",
"url": f"{ctx.metadata.repository_url}/blob/main/LICENSE",
},
}
if version is not None:
metadata["version"] = version
return metadata


def build_versioned_api_tags(version_name: str, repository_url: str = "") -> list[dict[str, Any]]:
def build_versioned_api_tags(version_name: str, *, context: FoundryContext | None = None) -> list[dict[str, Any]]:
"""Build ``openapi_tags`` for a versioned API instance.

Args:
version_name: The version name (e.g., "v1").
repository_url: URL to the source repository (used for external docs link).
context: Project context supplying the repository URL for the external docs link.
When ``None``, the global context installed via
:func:`aignostics_foundry_core.foundry.set_context` is used.

Returns:
List of OpenAPI tag dictionaries for the versioned API.
"""
repository_url = (context or get_context()).metadata.repository_url
return [
{
"name": version_name,
Expand Down Expand Up @@ -351,7 +346,6 @@ def build_root_api_tags(base_url: str, versions: list[str]) -> list[dict[str, An

def get_versioned_api_instances(
versions: list[str],
build_metadata: dict[str, Any] | None = None,
*,
context: FoundryContext | None = None,
) -> dict[str, FastAPI]:
Expand All @@ -364,18 +358,19 @@ def get_versioned_api_instances(

Args:
versions: Ordered list of API version names (e.g., ``["v1", "v2"]``).
build_metadata: Optional extra kwargs forwarded to each ``FastAPI()`` constructor.
context: Project context supplying the package name. When ``None``,
the global context installed via
:func:`aignostics_foundry_core.foundry.set_context` is used.
context: Project context supplying the package name, title, description, author,
and URLs for each ``FastAPI`` instance. When ``None``, the global context
installed via :func:`aignostics_foundry_core.foundry.set_context` is used.

Returns:
Mapping from version name to its configured ``FastAPI`` instance.
"""
from fastapi import FastAPI # noqa: PLC0415

load_modules(context=context or get_context())
api_instances: dict[str, FastAPI] = {version: FastAPI(**(build_metadata or {})) for version in versions}
ctx = context or get_context()
load_modules(context=ctx)
api_metadata = build_api_metadata(context=ctx)
api_instances: dict[str, FastAPI] = {version: FastAPI(**api_metadata) for version in versions}

for router in VersionedAPIRouter.get_instances():
router_version: str = cast("Any", router).version
Expand Down
Loading
Loading