use Codex 5.3 to review template to spec

This commit is contained in:
Jev
2026-02-14 11:47:18 +01:00
parent 1b66fecb9a
commit 46e0ef9de7
16 changed files with 277 additions and 123 deletions
@@ -4,9 +4,12 @@ from __future__ import annotations
from importlib.metadata import PackageNotFoundError, version
from {{ cookiecutter.package_name }}.config import get_config
from {{ cookiecutter.package_name }}.config.settings import Settings
try:
__version__ = version("{{ cookiecutter.project_slug }}")
except PackageNotFoundError: # pragma: no cover - only in editable/uninstalled mode
__version__ = "0.0.0"
__all__ = ["__version__"]
__all__ = ["Settings", "__version__", "get_config"]
@@ -1,6 +1,10 @@
from __future__ import annotations
import tomllib
from typing import NoReturn
import typer
from pydantic import ValidationError
from {{ cookiecutter.package_name }}.config import get_config
from {{ cookiecutter.package_name }}.config.settings import Settings
@@ -8,10 +12,13 @@ from {{ cookiecutter.package_name }}.config.settings import Settings
def load_config() -> Settings:
"""Load configuration once at the CLI boundary."""
return get_config()
try:
return get_config()
except (FileNotFoundError, tomllib.TOMLDecodeError, ValidationError) as exc:
exit_with_error(str(exc))
def exit_with_error(message: str, code: int = 1) -> None:
def exit_with_error(message: str, code: int = 1) -> NoReturn:
"""Print a concise error and exit with a non-zero status."""
typer.secho(message, fg=typer.colors.RED, err=True)
raise typer.Exit(code)
@@ -6,8 +6,8 @@ from typing import Annotated
import typer
from {{ cookiecutter.package_name }}.cli.common import exit_with_error
from {{ cookiecutter.package_name }}.config import get_config, get_config_source
from {{ cookiecutter.package_name }}.cli.common import exit_with_error, load_config
from {{ cookiecutter.package_name }}.config import get_config_source
from {{ cookiecutter.package_name }}.config.paths import config_file_path
from {{ cookiecutter.package_name }}.config.settings import write_default_config
@@ -17,12 +17,7 @@ app = typer.Typer(no_args_is_help=True)
@app.command()
def show() -> None:
"""Print the effective configuration and source path."""
try:
config = get_config()
except (FileNotFoundError, ValueError) as exc:
exit_with_error(str(exc))
return
config = load_config()
source = get_config_source()
payload = {
"source": str(source) if source else "<defaults>",
@@ -4,6 +4,7 @@ from typing import Annotated
import typer
from {{ cookiecutter.package_name }}.cli.common import load_config
from {{ cookiecutter.package_name }}.commands import format_greeting
app = typer.Typer(no_args_is_help=True)
@@ -12,6 +13,12 @@ app = typer.Typer(no_args_is_help=True)
@app.command()
def say(
name: Annotated[str, typer.Option("--name", "-n", help="Name to greet.")] = "World",
greeting: Annotated[
str | None,
typer.Option("--greeting", "-g", help="Greeting prefix; overrides config value."),
] = None,
) -> None:
"""Print a greeting."""
print(format_greeting(name))
config = load_config()
message = format_greeting(name=name, greeting=greeting or config.app.greeting)
print(message)
@@ -1,6 +1,6 @@
from __future__ import annotations
def format_greeting(name: str) -> str:
def format_greeting(name: str, greeting: str = "Hello") -> str:
"""Return a greeting message."""
return f"Hello, {name}!"
return f"{greeting}, {name}!"
@@ -4,15 +4,28 @@ import os
import tomllib
from pathlib import Path
from pydantic import BaseModel
from pydantic import BaseModel, Field
from .paths import ENV_CONFIG_VAR, config_file_path
from .paths import ENV_CONFIG_VAR, config_file_path, data_dir_path
class AppConfig(BaseModel):
model_config = {"frozen": True, "extra": "forbid"}
greeting: str = "Hello"
class DatabaseConfig(BaseModel):
model_config = {"frozen": True, "extra": "forbid"}
path: str = Field(default_factory=lambda: str(data_dir_path() / "data.db"))
class Settings(BaseModel):
model_config = {"frozen": True, "extra": "forbid"}
greeting: str = "Hello"
app: AppConfig = AppConfig()
database: DatabaseConfig = DatabaseConfig()
def load_settings() -> tuple[Settings, Path | None]:
@@ -43,23 +56,43 @@ def write_default_config(path: Path) -> None:
def _to_toml(payload: dict[str, object]) -> str:
lines: list[str] = []
for section, values in payload.items():
if isinstance(values, dict):
lines.append(f"[{section}]")
for key, value in values.items():
lines.append(f"{key} = {_toml_value(value)}")
lines.append("")
for key, value in payload.items():
if isinstance(value, dict):
_write_table(lines, key, value)
else:
lines.append(f"{section} = {_toml_value(values)}")
lines.append(f"{key} = {_toml_value(value)}")
return "\n".join(lines).rstrip() + "\n"
def _write_table(lines: list[str], table_name: str, values: dict[str, object]) -> None:
lines.append(f"[{table_name}]")
scalar_items: list[tuple[str, object]] = []
nested_items: list[tuple[str, dict[str, object]]] = []
for key, value in values.items():
if isinstance(value, dict):
nested_items.append((key, value))
else:
scalar_items.append((key, value))
for key, value in scalar_items:
lines.append(f"{key} = {_toml_value(value)}")
for key, nested in nested_items:
lines.append("")
_write_table(lines, f"{table_name}.{key}", nested)
lines.append("")
def _toml_value(value: object) -> str:
if isinstance(value, str):
escaped = value.replace("\\", "\\\\").replace('"', "\\\"")
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
return f'"{escaped}"'
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, (int, float)):
if isinstance(value, int | float):
return str(value)
raise TypeError(f"Unsupported TOML value: {value!r}")