This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
PrestoPlot is a Python library and CLI tool for text generation using YAML-based generative grammars. It supports idea generation, name generation, and story creation through a template system inspired by Tracery.
- Run tests:
./runtests.sh(uses uv and pytest with coverage) - Single test:
uv run python -m pytest tests/test_specific.py - Test with tox:
tox(tests across multiple Python versions)
- Lint code:
uv run ruff check src/ tests/ - Format code:
uv run ruff format src/ tests/ - Type checking: Not configured (but we do use type annotations, lightly checked by ruff)
- Install in development mode:
uv sync --all-groups - Build package:
uv build - Run CLI:
uv run presto --help
- Main CLI script:
prestocommand (defined insrc/prestoplot/cli.py) - Two main commands:
presto run <file.yaml>: Generate text from YAML grammar filespresto http <file.yaml>: Serve HTTP endpoint for text generation
Storage Layer (storages.py):
FileStorage: Loads YAML grammar files from filesystemCompilingFileStorage: Caches compiled grammars as MessagePack files
Grammar Processing (grammars.py):
- Parses YAML grammar files with
includesupport - Supports f-string and Jinja2 template rendering
- Handles recursive grammar includes
Story Rendering (story.py):
- Main entry point:
render_story(storage, name, start="Begin", seed=None, **kwargs) - Requires
Begin:stanza in grammar files
Context Management (contexts.py):
- Manages random seeds and generation context
- Provides deterministic generation with same seeds
Text Processing (texts.py):
- Template rendering engines (f-string and Jinja2)
- Text generation utilities
Additional Features:
markov.py: Markov chain text generationdb.py: Database utilities for grammar storagehttp.py: HTTP server for web-based generation
- YAML-based with Python f-string syntax
- Required
Begin:stanza as entry point - Support for
include:to compose grammar files - Modes:
reuse(default),pick(no replacement),markov(chain generation) - Seeded generation using keys (e.g.,
{Name.character1})
- Unit tests in
tests/directory - Test data in
tests/data/(sample YAML grammars) - Coverage reporting enabled by default
When asked to create a new convention (CLAUDE.md), add a second-level
heading section to this document, CLAUDE.md.
- Name the new convention heading with a short, descriptive title.
- Use the first line of the section to elaborate on the "When..." of the heading.
- Use bullet points to organize further details for the convention.
- Use full imperative sentences.
- Keep new conventions short and to the point.
- Use short examples for complex conventions.
When writing or editing Python code (*.py), follow these quality standards:
-
Use PEP8 style with CamelCase for class names and snake_case for variables/functions.
-
Include type annotations for all functions, methods, and complex structures.
-
Add Google Style docstrings to all packages, modules, functions, classes, and methods.
-
Run code quality tools:
- Format:
uv run ruff format - Lint:
uv run ruff check --fix
- Format:
When writing Python code (*.py), follow these testing practices:
-
Write tests first for each change using pytest.
-
Organize tests in a dedicated
tests/folder in the project root. -
Name test files by package and module, omitting the root
cloudmarchpackage name.- Example:
tests/test_config_loader.pytestssrc/cloudmarch/config/loader.py
- Example:
-
Use descriptive names for test functions and methods.
-
Group related tests in test classes.
-
Use fixtures for complex setup.
-
Aim for 100% test coverage for code under
src/. -
When writing tests, move common fixtures to
tests/conftest.py. -
Run tests with
./scripts/runtests.sh(which accepts normalpytestarguments and flags).- Example:
./scripts/runtests.sh tests/test_config_loader.py
- Example:
When organizing tests in pytest, group related tests using TestX classes:
- Use
TestXclasses to group tests for the same module, function, or behavior. - Name test classes with descriptive titles like
TestGrammarParserorTestFileStorage. - Do not inherit from
unittest.TestCasesince pytest handles plain classes. - Place setup and teardown logic in
setup_methodandteardown_method. - Example:
class TestGrammarParser: @pytest.fixture def parser(self) -> GrammarParser: return GrammarParser() def test_parses_simple_grammar(self, parser: GrammarParser) -> None: result = parser.parse("Begin: hello") assert result["Begin"] == ["hello"]
When writing unit tests for Python libraries, follow these pytest best practices:
- Test public APIs and behaviors, not implementation details.
- Focus on testing function contracts: inputs, outputs, and side effects.
- Use pytest's built-in
assertstatements rather than unittest-style assertions. - Structure tests with arrange-act-assert pattern for clarity.
- Test edge cases: empty inputs, None values, boundary conditions, and error states.
- Use parametrized tests for testing multiple similar cases:
@pytest.mark.parametrize("input_val,expected", [(1, 2), (3, 4)]) def test_increment(input_val, expected): assert increment(input_val) == expected
- Mock external dependencies using
pytest-mockorunittest.mock. - Test exception handling explicitly with
pytest.raises():def test_raises_value_error(): with pytest.raises(ValueError, match="invalid input"): parse_config("bad_input")
- Use fixtures for test data and setup, preferring function-scoped fixtures.
- Test one behavior per test function to maintain clarity and isolation.
- Avoid testing private methods directly; test through public interfaces.
- Do not test third-party library functionality; focus on your code's usage of it.
When tests fail during development, always fix them immediately:
- Stop all development work until failing tests are addressed.
- Identify the root cause of test failures before making changes.
- Fix the underlying issue rather than updating tests to match broken behavior.
- Ensure all tests pass before continuing with new development.
- Run the full test suite after fixes to prevent regression.
- Update mocks, test data, or test logic only when the intended behavior has genuinely changed.
- Never ignore or skip failing tests without explicit justification.
When naming variables in Python code, follow these naming practices:
- Use concise but descriptive variable names that clearly indicate purpose.
- Avoid single-character names except in the simplest comprehensions.
- Follow snake_case for all variables and functions.
- Use plural forms for collections and singular for items.
- Prefix boolean variables with verbs like
is_,has_, orshould_.
When raising exceptions in Python code, follow these practices:
-
Do not raise
Exception,RuntimeError, or any built-in base exception. -
Define specific exceptions in
src/cloudmarch/exceptions.py. -
Use
raise NewError from original_errorfor context chaining. -
Avoid interpolated strings in exception messages.
-
Attach context as explicit parameters to exception classes.
-
Example:
try: ... except ValueError as exc: raise MissingBucketError("S3 bucket missing", bucket_name) from exc
When using TYPE_CHECKING imports in Python:
-
Always include
# pragma: no coverto exclude them from test coverage. -
Place all type-only imports inside the block.
-
Example:
from typing import TYPE_CHECKING if TYPE_CHECKING: # pragma: no cover from cloudmarch.config.types import DeployConfig
When writing Python code with untestable defensive programming constructs:
-
Use
# pragma: no coverfor lines that cannot be practically tested. -
Use
# pragma: no branchfor branch conditions that cannot be practically tested. -
Apply pragmas to defensive re-raises, impossible conditions, and safety checks.
-
Examples:
except DeploymentError: raise # pragma: no cover - defensive re-raise if some_impossible_condition: # pragma: no branch raise RuntimeError("This should never happen") except Exception as exc: if isinstance(exc, SpecificError): # pragma: no branch raise # pragma: no cover
When committing changes to the repository, use conventional commit format:
- Use the format:
<type>(<scope>): <description> - Common types:
feat,fix,docs,style,refactor,test,chore - Keep the first line under 50 characters
- Use present tense imperative mood ("add feature" not "added feature")
- Examples:
feat(cli): add new grammar validation commandfix(storage): handle missing YAML files gracefullydocs: update installation instructionstest(grammars): add tests for include functionality