Skip to content
Merged
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
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
push:
branches: ["main"]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
- run: uv sync --all-extras
- run: uv run ruff check src/ tests/
- run: uv run mypy src/
- run: uv run pytest --cov=iolaus --cov-report=xml
- uses: codecov/codecov-action@v4
if: matrix.python-version == '3.14'
with:
token: ${{ secrets.CODECOV_TOKEN }}
26 changes: 26 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Docs

on:
push:
branches: ["main"]

permissions:
pages: write
id-token: write

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --group docs
- run: uv run mkdocs build --strict
- uses: actions/upload-pages-artifact@v3
with:
path: site/
- id: deployment
uses: actions/deploy-pages@v4
39 changes: 39 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish

on:
push:
branches: ["main"]

permissions:
contents: write
id-token: write # required for PyPI Trusted Publishing

jobs:
bump-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- uses: astral-sh/setup-uv@v5

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Bump patch version
run: |
uv add --dev bump-my-version
uv run bump-my-version bump patch --commit --tag

- name: Push commit and tag
run: git push --follow-tags

- name: Build
run: uv build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ cython_debug/
.abstra/

# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/

Expand Down
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.8
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-toml
- id: check-yaml
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]

- Initial project scaffolding
61 changes: 61 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Iolaus — Claude Code Guide

## Project overview

Iolaus is a lightweight Python framework for research data analysis projects. It wires together Dynaconf (config), Typer (CLI), and a custom run-logging system into a single decorator-based API. Every command run produces a timestamped output directory with a merged config snapshot and log file.

## Tech stack

- **Python >= 3.11** (targets 3.11–3.14)
- **Dynaconf** — configuration management
- **Typer** — CLI framework
- **uv** — package/dependency management
- **Hatchling** — build backend
- **pytest** — testing
- **ruff** — linting and formatting
- **mypy** (strict mode) — type checking
- **MkDocs Material** — documentation
- **bump-my-version** — version bumping

## Repository layout

```
src/iolaus/ # Library source (src layout)
__init__.py # Public API exports
decorators.py # command() decorator factory
settings.py # Dynaconf helpers / config merging
logging.py # Run directory setup, logging, config snapshots
py.typed # PEP 561 marker
tests/ # pytest test suite
docs/ # MkDocs documentation pages
.github/workflows/ # CI and publish workflows
```

## Common commands

```bash
uv sync --all-extras # Install all dependencies
uv run pytest # Run tests
uv run pytest --cov=iolaus # Run tests with coverage
uv run ruff check src/ tests/ # Lint
uv run ruff format src/ tests/ # Format
uv run mypy src/ # Type check
uv run mkdocs serve # Local docs server
uv run pre-commit run --all-files # Run all pre-commit hooks
```

## Key design decisions

- **src/ layout** — prevents accidental local imports during testing
- **`__` separator** for nested config keys (Dynaconf native convention)
- **`settings`/`run_dir` injection** is opt-in: only injected if the function signature declares them
- **Decorator returns the unwrapped original function** so it can be unit-tested without Typer
- **`basicConfig(force=True)`** ensures each command invocation gets its own log handlers
- **Config snapshots** are JSON (human-readable, diff-friendly, no extra dependency)

## Coding conventions

- Follow ruff's selected rules: E, F, I, UP
- Use Google-style docstrings (for mkdocstrings compatibility)
- Type annotations on all public APIs (mypy strict mode)
- Tests use `tmp_path` fixtures for isolation — no leftover artifacts
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Iolaus

> **Warning:** This project is under active development and is not yet stable. APIs may change without notice.

A lightweight Python framework for research data analysis projects. Iolaus wires together [Dynaconf](https://www.dynaconf.com/), [Typer](https://typer.tiangolo.com/), and a custom run-logging system into a single decorator-based API that adds automatic configuration management and reproducible run artifacts on every invocation.

## Features

- **Decorator-based API** — feels like FastAPI/Typer, with zero boilerplate
- **Automatic config management** — merge base settings, extra config files, and CLI overrides
- **Reproducible run artifacts** — every command run produces a timestamped output directory with a config snapshot and log file
- **Opt-in injection** — `settings` and `run_dir` are only injected if your function declares them

## Quick example

```python
from pathlib import Path
from dynaconf import Dynaconf
import typer
from iolaus import command

app = typer.Typer()
settings = Dynaconf(settings_files=["settings.toml"], envvar_prefix="MYAPP")
cmd = command(app, settings)

@cmd
def analyze(
input: Path,
verbose: bool = False,
settings=None, # injected by Iolaus
run_dir: Path = None, # injected by Iolaus
):
"""Run the analysis pipeline."""
...

if __name__ == "__main__":
app()
```

```bash
# Base invocation
python cli.py analyze data.csv

# Merge an extra config file
python cli.py analyze data.csv --config prod.yaml

# Override individual keys
python cli.py analyze data.csv --set model__lr=0.01 --set db__host=remote
```

Every run produces:

```
outputs/
└── analyze/
└── 2025-03-29/
└── 14-32-05/
├── run.log
└── config.json
```

## Installation

```bash
pip install iolaus
```

Or with uv:

```bash
uv add iolaus
```

## Development

```bash
git clone https://github.com/vgreg/iolaus.git
cd iolaus
uv sync --all-extras
uv run pytest
```

## License

MIT
15 changes: 15 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# API Reference

## Decorators

::: iolaus.decorators.command

## Settings

::: iolaus.settings.build_settings

## Logging

::: iolaus.logging.setup_logging

::: iolaus.logging.save_config_snapshot
61 changes: 61 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# CLI Options

Iolaus automatically adds two CLI options to every decorated command.

## `--config`, `-c`

Merge an extra config file on top of the base settings.

```bash
python cli.py train --config prod.toml
python cli.py train -c prod.toml
```

The extra file is loaded and merged using Dynaconf. Keys in the extra file override matching keys in the base settings.

## `--set`, `-s`

Override individual config values directly from the command line.

```bash
python cli.py train --set model__lr=0.01
python cli.py train -s model__lr=0.01 -s db__host=remote
```

Use `__` (double underscore) as a separator for nested keys. For example, `model__lr=0.01` sets the `lr` key inside the `model` section.

Multiple `--set` flags can be used in a single invocation.

## Combining options

Both options can be used together. `--set` overrides take the highest precedence:

```bash
python cli.py train --config prod.toml --set model__lr=0.01
```

## Run artifacts

Every invocation creates a timestamped run directory:

```
outputs/
└── <command_name>/
└── YYYY-MM-DD/
└── HH-MM-SS/
├── run.log # log output
└── config.json # merged config snapshot
```

The output base directory defaults to `outputs/` but can be configured via the `output_dir` parameter on the `command()` factory.

## Injected parameters

Your command function can optionally declare these parameters to receive them:

| Parameter | Type | Description |
|-----------|------|-------------|
| `settings` | `Dynaconf` | The fully merged configuration object |
| `run_dir` | `Path` | Path to the current run's output directory |

Both are opt-in: only injected if declared in your function signature.
Loading
Loading