actionable-errors 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- actionable_errors-0.1.0/.envrc +65 -0
- actionable_errors-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- actionable_errors-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- actionable_errors-0.1.0/.github/pull_request_template.md +19 -0
- actionable_errors-0.1.0/.github/secret_scanning.yml +2 -0
- actionable_errors-0.1.0/.github/workflows/ci.yml +54 -0
- actionable_errors-0.1.0/.github/workflows/publish.yml +58 -0
- actionable_errors-0.1.0/.gitignore +36 -0
- actionable_errors-0.1.0/.pre-commit-config.yaml +13 -0
- actionable_errors-0.1.0/CODE_OF_CONDUCT.md +40 -0
- actionable_errors-0.1.0/CONTRIBUTING.md +142 -0
- actionable_errors-0.1.0/LICENSE +21 -0
- actionable_errors-0.1.0/PKG-INFO +131 -0
- actionable_errors-0.1.0/README.md +105 -0
- actionable_errors-0.1.0/SECURITY.md +28 -0
- actionable_errors-0.1.0/docs/ARCHITECTURE.md +93 -0
- actionable_errors-0.1.0/docs/PUBLISHING.md +71 -0
- actionable_errors-0.1.0/pyproject.toml +111 -0
- actionable_errors-0.1.0/src/actionable_errors/__init__.py +28 -0
- actionable_errors-0.1.0/src/actionable_errors/classifier.py +61 -0
- actionable_errors-0.1.0/src/actionable_errors/error.py +227 -0
- actionable_errors-0.1.0/src/actionable_errors/guidance.py +49 -0
- actionable_errors-0.1.0/src/actionable_errors/py.typed +0 -0
- actionable_errors-0.1.0/src/actionable_errors/result.py +92 -0
- actionable_errors-0.1.0/src/actionable_errors/sanitizer.py +149 -0
- actionable_errors-0.1.0/src/actionable_errors/types.py +25 -0
- actionable_errors-0.1.0/tests/__init__.py +1 -0
- actionable_errors-0.1.0/tests/test_classifier.py +275 -0
- actionable_errors-0.1.0/tests/test_error.py +589 -0
- actionable_errors-0.1.0/tests/test_guidance.py +235 -0
- actionable_errors-0.1.0/tests/test_result.py +435 -0
- actionable_errors-0.1.0/tests/test_sanitizer.py +374 -0
- actionable_errors-0.1.0/tests/test_types.py +291 -0
- actionable_errors-0.1.0/uv.lock +575 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Auto-activate uv virtual environment
|
|
4
|
+
if ! has uv; then
|
|
5
|
+
echo "❌ uv is not installed. Please install it first:"
|
|
6
|
+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# Create virtual environment if it doesn't exist
|
|
11
|
+
if [[ ! -d ".venv" ]]; then
|
|
12
|
+
echo "📦 Creating virtual environment with uv..."
|
|
13
|
+
uv venv
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Activate the virtual environment
|
|
17
|
+
source .venv/bin/activate
|
|
18
|
+
# Source .envrc.local if present (for user/machine-specific settings)
|
|
19
|
+
if [[ -f ".envrc.local" ]]; then
|
|
20
|
+
echo "🔒 Sourcing .envrc.local..."
|
|
21
|
+
source .envrc.local
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Sync dependencies (including dev dependencies)
|
|
25
|
+
echo "🔄 Syncing dependencies..."
|
|
26
|
+
# Use --all-extras for maximum compatibility with any dependency structure
|
|
27
|
+
# (works with both dependency-groups and optional-dependencies)
|
|
28
|
+
uv sync --all-extras
|
|
29
|
+
|
|
30
|
+
# Git configuration for this project (optional)
|
|
31
|
+
# Uncomment and customize as needed or add to .envrc.local:
|
|
32
|
+
# git config user.name "Your Name"
|
|
33
|
+
# git config user.email "your.email@example.com"
|
|
34
|
+
|
|
35
|
+
# Install pre-commit hooks if they don't exist
|
|
36
|
+
if [[ -f ".pre-commit-config.yaml" ]] && ! uv run pre-commit --version >/dev/null 2>&1; then
|
|
37
|
+
echo "🪝 Installing pre-commit hooks..."
|
|
38
|
+
uv run pre-commit install
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Export environment variables
|
|
42
|
+
export PYTHONPATH="${PWD}/src:${PYTHONPATH}"
|
|
43
|
+
export UV_PROJECT_ENVIRONMENT="${PWD}/.venv"
|
|
44
|
+
|
|
45
|
+
echo "✅ Environment activated!"
|
|
46
|
+
echo "📁 Virtual environment: ${VIRTUAL_ENV}"
|
|
47
|
+
echo "🐍 Python: $(python --version)"
|
|
48
|
+
echo "📦 uv: $(uv --version)"
|
|
49
|
+
|
|
50
|
+
# Show available commands
|
|
51
|
+
echo ""
|
|
52
|
+
echo "🚀 Available commands:"
|
|
53
|
+
echo " uv run pytest # Run tests"
|
|
54
|
+
echo " uv run pytest --cov # Run tests with coverage report"
|
|
55
|
+
echo " uv run pytest --cov --cov-report=html # Generate HTML coverage report"
|
|
56
|
+
echo " uv run ruff check # Lint code"
|
|
57
|
+
echo " uv run ruff format # Format code"
|
|
58
|
+
echo " uv run mypy src/ # Type check"
|
|
59
|
+
echo " uv run pre-commit run --all # Run all pre-commit hooks"
|
|
60
|
+
# Show available taskipy commands if task is available
|
|
61
|
+
if has task; then
|
|
62
|
+
echo ""
|
|
63
|
+
echo "📝 Available taskipy commands (via 'task'):"
|
|
64
|
+
task --list
|
|
65
|
+
fi
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug Report
|
|
3
|
+
about: Something isn't working as expected
|
|
4
|
+
title: ""
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Who / What / Why
|
|
10
|
+
|
|
11
|
+
- **WHO:** Who is affected? (e.g., library consumer, AI agent, operator)
|
|
12
|
+
- **WHAT:** What behavior is broken or unexpected?
|
|
13
|
+
- **WHY:** Why does it matter? What's the impact?
|
|
14
|
+
|
|
15
|
+
## Steps to Reproduce
|
|
16
|
+
|
|
17
|
+
1. ...
|
|
18
|
+
2. ...
|
|
19
|
+
3. ...
|
|
20
|
+
|
|
21
|
+
## Expected Behavior
|
|
22
|
+
|
|
23
|
+
What you expected to happen.
|
|
24
|
+
|
|
25
|
+
## Actual Behavior
|
|
26
|
+
|
|
27
|
+
What actually happened. Include the full error message if applicable.
|
|
28
|
+
|
|
29
|
+
## Environment
|
|
30
|
+
|
|
31
|
+
- OS: [e.g., Ubuntu 24.04]
|
|
32
|
+
- Python version: [e.g., 3.12.3]
|
|
33
|
+
- actionable-errors version: [e.g., 0.1.0]
|
|
34
|
+
- Install method: [pip, uv, from source]
|
|
35
|
+
|
|
36
|
+
## Additional Context
|
|
37
|
+
|
|
38
|
+
Any other context — stack traces, minimal reproduction script, etc.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature Request
|
|
3
|
+
about: Suggest a new feature or improvement
|
|
4
|
+
title: ""
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Who / What / Why
|
|
10
|
+
|
|
11
|
+
- **WHO:** Who needs this? (e.g., library consumer, AI agent, operator)
|
|
12
|
+
- **WHAT:** What capability or behavior is needed?
|
|
13
|
+
- **WHY:** Why does it matter? What problem does it solve?
|
|
14
|
+
|
|
15
|
+
## Proposed Solution
|
|
16
|
+
|
|
17
|
+
How do you think it should work?
|
|
18
|
+
|
|
19
|
+
## Zero-Dependency Constraint
|
|
20
|
+
|
|
21
|
+
Can this be implemented with stdlib only? If not, explain why an exception
|
|
22
|
+
is warranted and what the dependency cost would be to consumers.
|
|
23
|
+
|
|
24
|
+
## Alternatives Considered
|
|
25
|
+
|
|
26
|
+
Any other approaches you thought about and why they're less ideal.
|
|
27
|
+
|
|
28
|
+
## Additional Context
|
|
29
|
+
|
|
30
|
+
Anything else — code samples, related issues, etc.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Pull Request
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
<!-- One paragraph: what this PR does and why -->
|
|
5
|
+
|
|
6
|
+
## Changes
|
|
7
|
+
<!-- Bullet list of changes -->
|
|
8
|
+
|
|
9
|
+
## Testing
|
|
10
|
+
<!-- How were these changes verified? -->
|
|
11
|
+
- [ ] All existing tests pass (`task check`)
|
|
12
|
+
- [ ] New tests added for new behavior
|
|
13
|
+
- [ ] Coverage target maintained (100%)
|
|
14
|
+
|
|
15
|
+
## Checklist
|
|
16
|
+
- [ ] `from __future__ import annotations` at top of every new module
|
|
17
|
+
- [ ] mypy strict passes
|
|
18
|
+
- [ ] ruff lint + format passes
|
|
19
|
+
- [ ] No runtime dependencies added (stdlib only)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python 3.12
|
|
23
|
+
run: uv python install 3.12
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: uv sync --extra dev
|
|
27
|
+
|
|
28
|
+
- name: Lint with ruff
|
|
29
|
+
run: uv run ruff check src/ tests/
|
|
30
|
+
|
|
31
|
+
- name: Type check with mypy
|
|
32
|
+
run: uv run mypy src/
|
|
33
|
+
|
|
34
|
+
- name: Run tests with coverage
|
|
35
|
+
run: uv run pytest --cov=actionable_errors --cov-report=term-missing --cov-report=json
|
|
36
|
+
|
|
37
|
+
- name: Extract coverage percentage
|
|
38
|
+
if: github.ref == 'refs/heads/main'
|
|
39
|
+
run: |
|
|
40
|
+
COVERAGE=$(python -c "import json; print(int(json.load(open('coverage.json'))['totals']['percent_covered']))")
|
|
41
|
+
echo "COVERAGE_PCT=$COVERAGE" >> $GITHUB_ENV
|
|
42
|
+
|
|
43
|
+
- name: Update coverage badge
|
|
44
|
+
if: github.ref == 'refs/heads/main'
|
|
45
|
+
uses: schneegans/dynamic-badges-action@v1.7.0
|
|
46
|
+
with:
|
|
47
|
+
auth: ${{ secrets.GIST_SECRET }}
|
|
48
|
+
gistID: ${{ vars.COVERAGE_GIST_ID }}
|
|
49
|
+
filename: actionable-errors-coverage.json
|
|
50
|
+
label: coverage
|
|
51
|
+
message: ${{ env.COVERAGE_PCT }}%
|
|
52
|
+
valColorRange: ${{ env.COVERAGE_PCT }}
|
|
53
|
+
minColorRange: 50
|
|
54
|
+
maxColorRange: 100
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write # Required for trusted publishing (OIDC)
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python 3.12
|
|
21
|
+
run: uv python install 3.12
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync --extra dev
|
|
25
|
+
|
|
26
|
+
- name: Lint
|
|
27
|
+
run: uv run ruff check src/ tests/
|
|
28
|
+
|
|
29
|
+
- name: Type check
|
|
30
|
+
run: uv run mypy src/
|
|
31
|
+
|
|
32
|
+
- name: Test
|
|
33
|
+
run: uv run pytest --cov=actionable_errors --cov-report=term-missing
|
|
34
|
+
|
|
35
|
+
- name: Build
|
|
36
|
+
run: uv build
|
|
37
|
+
|
|
38
|
+
- name: Upload dist artifacts
|
|
39
|
+
uses: actions/upload-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
needs: build
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
environment: pypi # GitHub Environment for deployment protection
|
|
48
|
+
permissions:
|
|
49
|
+
id-token: write # OIDC token for PyPI trusted publishing
|
|
50
|
+
steps:
|
|
51
|
+
- name: Download dist artifacts
|
|
52
|
+
uses: actions/download-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: dist
|
|
55
|
+
path: dist/
|
|
56
|
+
|
|
57
|
+
- name: Publish to PyPI
|
|
58
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# IDE
|
|
14
|
+
.vscode/
|
|
15
|
+
.idea/
|
|
16
|
+
|
|
17
|
+
# direnv
|
|
18
|
+
.direnv/
|
|
19
|
+
.envrc.local
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
24
|
+
|
|
25
|
+
# Coverage
|
|
26
|
+
.coverage
|
|
27
|
+
coverage.json
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# mypy / ruff / pytest caches
|
|
31
|
+
.mypy_cache/
|
|
32
|
+
.ruff_cache/
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
|
|
35
|
+
# Direnv local environment variables
|
|
36
|
+
.envrc.local
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.8.6
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
9
|
+
rev: v1.13.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: mypy
|
|
12
|
+
additional_dependencies: []
|
|
13
|
+
args: [--strict]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to a positive environment:
|
|
15
|
+
|
|
16
|
+
- Using welcoming and inclusive language
|
|
17
|
+
- Being respectful of differing viewpoints and experiences
|
|
18
|
+
- Gracefully accepting constructive criticism
|
|
19
|
+
- Focusing on what is best for the community
|
|
20
|
+
- Showing empathy towards other community members
|
|
21
|
+
|
|
22
|
+
Examples of unacceptable behavior:
|
|
23
|
+
|
|
24
|
+
- The use of sexualized language or imagery and unwelcome sexual attention
|
|
25
|
+
- Trolling, insulting/derogatory comments, and personal or political attacks
|
|
26
|
+
- Public or private harassment
|
|
27
|
+
- Publishing others' private information without explicit permission
|
|
28
|
+
- Other conduct which could reasonably be considered inappropriate
|
|
29
|
+
|
|
30
|
+
## Enforcement
|
|
31
|
+
|
|
32
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
33
|
+
reported to the project maintainer. All complaints will be reviewed and
|
|
34
|
+
investigated and will result in a response that is deemed necessary and
|
|
35
|
+
appropriate to the circumstances.
|
|
36
|
+
|
|
37
|
+
## Attribution
|
|
38
|
+
|
|
39
|
+
This Code of Conduct is adapted from the
|
|
40
|
+
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing to actionable-errors. This document
|
|
4
|
+
covers the development setup, coding standards, testing philosophy, and
|
|
5
|
+
PR process.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Development Setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Clone
|
|
13
|
+
git clone https://github.com/grimlor/actionable-errors.git
|
|
14
|
+
cd actionable-errors
|
|
15
|
+
|
|
16
|
+
# Install with dev dependencies (creates .venv automatically)
|
|
17
|
+
uv sync --extra dev
|
|
18
|
+
|
|
19
|
+
# Optional: auto-activate venv
|
|
20
|
+
direnv allow
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Running Checks
|
|
24
|
+
|
|
25
|
+
All checks must pass before submitting a PR:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
task check # runs lint → type → test
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or individually:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
task lint # ruff check src/ tests/
|
|
35
|
+
task format # ruff format src/ tests/
|
|
36
|
+
task type # mypy strict mode
|
|
37
|
+
task test # pytest -v
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Code Style
|
|
41
|
+
|
|
42
|
+
- **Python 3.12+** — use modern syntax (`X | Y` unions, `@dataclass`).
|
|
43
|
+
- **`from __future__ import annotations`** at the top of every module.
|
|
44
|
+
- **ruff** handles formatting and import sorting. Don't fight it.
|
|
45
|
+
- **mypy strict** — all functions need type annotations. No `Any` unless
|
|
46
|
+
you have a good reason and document it.
|
|
47
|
+
- **Line length:** 99 characters (configured in `pyproject.toml`).
|
|
48
|
+
- **Quote style:** double quotes.
|
|
49
|
+
|
|
50
|
+
## Zero-Dependency Constraint
|
|
51
|
+
|
|
52
|
+
This package has **zero runtime dependencies** — stdlib only. This is a
|
|
53
|
+
hard architectural constraint, not a preference. `actionable-errors` sits
|
|
54
|
+
at the bottom of every dependency tree. Adding a runtime dependency would
|
|
55
|
+
transitively infect every consumer.
|
|
56
|
+
|
|
57
|
+
Dev dependencies (ruff, mypy, pytest, etc.) are fine — they don't ship.
|
|
58
|
+
|
|
59
|
+
## Testing Standards
|
|
60
|
+
|
|
61
|
+
Tests are the living specification. Every test class documents a behavioral
|
|
62
|
+
requirement, not a code structure.
|
|
63
|
+
|
|
64
|
+
### Test Class Structure
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
class TestYourFeature:
|
|
68
|
+
"""
|
|
69
|
+
REQUIREMENT: One-sentence summary of the behavioral contract.
|
|
70
|
+
|
|
71
|
+
WHO: Who depends on this behavior (calling code, operator, AI agent)
|
|
72
|
+
WHAT: What the behavior is, including failure modes
|
|
73
|
+
WHY: What breaks if this contract is violated
|
|
74
|
+
|
|
75
|
+
MOCK BOUNDARY:
|
|
76
|
+
Mock: nothing — this package is pure computation
|
|
77
|
+
Real: all classes and functions under test
|
|
78
|
+
Never: construct expected output and assert on the construction
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def test_descriptive_name_of_scenario(self) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Given some precondition
|
|
84
|
+
When an action is taken
|
|
85
|
+
Then an observable outcome occurs
|
|
86
|
+
"""
|
|
87
|
+
...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Key Principles
|
|
91
|
+
|
|
92
|
+
1. **Mock I/O boundaries, not implementation.** Since this package has no
|
|
93
|
+
I/O (stdlib only, no network, no filesystem), most tests will have
|
|
94
|
+
`Mock: nothing` in their mock boundary contract.
|
|
95
|
+
|
|
96
|
+
2. **Failure specs matter.** For every happy path, ask: what goes wrong?
|
|
97
|
+
Write specs for those failure modes. An unspecified failure is an
|
|
98
|
+
unhandled failure.
|
|
99
|
+
|
|
100
|
+
3. **Missing spec = missing requirement.** If you find a bug, the first
|
|
101
|
+
step is always adding the test that should have caught it, then fixing
|
|
102
|
+
the code to pass that test.
|
|
103
|
+
|
|
104
|
+
4. **Every assertion includes a diagnostic message.** Bare assertions are
|
|
105
|
+
not permitted.
|
|
106
|
+
|
|
107
|
+
## Architecture
|
|
108
|
+
|
|
109
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the three-audience
|
|
110
|
+
error design philosophy and module responsibilities.
|
|
111
|
+
|
|
112
|
+
## Commit Messages
|
|
113
|
+
|
|
114
|
+
Use clear, imperative commit messages:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Add credential sanitizer with configurable patterns
|
|
118
|
+
|
|
119
|
+
- Eight built-in regex patterns for common credential formats
|
|
120
|
+
- Consumer-extensible pattern registration
|
|
121
|
+
- 15 tests covering all built-in patterns and custom registration
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Pull Requests
|
|
125
|
+
|
|
126
|
+
1. **Branch from `main`.**
|
|
127
|
+
2. **All checks must pass** — `task check` (lint + type + test).
|
|
128
|
+
3. **Include tests** for any new behavior or bug fix.
|
|
129
|
+
4. **One concern per PR** — don't mix a new feature with unrelated refactoring.
|
|
130
|
+
5. **No runtime dependencies** — this is a hard constraint.
|
|
131
|
+
6. **Describe what and why** in the PR description.
|
|
132
|
+
|
|
133
|
+
## Reporting Issues
|
|
134
|
+
|
|
135
|
+
When filing an issue:
|
|
136
|
+
|
|
137
|
+
- **Bug:** Include the error message, what you expected, and steps to
|
|
138
|
+
reproduce. Include the Python version and how actionable-errors was
|
|
139
|
+
installed.
|
|
140
|
+
- **Feature request:** Describe the problem you're trying to solve, not
|
|
141
|
+
just the solution you have in mind. Note whether it can be done with
|
|
142
|
+
stdlib only.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 grimlor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: actionable-errors
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Three-audience error framework: typed errors for code, actionable suggestions for humans, tool guidance for AI agents
|
|
5
|
+
Project-URL: Repository, https://github.com/grimlor/actionable-errors
|
|
6
|
+
Author: grimlor
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: mypy<2,>=1.13; extra == 'dev'
|
|
19
|
+
Requires-Dist: pre-commit<5,>=4; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov<7,>=6; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest<9,>=8; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff<1,>=0.8; extra == 'dev'
|
|
24
|
+
Requires-Dist: taskipy<2,>=1.14; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# actionable-errors
|
|
28
|
+
|
|
29
|
+
Three-audience error framework for Python.
|
|
30
|
+
|
|
31
|
+
Every `ActionableError` speaks to three audiences simultaneously:
|
|
32
|
+
|
|
33
|
+
| Audience | Uses | Provided by |
|
|
34
|
+
|----------|------|-------------|
|
|
35
|
+
| **Calling code** | Typed `ErrorType` for routing (retry? escalate? ignore?) | `ErrorType(StrEnum)` — 8 base categories |
|
|
36
|
+
| **Human operator** | `suggestion` + `Troubleshooting` steps | Frozen dataclasses with actionable text |
|
|
37
|
+
| **AI agent** | `AIGuidance` with concrete next tool calls | Frozen dataclass with tool suggestions |
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install actionable-errors
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Zero runtime dependencies** — stdlib only. Sits at the bottom of every dependency tree.
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from actionable_errors import (
|
|
51
|
+
ActionableError,
|
|
52
|
+
AIGuidance,
|
|
53
|
+
ToolResult,
|
|
54
|
+
from_exception,
|
|
55
|
+
sanitize,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Domain-specific factory with built-in defaults
|
|
59
|
+
error = ActionableError.authentication(
|
|
60
|
+
service="Azure DevOps",
|
|
61
|
+
raw_error="401 Unauthorized",
|
|
62
|
+
)
|
|
63
|
+
print(error.suggestion) # "Check your credentials and try again."
|
|
64
|
+
print(error.ai_guidance) # AIGuidance(action_required="Re-authenticate", command="az login")
|
|
65
|
+
|
|
66
|
+
# Auto-classify any exception
|
|
67
|
+
try:
|
|
68
|
+
raise ConnectionError("Connection refused")
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
ae = from_exception(exc, service="Kusto", operation="query")
|
|
71
|
+
print(ae.error_type) # "connection"
|
|
72
|
+
|
|
73
|
+
# Typed result envelope for MCP tool responses
|
|
74
|
+
result = ToolResult.ok(data={"items": 42})
|
|
75
|
+
result = ToolResult.fail(error=error) # extracts error_type + suggestion
|
|
76
|
+
|
|
77
|
+
# Credential sanitization
|
|
78
|
+
clean = sanitize('password="hunter2" token=abc123')
|
|
79
|
+
# → 'password="***" token=***'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Extending ErrorType
|
|
83
|
+
|
|
84
|
+
Python `StrEnum` can't be subclassed once it has members, so extend via composition:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from enum import StrEnum
|
|
88
|
+
from actionable_errors import ActionableError
|
|
89
|
+
|
|
90
|
+
class RAGErrorType(StrEnum):
|
|
91
|
+
EMBEDDING = "embedding"
|
|
92
|
+
INDEX = "index"
|
|
93
|
+
|
|
94
|
+
error = ActionableError(
|
|
95
|
+
error="Vector store unavailable",
|
|
96
|
+
error_type=RAGErrorType.INDEX,
|
|
97
|
+
service="pinecone",
|
|
98
|
+
suggestion="Check Pinecone cluster status.",
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Factories
|
|
103
|
+
|
|
104
|
+
Eight domain-specific factories with sensible defaults:
|
|
105
|
+
|
|
106
|
+
| Factory | Key Parameters |
|
|
107
|
+
|---------|---------------|
|
|
108
|
+
| `.authentication(service, raw_error)` | Default suggestion + AI guidance |
|
|
109
|
+
| `.configuration(field_name, reason)` | — |
|
|
110
|
+
| `.connection(service, url, raw_error)` | — |
|
|
111
|
+
| `.timeout(service, operation, timeout_seconds)` | — |
|
|
112
|
+
| `.permission(service, resource, raw_error)` | — |
|
|
113
|
+
| `.validation(service, field_name, reason)` | — |
|
|
114
|
+
| `.not_found(service, resource_type, resource_id, raw_error)` | — |
|
|
115
|
+
| `.internal(service, operation, raw_error)` | — |
|
|
116
|
+
|
|
117
|
+
All factories accept optional `suggestion`, `ai_guidance`, and `troubleshooting` kwargs.
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Install dev dependencies
|
|
123
|
+
uv sync --extra dev
|
|
124
|
+
|
|
125
|
+
# Run all checks
|
|
126
|
+
task check # lint → type → test (90 tests, 100% coverage)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|