free-claude-code/api/admin_config/sources.py
Ali Khokhar 51157f91bd
Refactor admin config into catalog-driven package (#926)
## Problem

Admin config was a single responsibility hub with manually duplicated
provider metadata. Provider labels, fields, template loading,
validation, persistence, and status lived in one place.

## Changes

| Before | After |
| --- | --- |
| Admin config lived in one large `api/admin_config.py` module. | Admin
config lives in package modules for manifest, sources, values,
validation, persistence, and status. |
| Provider admin fields and UI labels were manually duplicated. |
Provider admin fields and display names derive from `PROVIDER_CATALOG`
with admin-only help overrides. |
| `fcc-init` and Admin UI loaded `.env.example` separately. | `fcc-init`
and Admin UI use shared `config.env_template` loading. |
| Architecture docs pointed to the old admin config module. |
Architecture docs describe the package owners and catalog-driven
provider manifest. |

<!-- greptile_comment -->

<details open><summary><h3>Greptile Summary</h3></summary>

This PR refactors admin configuration into a catalog-driven package. The
main changes are:

- Split the former monolithic `api/admin_config.py` into manifest,
source loading, value presentation, validation, persistence, and
provider status modules.
- Generate provider admin fields and display names from
`PROVIDER_CATALOG` with admin-specific help overrides.
- Share `.env.example` loading between `fcc-init` and Admin UI defaults
through `config.env_template`.
- Update admin routes, Admin UI provider labels, architecture docs,
version metadata, and contract/API tests for the new module layout.
</details>

<h3>Confidence Score: 5/5</h3>

The refactor appears merge-safe with no code issues identified in the
reviewed changes.

The package split, catalog-driven provider metadata, shared environment
template loading, route updates, and tests/docs changes are cohesive and
covered by corresponding contract/API/CLI test updates.

<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>

**What T-Rex did**
- T-Rex ran manifest validation for catalog provider before and after
routes, capturing base and head responses and catalog-alignment checks,
and confirmed the validation completed successfully.
- T-Rex evaluated the shared-env-template scenarios, observing the
before run with no config.env\_template module and the after run with
the module present, with patched loader values and all consistency
checks passing, and the run exited with code 0.
- T-Rex executed the package-admin-workflow validation, verifying the
base and after import paths, the load/validate/write workflow produced
matching outputs, and the run completed with exit code 0.

<a
href="https://app.greptile.com/trex/runs/12529845/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=1"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"
height="32"></picture></a>

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>

<sub>Reviews (1): Last reviewed commit: ["Refactor admin config into
catalog-drive..."](d6239d7953)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=40315222)</sub>

<!-- /greptile_comment -->
2026-06-27 15:37:29 -07:00

81 lines
2.1 KiB
Python

"""Admin config source loading and source precedence."""
from __future__ import annotations
import os
from io import StringIO
from pathlib import Path
from typing import Literal
from dotenv import dotenv_values
from config.env_template import load_env_template_or_empty
from config.paths import managed_env_path
from .manifest import FIELDS
SourceType = Literal[
"default",
"template",
"repo_env",
"managed_env",
"explicit_env_file",
"process",
]
def repo_env_path() -> Path:
"""Return the repo-local env path."""
return Path(".env")
def explicit_env_path() -> Path | None:
"""Return the explicit FCC_ENV_FILE path, when configured."""
if explicit := os.environ.get("FCC_ENV_FILE"):
return Path(explicit)
return None
def configured_env_files() -> tuple[tuple[SourceType, Path], ...]:
"""Return dotenv files in low-to-high precedence order."""
files: list[tuple[SourceType, Path]] = [
("repo_env", repo_env_path()),
("managed_env", managed_env_path()),
]
if explicit := explicit_env_path():
files.append(("explicit_env_file", explicit))
return tuple(files)
def dotenv_values_from_text(text: str) -> dict[str, str]:
"""Parse dotenv text into string values."""
values = dotenv_values(stream=StringIO(text))
return {key: "" if value is None else value for key, value in values.items()}
def template_values() -> dict[str, str]:
"""Return .env.example values plus manifest defaults for newer fields."""
values = dotenv_values_from_text(load_env_template_or_empty())
for field in FIELDS:
values.setdefault(field.key, field.default)
return values
def dotenv_values_from_file(path: Path) -> dict[str, str]:
"""Return dotenv values from a file, or an empty mapping when absent."""
if not path.is_file():
return {}
values = dotenv_values(path)
return {key: "" if value is None else value for key, value in values.items()}
def is_locked_source(source: SourceType) -> bool:
"""Return whether an admin value source must not be overwritten."""
return source in {"process", "explicit_env_file"}