# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
# Install dependencies
uv sync --all-extras

# Run all tests
uv run pytest

# Run a single test file
uv run pytest tests/test_parser.py

# Run a single test
uv run pytest tests/test_parser.py::test_name

# Type check
uv run mypy . --exclude 'build/' --ignore-missing-imports

# Lint
uv run ruff check .

# Format
uv run ruff format .

# Generate Pydantic code from a YAML schema
uv run ezconfy generate <schema.yaml> <output.py>
```

## Architecture

**ezconfy** (`src/ezconfy/`) is a YAML-based configuration framework with a three-stage pipeline:

### Stage 1 — Schema Parsing (`schema_parser.py`)
`SchemaParser.parse(schema_yaml)` converts a YAML schema dict into Pydantic `BaseModel` types. It builds a dependency graph of custom types, topologically sorts them, then generates models, enums, or type aliases. The type DSL supports:
- Optionals (`type?`), defaults (`type = value`), unions (`A|B`), lists (`list[T]`)
- Enums: `[value1, value2]`
- Inheritance: `Child < Parent`
- External types: `module.path:ClassName` or `/path/to/file.py:ClassName`

### Stage 2 — Config Loading (`config_builder.py`)
`ConfigBuilder.from_files(files, schema_yaml)` reads one or more YAML files, deep-merges them in order (last file wins for scalar values), validates against the Pydantic model produced by `SchemaParser` (if a schema is given), then passes the result to `Instantiator`.

### Stage 3 — Instantiation (`instantiator.py`)
`Instantiator.__call__(config_dict)` resolves placeholders and instantiates Python objects. Special YAML keys:
- `_target_type_`: dotted class path or `/file.py:ClassName` — triggers object creation
- `_init_args_`: positional constructor arguments
- `_init_method_`: alternative constructor (e.g., `from_pretrained`)
- `${key}`, `${key.attr}`, `${key.method()}`: placeholder references to other config values

Object instantiation order is determined by a topological sort of the placeholder dependency graph.

### Supporting modules
- **`module_loader.py`** — `ModuleLoader.load_class()` resolves class paths (import string or file path) with caching
- **`cli.py`** — Typer CLI (`ezconfy` entry point); `generate` command emits a `.py` file with Pydantic models matching the schema
- **`io.py`** — `read_yaml()` thin wrapper around PyYAML safe loader

### Testing (`tests/`)
- `conftest.py` creates temporary Python modules on the fly for use in tests (fake datasets, models)
- `test_parser.py` covers `SchemaParser`
- `test_config_builder.py` covers `ConfigBuilder` end-to-end

### Tooling notes
- Ruff is configured for 120-char lines targeting Python 3.11
- MyPy runs in strict mode — all public APIs must be fully typed
- Pre-commit runs ruff lint, ruff format, and nbstripout
