Commit da134e8559f6
Changed files (12)
dots
.config
claude
skills
Python
dots/.config/claude/skills/Python/tools/python-check
@@ -0,0 +1,99 @@
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "ruff>=0.8.0",
+# "mypy>=1.13.0",
+# "pytest>=8.0.0",
+# "pytest-cov>=6.0.0",
+# ]
+# ///
+"""Run all Python quality checks: lint, type check, and tests.
+
+This comprehensive check runs:
+1. ruff check (linting)
+2. ruff format --check (formatting)
+3. mypy (type checking)
+4. pytest with coverage (testing)
+"""
+
+import subprocess
+import sys
+from pathlib import Path
+
+
+def run_check(name: str, cmd: list[str]) -> tuple[bool, int]:
+ """Run a check command.
+
+ Args:
+ name: Name of the check
+ cmd: Command and arguments to run
+
+ Returns:
+ Tuple of (success, exit_code)
+ """
+ print(f"\n{'='*60}")
+ print(f"Running: {name}")
+ print(f"{'='*60}")
+ print(f"Command: {' '.join(cmd)}\n")
+
+ result = subprocess.run(cmd, check=False)
+ success = result.returncode == 0
+
+ if success:
+ print(f"\n✓ {name} passed")
+ else:
+ print(f"\n✗ {name} failed")
+
+ return success, result.returncode
+
+
+def main() -> int:
+ """Run all quality checks.
+
+ Returns:
+ Exit code (0 if all checks pass, 1 if any fail)
+ """
+ target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
+
+ if not target.exists():
+ print(f"Error: {target} does not exist", file=sys.stderr)
+ return 1
+
+ checks: list[tuple[str, list[str]]] = [
+ ("Linting (ruff check)", ["ruff", "check", str(target)]),
+ ("Formatting (ruff format)", ["ruff", "format", "--check", str(target)]),
+ ("Type checking (mypy)", ["mypy", str(target)]),
+ ("Tests (pytest)", ["pytest", "--cov", str(target)]),
+ ]
+
+ results: list[tuple[str, bool, int]] = []
+
+ for name, cmd in checks:
+ success, code = run_check(name, cmd)
+ results.append((name, success, code))
+
+ # Print summary
+ print(f"\n{'='*60}")
+ print("Summary")
+ print(f"{'='*60}")
+
+ all_passed = True
+ for name, success, code in results:
+ status = "✓ PASS" if success else "✗ FAIL"
+ print(f"{status} - {name}")
+ if not success:
+ all_passed = False
+
+ print(f"{'='*60}\n")
+
+ if all_passed:
+ print("✓ All checks passed!")
+ return 0
+ else:
+ print("✗ Some checks failed")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
dots/.config/claude/skills/Python/tools/python-lint
@@ -0,0 +1,61 @@
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "ruff>=0.8.0",
+# ]
+# ///
+"""Run ruff linter and formatter on Python code.
+
+This script runs both ruff check and ruff format on the specified directory
+or the current directory if none is provided.
+"""
+
+import subprocess
+import sys
+from pathlib import Path
+
+
+def run_command(cmd: list[str]) -> int:
+ """Run a command and return its exit code.
+
+ Args:
+ cmd: Command and arguments to run
+
+ Returns:
+ Exit code from the command
+ """
+ print(f"Running: {' '.join(cmd)}")
+ result = subprocess.run(cmd, check=False)
+ return result.returncode
+
+
+def main() -> int:
+ """Run linter and formatter.
+
+ Returns:
+ Exit code (0 for success, non-zero for errors)
+ """
+ # Get target directory from args or use current directory
+ target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
+
+ if not target.exists():
+ print(f"Error: {target} does not exist", file=sys.stderr)
+ return 1
+
+ # Run ruff check
+ check_result = run_command(["ruff", "check", "--fix", str(target)])
+
+ # Run ruff format
+ format_result = run_command(["ruff", "format", str(target)])
+
+ # Return non-zero if either failed
+ if check_result != 0 or format_result != 0:
+ return 1
+
+ print("\n✓ Linting and formatting complete!")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
dots/.config/claude/skills/Python/workflows/Deps.md
@@ -0,0 +1,138 @@
+# Dependencies Workflow
+
+Manage Python dependencies with uv.
+
+## When to Use
+
+- "add python dependency"
+- "update dependencies"
+- "manage requirements"
+- "uv lock"
+
+## Quick Commands
+
+```bash
+# Add dependency
+uv add requests
+
+# Add dev dependency
+uv add --dev pytest
+
+# Add with version constraint
+uv add 'requests>=2.31.0,<3.0.0'
+
+# Remove dependency
+uv remove requests
+
+# Update all dependencies
+uv lock --upgrade
+
+# Sync environment with lock file
+uv sync
+
+# Export to requirements.txt
+uv pip compile pyproject.toml -o requirements.txt
+```
+
+## Managing Dependencies
+
+### Add Dependencies
+
+```bash
+# Runtime dependency
+uv add httpx pydantic rich
+
+# Development tools
+uv add --dev pytest ruff mypy pytest-cov
+
+# Optional dependency group
+uv add --optional docs sphinx
+
+# From git
+uv add git+https://github.com/user/repo.git
+
+# From git with branch/tag
+uv add git+https://github.com/user/repo.git@main
+uv add git+https://github.com/user/repo.git@v1.0.0
+
+# From local path (editable)
+uv add --editable ./local-package
+```
+
+### Update Dependencies
+
+```bash
+# Update specific package
+uv lock --upgrade-package requests
+
+# Update all packages
+uv lock --upgrade
+
+# Update and sync
+uv lock --upgrade && uv sync
+```
+
+## Lock File
+
+The `uv.lock` file ensures reproducible builds:
+
+```bash
+# Generate lock file
+uv lock
+
+# Sync from lock file
+uv sync
+
+# Include optional dependencies
+uv sync --extra docs
+
+# Include all optional dependencies
+uv sync --all-extras
+```
+
+**Always commit `uv.lock` to version control.**
+
+## Dependency Groups
+
+### pyproject.toml
+
+```toml
+[project]
+dependencies = [
+ "requests>=2.31.0",
+ "pydantic>=2.0.0",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=8.0.0",
+ "ruff>=0.8.0",
+ "mypy>=1.13.0",
+]
+docs = [
+ "sphinx>=7.0.0",
+ "sphinx-rtd-theme>=2.0.0",
+]
+```
+
+### Install Groups
+
+```bash
+# Install with dev dependencies
+uv sync --group dev
+
+# Install with docs dependencies
+uv sync --extra docs
+
+# Install all groups
+uv sync --all-extras
+```
+
+## Best Practices
+
+1. **Lock dependencies** - Always maintain `uv.lock`
+2. **Pin versions** - Use version constraints
+3. **Separate dev deps** - Use `--dev` for tools
+4. **Regular updates** - Update dependencies monthly
+5. **Security audit** - Check for vulnerabilities
+6. **Minimal deps** - Only add what you need
dots/.config/claude/skills/Python/workflows/Lint.md
@@ -0,0 +1,271 @@
+# Lint Workflow
+
+Lint and format Python code with ruff - the fast, modern linter and formatter.
+
+## When to Use
+
+- "lint python code"
+- "format python"
+- "ruff check"
+- "ruff format"
+- "fix code style"
+
+## Quick Commands
+
+### Linting
+
+```bash
+# Check for issues
+uv run ruff check .
+
+# Auto-fix issues
+uv run ruff check --fix .
+
+# Show fixes that would be applied (dry run)
+uv run ruff check --fix --diff .
+
+# Check specific file
+uv run ruff check src/myproject/main.py
+
+# Output as JSON
+uv run ruff check --output-format=json .
+```
+
+### Formatting
+
+```bash
+# Format code
+uv run ruff format .
+
+# Check if code would be reformatted (dry run)
+uv run ruff format --check .
+
+# Show diff of what would change
+uv run ruff format --diff .
+
+# Format specific file
+uv run ruff format src/myproject/main.py
+```
+
+### Combined
+
+```bash
+# Lint and format in one go
+uv run ruff check --fix . && uv run ruff format .
+```
+
+## Configuration
+
+### pyproject.toml
+
+```toml
+[tool.ruff]
+# Set line length (default: 88, Black compatible)
+line-length = 100
+
+# Target Python version
+target-version = "py311"
+
+# Source code directories
+src = ["src", "tests"]
+
+# Files to exclude
+extend-exclude = [
+ ".git",
+ "__pycache__",
+ "dist",
+ "build",
+ ".venv",
+]
+
+[tool.ruff.lint]
+# Enable rule sets
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # Pyflakes
+ "I", # isort
+ "N", # pep8-naming
+ "UP", # pyupgrade
+ "ANN", # flake8-annotations
+ "ASYNC", # flake8-async
+ "S", # flake8-bandit
+ "B", # flake8-bugbear
+ "A", # flake8-builtins
+ "C4", # flake8-comprehensions
+ "DTZ", # flake8-datetimez
+ "T10", # flake8-debugger
+ "EM", # flake8-errmsg
+ "ISC", # flake8-implicit-str-concat
+ "ICN", # flake8-import-conventions
+ "G", # flake8-logging-format
+ "PIE", # flake8-pie
+ "T20", # flake8-print
+ "PT", # flake8-pytest-style
+ "Q", # flake8-quotes
+ "RSE", # flake8-raise
+ "RET", # flake8-return
+ "SIM", # flake8-simplify
+ "TID", # flake8-tidy-imports
+ "ARG", # flake8-unused-arguments
+ "PTH", # flake8-use-pathlib
+ "PL", # Pylint
+ "TRY", # tryceratops
+ "RUF", # Ruff-specific rules
+]
+
+# Rules to ignore
+ignore = [
+ "ANN101", # Missing type annotation for self
+ "ANN102", # Missing type annotation for cls
+ "D203", # 1 blank line required before class docstring
+ "D213", # Multi-line docstring summary should start at the second line
+]
+
+# Allow fix for all enabled rules (when `--fix` is used)
+fixable = ["ALL"]
+unfixable = []
+
+# Per-file ignores
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = [
+ "S101", # Use of assert
+ "ARG", # Unused arguments
+ "PLR2004", # Magic value used in comparison
+]
+"__init__.py" = [
+ "F401", # Unused imports
+]
+
+# isort settings
+[tool.ruff.lint.isort]
+known-first-party = ["myproject"]
+force-single-line = false
+lines-after-imports = 2
+
+# flake8-quotes settings
+[tool.ruff.lint.flake8-quotes]
+docstring-quotes = "double"
+inline-quotes = "double"
+
+# pylint settings
+[tool.ruff.lint.pylint]
+max-args = 5
+max-branches = 12
+max-returns = 6
+max-statements = 50
+```
+
+## Common Rules
+
+### Import Organization (I)
+```python
+# Ruff automatically organizes imports
+import os
+import sys
+from pathlib import Path
+
+import requests
+import httpx
+
+from myproject import utils
+from myproject.core import MyClass
+```
+
+### Type Annotations (ANN)
+```python
+# Enforces type hints
+def greet(name: str) -> str:
+ return f"Hello, {name}!"
+
+def process_data(items: list[int]) -> dict[str, int]:
+ return {"count": len(items), "sum": sum(items)}
+```
+
+### Pathlib Usage (PTH)
+```python
+# Bad: Using os.path
+import os
+path = os.path.join("data", "file.txt")
+
+# Good: Using pathlib
+from pathlib import Path
+path = Path("data") / "file.txt"
+```
+
+### Simplifications (SIM)
+```python
+# Bad: Unnecessary comprehension
+squares = [x ** 2 for x in numbers if x ** 2 > 10]
+
+# Good: Direct filtering
+squares = [x ** 2 for x in numbers if x > 3]
+```
+
+## Ignoring Rules
+
+### Inline Ignore
+
+```python
+# Ignore specific rule for one line
+x = eval(user_input) # noqa: S307
+
+# Ignore multiple rules
+result = func() # noqa: ARG001, ANN201
+
+# Ignore all rules (use sparingly!)
+dangerous_code() # noqa
+```
+
+### File-level Ignore
+
+```python
+# At top of file
+# ruff: noqa: ANN
+```
+
+## Integration with Pre-commit
+
+```yaml
+# .pre-commit-config.yaml
+repos:
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.8.0
+ hooks:
+ # Run linter
+ - id: ruff
+ args: [--fix]
+ # Run formatter
+ - id: ruff-format
+```
+
+## CI/CD Integration
+
+```yaml
+# .github/workflows/lint.yml
+name: Lint
+
+on: [push, pull_request]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: astral-sh/setup-uv@v1
+ - name: Lint
+ run: uv run ruff check .
+ - name: Format check
+ run: uv run ruff format --check .
+```
+
+## Best Practices
+
+1. **Run ruff before committing** - Use pre-commit hooks
+2. **Fix automatically** - Use `--fix` flag liberally
+3. **Configure in pyproject.toml** - Keep all config in one place
+4. **Use per-file ignores** - Different rules for tests vs src
+5. **Don't ignore too much** - Only ignore rules when necessary
+6. **Format before linting** - Format first, then lint
+7. **CI enforcement** - Run in CI to catch issues
+8. **Editor integration** - Configure your editor to run ruff on save
dots/.config/claude/skills/Python/workflows/Package.md
@@ -0,0 +1,139 @@
+# Package Workflow
+
+Build and publish Python packages.
+
+## When to Use
+
+- "build python package"
+- "publish to pypi"
+- "create wheel"
+- "distribute package"
+
+## Quick Commands
+
+```bash
+# Build package
+uv build
+
+# Build wheel only
+uv build --wheel
+
+# Build source distribution only
+uv build --sdist
+
+# Publish to PyPI
+uv publish
+
+# Publish to test PyPI
+uv publish --publish-url https://test.pypi.org/legacy/
+```
+
+## Package Configuration
+
+### pyproject.toml
+
+```toml
+[project]
+name = "mypackage"
+version = "0.1.0"
+description = "My awesome package"
+readme = "README.md"
+requires-python = ">=3.11"
+license = {text = "MIT"}
+authors = [
+ {name = "Your Name", email = "you@example.com"}
+]
+keywords = ["example", "package"]
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3.11",
+]
+
+dependencies = [
+ "requests>=2.31.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/user/mypackage"
+Repository = "https://github.com/user/mypackage"
+Documentation = "https://mypackage.readthedocs.io"
+Changelog = "https://github.com/user/mypackage/blob/main/CHANGELOG.md"
+
+[project.scripts]
+mypackage = "mypackage.cli:main"
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/mypackage"]
+```
+
+## Building
+
+```bash
+# Clean previous builds
+rm -rf dist/
+
+# Build distributions
+uv build
+
+# Outputs:
+# dist/mypackage-0.1.0-py3-none-any.whl (wheel)
+# dist/mypackage-0.1.0.tar.gz (source)
+```
+
+## Publishing
+
+### Setup PyPI Credentials
+
+```bash
+# Set token (recommended)
+export UV_PUBLISH_TOKEN=pypi-...
+
+# Or use ~/.pypirc
+cat > ~/.pypirc << EOF
+[pypi]
+username = __token__
+password = pypi-...
+EOF
+```
+
+### Publish
+
+```bash
+# Publish to PyPI
+uv publish
+
+# Publish to Test PyPI
+uv publish --publish-url https://test.pypi.org/legacy/
+
+# Dry run
+uv publish --dry-run
+```
+
+## Version Management
+
+Update version in `pyproject.toml`, then:
+
+```bash
+# Tag release
+git tag v0.1.0
+git push --tags
+
+# Build and publish
+uv build
+uv publish
+```
+
+## Best Practices
+
+1. **Use src layout** - `src/package/`
+2. **Semantic versioning** - MAJOR.MINOR.PATCH
+3. **Include README** - And CHANGELOG
+4. **Test on Test PyPI** - Before real PyPI
+5. **Use build backend** - hatchling, setuptools, etc.
+6. **Add classifiers** - Help users find your package
dots/.config/claude/skills/Python/workflows/Project.md
@@ -0,0 +1,485 @@
+# Project Workflow
+
+Create and manage Python projects with uv, following modern best practices.
+
+## When to Use
+
+- "create python project"
+- "new python project"
+- "initialize python app"
+- "uv init"
+- "setup python package"
+
+## Quick Start
+
+### Create New Project
+
+```bash
+# Create new project with default structure
+uv init myproject
+
+# Create library project
+uv init --lib myproject
+
+# Create app project (default)
+uv init --app myproject
+
+# Create in existing directory
+cd myproject
+uv init
+```
+
+### Project Structure Created
+
+```
+myproject/
+├── .python-version # Python version (e.g., 3.11)
+├── README.md
+├── pyproject.toml # Project configuration
+├── hello.py # Entry point (for apps)
+└── src/
+ └── myproject/
+ └── __init__.py # For libraries
+```
+
+## Configure Project
+
+### Edit pyproject.toml
+
+```toml
+[project]
+name = "myproject"
+version = "0.1.0"
+description = "Description of my awesome project"
+readme = "README.md"
+requires-python = ">=3.11"
+authors = [
+ { name = "Your Name", email = "your.email@example.com" }
+]
+license = { text = "MIT" }
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Developers",
+ "Programming Language :: Python :: 3.11",
+]
+
+dependencies = [
+ # Runtime dependencies
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=8.0.0",
+ "pytest-cov>=6.0.0",
+ "ruff>=0.8.0",
+ "mypy>=1.13.0",
+]
+
+[project.scripts]
+myproject = "myproject.main:main"
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+# Ruff configuration
+[tool.ruff]
+line-length = 100
+target-version = "py311"
+src = ["src", "tests"]
+
+[tool.ruff.lint]
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "N", # pep8-naming
+ "B", # flake8-bugbear
+ "Q", # flake8-quotes
+ "UP", # pyupgrade
+ "ANN", # flake8-annotations
+ "S", # flake8-bandit
+ "C4", # flake8-comprehensions
+]
+ignore = [
+ "ANN101", # Missing type annotation for self
+ "ANN102", # Missing type annotation for cls
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["S101"] # Allow assert in tests
+
+# mypy configuration
+[tool.mypy]
+python_version = "3.11"
+strict = true
+warn_return_any = true
+warn_unused_configs = true
+disallow_untyped_defs = true
+disallow_any_generics = true
+check_untyped_defs = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+
+# pytest configuration
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = "test_*.py"
+python_functions = "test_*"
+addopts = [
+ "--strict-markers",
+ "--strict-config",
+ "--cov=src",
+ "--cov-report=term-missing",
+ "--cov-report=html",
+]
+
+# Coverage configuration
+[tool.coverage.run]
+source = ["src"]
+omit = ["tests/*", "**/__pycache__/*"]
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
+exclude_lines = [
+ "pragma: no cover",
+ "def __repr__",
+ "raise AssertionError",
+ "raise NotImplementedError",
+ "if __name__ == .__main__.:",
+ "if TYPE_CHECKING:",
+]
+```
+
+## Add Dependencies
+
+### Runtime Dependencies
+
+```bash
+# Add single package
+uv add requests
+
+# Add multiple packages
+uv add requests httpx rich
+
+# Add with version constraint
+uv add 'requests>=2.31.0,<3.0.0'
+
+# Add from git
+uv add git+https://github.com/user/repo.git
+
+# Add from local path
+uv add --editable ./local-package
+```
+
+### Development Dependencies
+
+```bash
+# Add dev dependencies
+uv add --dev pytest pytest-cov ruff mypy
+
+# Add testing tools
+uv add --dev pytest pytest-asyncio pytest-mock
+
+# Add type stubs
+uv add --dev types-requests types-pyyaml
+```
+
+### Optional Dependencies
+
+```bash
+# Add optional dependency group
+uv add --optional docs sphinx sphinx-rtd-theme
+
+# Install with optional group
+uv sync --extra docs
+```
+
+## Project Setup Workflow
+
+### 1. Initialize Project
+
+```bash
+uv init myproject
+cd myproject
+```
+
+### 2. Set Python Version
+
+```bash
+# Use specific Python version
+echo "3.11" > .python-version
+
+# Or let uv detect/install
+uv python pin 3.11
+```
+
+### 3. Add Dependencies
+
+```bash
+# Add core dependencies
+uv add requests pydantic
+
+# Add development tools
+uv add --dev pytest ruff mypy pytest-cov
+```
+
+### 4. Create Project Structure
+
+```bash
+# For applications
+mkdir -p src/myproject tests docs
+touch src/myproject/{__init__.py,main.py}
+touch tests/{__init__.py,test_main.py}
+
+# For libraries
+mkdir -p src/myproject tests docs examples
+touch src/myproject/{__init__.py,core.py}
+touch tests/{__init__.py,test_core.py}
+touch examples/basic_usage.py
+```
+
+### 5. Initialize Git
+
+```bash
+git init
+cat > .gitignore << 'EOF'
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+env/
+venv/
+.venv/
+ENV/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Testing
+.pytest_cache/
+.coverage
+htmlcov/
+.tox/
+
+# Type checking
+.mypy_cache/
+.dmypy.json
+
+# IDEs
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# uv
+uv.lock
+EOF
+
+git add .
+git commit -m "Initial commit"
+```
+
+### 6. Setup Pre-commit (Optional)
+
+```bash
+# Add pre-commit
+uv add --dev pre-commit
+
+# Create .pre-commit-config.yaml
+cat > .pre-commit-config.yaml << 'EOF'
+repos:
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.8.0
+ hooks:
+ - id: ruff
+ args: [--fix]
+ - id: ruff-format
+
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.13.0
+ hooks:
+ - id: mypy
+ additional_dependencies: []
+EOF
+
+# Install hooks
+uv run pre-commit install
+```
+
+## Project Templates
+
+### CLI Application
+
+```python
+# src/myproject/main.py
+"""Main entry point for myproject CLI."""
+import argparse
+import sys
+from typing import Sequence
+
+def main(argv: Sequence[str] | None = None) -> int:
+ """Run the CLI application.
+
+ Args:
+ argv: Command line arguments (defaults to sys.argv)
+
+ Returns:
+ Exit code (0 for success, non-zero for errors)
+ """
+ parser = argparse.ArgumentParser(description="My awesome CLI")
+ parser.add_argument("--version", action="version", version="0.1.0")
+ parser.add_argument("input", help="Input file path")
+ parser.add_argument("-o", "--output", help="Output file path")
+
+ args = parser.parse_args(argv)
+
+ try:
+ # Your logic here
+ print(f"Processing {args.input}")
+ return 0
+ except Exception as e:
+ print(f"Error: {e}", file=sys.stderr)
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+### Library Package
+
+```python
+# src/myproject/__init__.py
+"""MyProject - A Python library for awesome things."""
+from myproject.core import MyClass, my_function
+
+__version__ = "0.1.0"
+__all__ = ["MyClass", "my_function"]
+
+# src/myproject/core.py
+"""Core functionality for myproject."""
+from typing import Any
+
+class MyClass:
+ """Main class for myproject."""
+
+ def __init__(self, name: str) -> None:
+ """Initialize MyClass.
+
+ Args:
+ name: Name of the instance
+ """
+ self.name = name
+
+ def process(self, data: dict[str, Any]) -> dict[str, Any]:
+ """Process data.
+
+ Args:
+ data: Input data dictionary
+
+ Returns:
+ Processed data dictionary
+ """
+ return {"name": self.name, **data}
+
+def my_function(value: int) -> int:
+ """Perform calculation on value.
+
+ Args:
+ value: Input integer
+
+ Returns:
+ Calculated result
+ """
+ return value * 2
+```
+
+## Run Project
+
+### Development
+
+```bash
+# Run module
+uv run python -m myproject
+
+# Run script entry point
+uv run myproject
+
+# Run tests
+uv run pytest
+
+# Run with coverage
+uv run pytest --cov
+
+# Lint and format
+uv run ruff check .
+uv run ruff format .
+
+# Type check
+uv run mypy .
+```
+
+### Build and Install
+
+```bash
+# Build distribution
+uv build
+
+# Install locally
+uv pip install -e .
+
+# Install from built wheel
+uv pip install dist/*.whl
+```
+
+## Common Project Commands
+
+```bash
+# Show project info
+uv tree # Show dependency tree
+cat pyproject.toml # View configuration
+
+# Update dependencies
+uv lock --upgrade # Update lock file
+uv sync # Sync environment
+
+# Clean project
+rm -rf .venv # Remove virtual environment
+rm -rf dist build *.egg-info # Remove build artifacts
+rm -rf .pytest_cache .mypy_cache __pycache__ # Remove caches
+
+# Rebuild environment
+rm -rf .venv && uv sync
+```
+
+## Best Practices
+
+1. **Always use pyproject.toml** - Single source of configuration
+2. **Pin Python version** - Use `.python-version` file
+3. **Lock dependencies** - Commit `uv.lock` to git
+4. **Separate dev deps** - Use `--dev` for development tools
+5. **Configure tools** - Add ruff, mypy, pytest config to pyproject.toml
+6. **Use src layout** - Place code in `src/package/` for better testing
+7. **Add type hints** - Enable mypy strict mode
+8. **Write tests** - Aim for >80% coverage
+9. **Use entry points** - Define CLI commands in `[project.scripts]`
+10. **Document** - Add docstrings and README
dots/.config/claude/skills/Python/workflows/Script.md
@@ -0,0 +1,621 @@
+# Script Workflow
+
+Create and run single-file Python scripts with inline dependencies using PEP 723 and uv.
+
+## When to Use
+
+- "create python script"
+- "single file script"
+- "uv run"
+- "PEP 723"
+- "standalone script"
+
+## Quick Start
+
+### Basic Script with Dependencies
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "requests>=2.31.0",
+# "rich>=13.0.0",
+# ]
+# ///
+"""Fetch and display data from an API."""
+
+import requests
+from rich.console import Console
+from rich.table import Table
+
+def main() -> None:
+ """Fetch data and display in a table."""
+ console = Console()
+
+ with console.status("[bold green]Fetching data..."):
+ response = requests.get("https://api.github.com/users/github")
+ response.raise_for_status()
+ data = response.json()
+
+ table = Table(title="GitHub User Info")
+ table.add_column("Field", style="cyan")
+ table.add_column("Value", style="magenta")
+
+ table.add_row("Name", data.get("name", "N/A"))
+ table.add_row("Location", data.get("location", "N/A"))
+ table.add_row("Public Repos", str(data.get("public_repos", 0)))
+
+ console.print(table)
+
+if __name__ == "__main__":
+ main()
+```
+
+### Run the Script
+
+```bash
+# Make executable
+chmod +x script.py
+
+# Run directly (uv handles dependencies automatically)
+./script.py
+
+# Or run with uv explicitly
+uv run script.py
+```
+
+## PEP 723 Inline Metadata
+
+### Metadata Block Format
+
+The metadata must be:
+- In a comment block starting with `# /// script`
+- Followed by TOML-formatted metadata
+- Closed with `# ///`
+- Placed before any imports
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "requests>=2.31.0",
+# "rich>=13.0.0",
+# ]
+# ///
+```
+
+### Shebang Line
+
+**Required format:**
+```python
+#!/usr/bin/env -S uv run --script
+```
+
+**Important:** Must use `--script` flag with `uv run` in shebang.
+
+### Dependencies Syntax
+
+```python
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# # Simple dependency
+# "requests",
+#
+# # With version constraint
+# "rich>=13.0.0",
+#
+# # Version range
+# "httpx>=0.24.0,<1.0.0",
+#
+# # Git dependency
+# "mypackage @ git+https://github.com/user/repo.git",
+#
+# # Git with tag/branch
+# "mypackage @ git+https://github.com/user/repo.git@v1.0.0",
+# ]
+# ///
+```
+
+## Script Templates
+
+### Data Processing Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "pandas>=2.0.0",
+# "rich>=13.0.0",
+# ]
+# ///
+"""Process CSV data and generate report."""
+
+import sys
+from pathlib import Path
+from typing import NoReturn
+
+import pandas as pd
+from rich.console import Console
+from rich.table import Table
+
+console = Console()
+
+def process_csv(input_path: Path, output_path: Path) -> None:
+ """Process CSV file and generate summary.
+
+ Args:
+ input_path: Path to input CSV file
+ output_path: Path to output summary file
+
+ Raises:
+ FileNotFoundError: If input file doesn't exist
+ ValueError: If CSV is invalid or empty
+ """
+ if not input_path.exists():
+ raise FileNotFoundError(f"Input file not found: {input_path}")
+
+ # Read and process
+ df = pd.read_csv(input_path)
+
+ if df.empty:
+ raise ValueError("CSV file is empty")
+
+ # Generate summary
+ summary = df.describe()
+
+ # Save output
+ summary.to_csv(output_path)
+
+ # Display
+ table = Table(title=f"Summary: {input_path.name}")
+ for col in summary.columns:
+ table.add_column(col)
+
+ for idx, row in summary.iterrows():
+ table.add_row(str(idx), *[f"{val:.2f}" for val in row])
+
+ console.print(table)
+ console.print(f"\n[green]Summary saved to:[/green] {output_path}")
+
+def main() -> int:
+ """Run the data processing script.
+
+ Returns:
+ Exit code (0 for success, 1 for error)
+ """
+ if len(sys.argv) != 3:
+ console.print("[red]Usage:[/red] script.py <input.csv> <output.csv>")
+ return 1
+
+ input_path = Path(sys.argv[1])
+ output_path = Path(sys.argv[2])
+
+ try:
+ process_csv(input_path, output_path)
+ return 0
+ except Exception as e:
+ console.print(f"[red]Error:[/red] {e}")
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+### API Client Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "httpx>=0.24.0",
+# "pydantic>=2.0.0",
+# "rich>=13.0.0",
+# ]
+# ///
+"""Fetch and validate data from REST API."""
+
+import sys
+from typing import Any
+
+import httpx
+from pydantic import BaseModel, Field, ValidationError
+from rich.console import Console
+from rich.pretty import pprint
+
+console = Console()
+
+class User(BaseModel):
+ """User data model."""
+
+ id: int
+ name: str
+ email: str
+ company: dict[str, Any] = Field(default_factory=dict)
+
+ class Config:
+ """Pydantic configuration."""
+
+ frozen = True
+
+async def fetch_user(user_id: int) -> User:
+ """Fetch user from API.
+
+ Args:
+ user_id: ID of user to fetch
+
+ Returns:
+ User object with validated data
+
+ Raises:
+ httpx.HTTPError: If request fails
+ ValidationError: If response data is invalid
+ """
+ async with httpx.AsyncClient() as client:
+ response = await client.get(
+ f"https://jsonplaceholder.typicode.com/users/{user_id}"
+ )
+ response.raise_for_status()
+ data = response.json()
+
+ return User(**data)
+
+async def main() -> int:
+ """Run the API client.
+
+ Returns:
+ Exit code (0 for success, 1 for error)
+ """
+ if len(sys.argv) != 2:
+ console.print("[red]Usage:[/red] script.py <user_id>")
+ return 1
+
+ try:
+ user_id = int(sys.argv[1])
+ except ValueError:
+ console.print("[red]Error:[/red] user_id must be an integer")
+ return 1
+
+ try:
+ with console.status(f"[bold green]Fetching user {user_id}..."):
+ user = await fetch_user(user_id)
+
+ console.print("\n[bold green]User data:[/bold green]")
+ pprint(user.model_dump())
+ return 0
+
+ except httpx.HTTPError as e:
+ console.print(f"[red]HTTP Error:[/red] {e}")
+ return 1
+ except ValidationError as e:
+ console.print(f"[red]Validation Error:[/red] {e}")
+ return 1
+ except Exception as e:
+ console.print(f"[red]Error:[/red] {e}")
+ return 1
+
+if __name__ == "__main__":
+ import asyncio
+ sys.exit(asyncio.run(main()))
+```
+
+### CLI Tool Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "click>=8.0.0",
+# "rich>=13.0.0",
+# ]
+# ///
+"""Example CLI tool with multiple commands."""
+
+import click
+from rich.console import Console
+
+console = Console()
+
+@click.group()
+@click.version_option(version="1.0.0")
+def cli() -> None:
+ """My awesome CLI tool."""
+
+@cli.command()
+@click.argument("name")
+@click.option("--greeting", default="Hello", help="Greeting to use")
+def greet(name: str, greeting: str) -> None:
+ """Greet someone by name.
+
+ Args:
+ name: Name to greet
+ greeting: Greeting message
+ """
+ console.print(f"[bold green]{greeting}, {name}![/bold green]")
+
+@cli.command()
+@click.argument("numbers", nargs=-1, type=int)
+@click.option("--operation", type=click.Choice(["sum", "product"]), default="sum")
+def calculate(numbers: tuple[int, ...], operation: str) -> None:
+ """Perform calculation on numbers.
+
+ Args:
+ numbers: Numbers to calculate
+ operation: Operation to perform (sum or product)
+ """
+ if not numbers:
+ console.print("[red]Error:[/red] No numbers provided")
+ return
+
+ if operation == "sum":
+ result = sum(numbers)
+ console.print(f"Sum: [bold]{result}[/bold]")
+ else:
+ result = 1
+ for num in numbers:
+ result *= num
+ console.print(f"Product: [bold]{result}[/bold]")
+
+if __name__ == "__main__":
+ cli()
+```
+
+### File Processing Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "pathlib",
+# ]
+# ///
+"""Process files in a directory."""
+
+import sys
+from pathlib import Path
+from typing import Iterator
+
+def find_python_files(directory: Path) -> Iterator[Path]:
+ """Find all Python files in directory.
+
+ Args:
+ directory: Directory to search
+
+ Yields:
+ Path objects for Python files
+ """
+ for path in directory.rglob("*.py"):
+ if path.is_file() and not path.name.startswith("."):
+ yield path
+
+def count_lines(file_path: Path) -> int:
+ """Count lines in file.
+
+ Args:
+ file_path: Path to file
+
+ Returns:
+ Number of lines in file
+ """
+ return len(file_path.read_text().splitlines())
+
+def main() -> int:
+ """Process Python files and count lines.
+
+ Returns:
+ Exit code (0 for success, 1 for error)
+ """
+ if len(sys.argv) != 2:
+ print("Usage: script.py <directory>")
+ return 1
+
+ directory = Path(sys.argv[1])
+
+ if not directory.is_dir():
+ print(f"Error: {directory} is not a directory")
+ return 1
+
+ total_lines = 0
+ file_count = 0
+
+ for file_path in find_python_files(directory):
+ lines = count_lines(file_path)
+ total_lines += lines
+ file_count += 1
+ print(f"{file_path.relative_to(directory)}: {lines} lines")
+
+ print(f"\nTotal: {file_count} files, {total_lines} lines")
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+## Managing Script Dependencies
+
+### Add Dependencies to Existing Script
+
+```bash
+# Add dependency to script
+uv add --script myscript.py requests
+
+# Add multiple dependencies
+uv add --script myscript.py requests rich httpx
+```
+
+This updates the inline metadata in the script automatically.
+
+### Remove Dependencies
+
+Manually edit the script's metadata block to remove dependencies.
+
+## Testing Scripts
+
+### Inline Tests
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "pytest>=8.0.0",
+# ]
+# ///
+"""Script with inline tests."""
+
+def add(a: int, b: int) -> int:
+ """Add two numbers."""
+ return a + b
+
+def test_add() -> None:
+ """Test add function."""
+ assert add(2, 3) == 5
+ assert add(0, 0) == 0
+ assert add(-1, 1) == 0
+
+if __name__ == "__main__":
+ # Run main logic
+ result = add(10, 20)
+ print(f"Result: {result}")
+
+ # To run tests: pytest script.py
+```
+
+```bash
+# Run the script normally
+./script.py
+
+# Run tests
+uv run pytest script.py
+```
+
+## Script Organization
+
+### Multiple Related Scripts
+
+For related scripts, consider this structure:
+
+```
+scripts/
+├── common.py # Shared utilities (no shebang)
+├── fetch_data.py # Script 1 (with shebang + metadata)
+├── process_data.py # Script 2 (with shebang + metadata)
+└── generate_report.py # Script 3 (with shebang + metadata)
+```
+
+**Shared utilities (common.py):**
+```python
+"""Shared utilities for scripts."""
+from pathlib import Path
+
+def ensure_dir(path: Path) -> None:
+ """Ensure directory exists."""
+ path.mkdir(parents=True, exist_ok=True)
+```
+
+**Script using shared code:**
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = []
+# ///
+"""Script using shared utilities."""
+
+import sys
+from pathlib import Path
+
+# Import from adjacent file
+sys.path.insert(0, str(Path(__file__).parent))
+from common import ensure_dir
+
+def main() -> int:
+ ensure_dir(Path("output"))
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+## Best Practices
+
+1. **Always use shebang** - `#!/usr/bin/env -S uv run --script`
+2. **Always use metadata block** - Even if no dependencies
+3. **Pin Python version** - Use `requires-python`
+4. **Use version constraints** - Pin dependency versions
+5. **Add docstrings** - Document what the script does
+6. **Add type hints** - Make code more maintainable
+7. **Handle errors** - Use try/except and return error codes
+8. **Make executable** - `chmod +x script.py`
+9. **Use pathlib** - Use `Path` objects instead of strings
+10. **Return exit codes** - Return 0 for success, non-zero for errors
+
+## Common Patterns
+
+### Read from stdin, write to stdout
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = []
+# ///
+"""Process data from stdin to stdout."""
+
+import sys
+
+def main() -> int:
+ """Read from stdin, process, write to stdout."""
+ for line in sys.stdin:
+ processed = line.strip().upper()
+ print(processed)
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+```bash
+# Usage
+echo "hello world" | ./script.py
+cat input.txt | ./script.py > output.txt
+```
+
+### Configuration from environment
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = []
+# ///
+"""Script using environment variables."""
+
+import os
+import sys
+
+def main() -> int:
+ """Run script with config from environment."""
+ api_key = os.getenv("API_KEY")
+ if not api_key:
+ print("Error: API_KEY environment variable not set", file=sys.stderr)
+ return 1
+
+ # Use api_key...
+ print(f"Using API key: {api_key[:4]}...")
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+```bash
+# Usage
+API_KEY=secret ./script.py
+```
dots/.config/claude/skills/Python/workflows/Test.md
@@ -0,0 +1,567 @@
+# Test Workflow
+
+Run Python tests with pytest, coverage, and various testing patterns.
+
+## When to Use
+
+- "run python tests"
+- "pytest"
+- "test coverage"
+- "run specific test"
+- "test with fixtures"
+
+## Quick Commands
+
+### Basic Testing
+
+```bash
+# Run all tests
+uv run pytest
+
+# Verbose output
+uv run pytest -v
+
+# Very verbose (show test names as they run)
+uv run pytest -vv
+
+# Run specific test file
+uv run pytest tests/test_auth.py
+
+# Run specific test function
+uv run pytest tests/test_auth.py::test_login
+
+# Run specific test class
+uv run pytest tests/test_auth.py::TestAuth
+
+# Run specific test method
+uv run pytest tests/test_auth.py::TestAuth::test_login
+
+# Run tests matching pattern
+uv run pytest -k "auth"
+uv run pytest -k "test_login or test_logout"
+```
+
+### Test Coverage
+
+```bash
+# Run tests with coverage
+uv run pytest --cov
+
+# Coverage for specific module
+uv run pytest --cov=src/myproject
+
+# Show missing lines
+uv run pytest --cov --cov-report=term-missing
+
+# Generate HTML coverage report
+uv run pytest --cov --cov-report=html
+# Open htmlcov/index.html in browser
+
+# Generate XML coverage (for CI)
+uv run pytest --cov --cov-report=xml
+
+# Fail if coverage below threshold
+uv run pytest --cov --cov-fail-under=80
+```
+
+### Test Output Control
+
+```bash
+# Show print statements
+uv run pytest -s
+
+# Stop at first failure
+uv run pytest -x
+
+# Stop after N failures
+uv run pytest --maxfail=3
+
+# Show local variables in traceback
+uv run pytest -l
+
+# Show summary of all test outcomes
+uv run pytest -ra
+```
+
+### Test Selection
+
+```bash
+# Run only failed tests from last run
+uv run pytest --lf
+
+# Run failed tests first, then others
+uv run pytest --ff
+
+# Run tests in random order (requires pytest-random-order)
+uv run pytest --random-order
+```
+
+## Test Structure
+
+### Basic Test File
+
+```python
+# tests/test_calculator.py
+"""Tests for calculator module."""
+import pytest
+from myproject.calculator import Calculator
+
+class TestCalculator:
+ """Test suite for Calculator class."""
+
+ def test_add(self) -> None:
+ """Test addition operation."""
+ calc = Calculator()
+ result = calc.add(2, 3)
+ assert result == 5
+
+ def test_subtract(self) -> None:
+ """Test subtraction operation."""
+ calc = Calculator()
+ result = calc.subtract(5, 3)
+ assert result == 2
+
+ def test_multiply(self) -> None:
+ """Test multiplication operation."""
+ calc = Calculator()
+ result = calc.multiply(2, 3)
+ assert result == 6
+
+ def test_divide(self) -> None:
+ """Test division operation."""
+ calc = Calculator()
+ result = calc.divide(6, 3)
+ assert result == 2.0
+
+ def test_divide_by_zero(self) -> None:
+ """Test division by zero raises error."""
+ calc = Calculator()
+ with pytest.raises(ValueError, match="Cannot divide by zero"):
+ calc.divide(10, 0)
+```
+
+### Using Fixtures
+
+```python
+# tests/test_database.py
+"""Tests for database operations."""
+import pytest
+from myproject.database import Database
+
+@pytest.fixture
+def db() -> Database:
+ """Provide a test database instance."""
+ database = Database(":memory:") # SQLite in-memory
+ database.create_tables()
+ yield database
+ database.close()
+
+@pytest.fixture
+def sample_data() -> dict[str, str]:
+ """Provide sample test data."""
+ return {
+ "name": "John Doe",
+ "email": "john@example.com",
+ }
+
+def test_insert_user(db: Database, sample_data: dict[str, str]) -> None:
+ """Test inserting a user into database."""
+ user_id = db.insert_user(sample_data)
+ assert user_id > 0
+
+def test_get_user(db: Database, sample_data: dict[str, str]) -> None:
+ """Test retrieving a user from database."""
+ user_id = db.insert_user(sample_data)
+ user = db.get_user(user_id)
+ assert user["name"] == sample_data["name"]
+ assert user["email"] == sample_data["email"]
+```
+
+### Parametrized Tests
+
+```python
+# tests/test_validation.py
+"""Tests for validation functions."""
+import pytest
+from myproject.validation import validate_email, validate_phone
+
+@pytest.mark.parametrize("email,expected", [
+ ("user@example.com", True),
+ ("user.name@example.co.uk", True),
+ ("user+tag@example.com", True),
+ ("invalid.email", False),
+ ("@example.com", False),
+ ("user@", False),
+ ("", False),
+])
+def test_validate_email(email: str, expected: bool) -> None:
+ """Test email validation with various inputs."""
+ assert validate_email(email) == expected
+
+@pytest.mark.parametrize("phone,expected", [
+ ("+1-555-123-4567", True),
+ ("555-123-4567", True),
+ ("5551234567", True),
+ ("invalid", False),
+ ("", False),
+])
+def test_validate_phone(phone: str, expected: bool) -> None:
+ """Test phone validation with various inputs."""
+ assert validate_phone(phone) == expected
+
+# Test with multiple parameters
+@pytest.mark.parametrize("a,b,expected", [
+ (2, 3, 5),
+ (0, 0, 0),
+ (-1, 1, 0),
+ (10, -5, 5),
+ (100, 200, 300),
+])
+def test_addition(a: int, b: int, expected: int) -> None:
+ """Test addition with multiple input combinations."""
+ assert a + b == expected
+```
+
+### Fixture Scopes
+
+```python
+# tests/conftest.py
+"""Shared fixtures for all tests."""
+import pytest
+from myproject.app import create_app
+from myproject.database import Database
+
+@pytest.fixture(scope="session")
+def app():
+ """Provide application instance for entire test session."""
+ app = create_app({"TESTING": True})
+ yield app
+
+@pytest.fixture(scope="module")
+def db():
+ """Provide database for all tests in a module."""
+ database = Database(":memory:")
+ database.create_tables()
+ yield database
+ database.close()
+
+@pytest.fixture(scope="function")
+def clean_db(db: Database):
+ """Provide clean database for each test function."""
+ yield db
+ db.clear_all_tables() # Clean up after each test
+
+@pytest.fixture
+def client(app):
+ """Provide test client for making requests."""
+ return app.test_client()
+```
+
+## Advanced Testing Patterns
+
+### Testing Exceptions
+
+```python
+def test_exception_raised() -> None:
+ """Test that exception is raised."""
+ with pytest.raises(ValueError):
+ my_function(-1)
+
+def test_exception_with_message() -> None:
+ """Test exception message matches pattern."""
+ with pytest.raises(ValueError, match="must be positive"):
+ my_function(-1)
+
+def test_exception_attributes() -> None:
+ """Test exception has expected attributes."""
+ with pytest.raises(ValueError) as exc_info:
+ my_function(-1)
+
+ assert exc_info.value.args[0] == "Value must be positive"
+ assert exc_info.type is ValueError
+```
+
+### Testing Async Code
+
+```python
+import pytest
+
+@pytest.mark.asyncio
+async def test_async_function() -> None:
+ """Test async function."""
+ result = await fetch_data()
+ assert result is not None
+
+@pytest.mark.asyncio
+async def test_async_with_fixture(async_client) -> None:
+ """Test async code with fixture."""
+ response = await async_client.get("/api/data")
+ assert response.status_code == 200
+```
+
+### Mocking and Patching
+
+```python
+from unittest.mock import Mock, patch, MagicMock
+import pytest
+
+def test_with_mock() -> None:
+ """Test using mock objects."""
+ # Create a mock
+ mock_db = Mock()
+ mock_db.get_user.return_value = {"id": 1, "name": "John"}
+
+ # Use the mock
+ user = mock_db.get_user(1)
+ assert user["name"] == "John"
+
+ # Verify mock was called
+ mock_db.get_user.assert_called_once_with(1)
+
+@patch("myproject.api.requests.get")
+def test_with_patch(mock_get) -> None:
+ """Test using patch decorator."""
+ # Configure mock return value
+ mock_response = Mock()
+ mock_response.json.return_value = {"status": "ok"}
+ mock_response.status_code = 200
+ mock_get.return_value = mock_response
+
+ # Call function that uses requests.get
+ result = fetch_api_data()
+
+ # Verify
+ assert result["status"] == "ok"
+ mock_get.assert_called_once()
+
+def test_with_pytest_mock(mocker) -> None:
+ """Test using pytest-mock plugin."""
+ # Mock a method
+ mock = mocker.patch("myproject.api.requests.get")
+ mock.return_value.json.return_value = {"data": "test"}
+
+ result = fetch_api_data()
+ assert result["data"] == "test"
+```
+
+### Testing with Temporary Files
+
+```python
+import pytest
+from pathlib import Path
+
+def test_with_tmp_path(tmp_path: Path) -> None:
+ """Test using temporary directory.
+
+ tmp_path is a pytest fixture that provides a temporary directory
+ unique to the test invocation.
+ """
+ # Create a file in temp directory
+ test_file = tmp_path / "test.txt"
+ test_file.write_text("test content")
+
+ # Use the file
+ result = process_file(test_file)
+
+ assert result is not None
+ assert test_file.exists()
+
+def test_with_tmp_path_factory(tmp_path_factory) -> None:
+ """Test using temporary directory factory.
+
+ Useful for session-scoped fixtures.
+ """
+ temp_dir = tmp_path_factory.mktemp("data")
+ test_file = temp_dir / "test.txt"
+ test_file.write_text("content")
+
+ assert test_file.read_text() == "content"
+```
+
+## Test Configuration
+
+### pyproject.toml
+
+```toml
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = "test_*.py"
+python_classes = "Test*"
+python_functions = "test_*"
+
+# Add options
+addopts = [
+ "--strict-markers", # Fail on unknown markers
+ "--strict-config", # Fail on config errors
+ "--verbose", # Verbose output
+ "--cov=src", # Coverage for src directory
+ "--cov-report=term-missing", # Show missing lines
+ "--cov-report=html", # Generate HTML report
+ "--cov-fail-under=80", # Fail if coverage < 80%
+]
+
+# Markers for categorizing tests
+markers = [
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
+ "integration: marks tests as integration tests",
+ "unit: marks tests as unit tests",
+]
+
+# Filter warnings
+filterwarnings = [
+ "error", # Treat warnings as errors
+ "ignore::DeprecationWarning", # Ignore deprecation warnings
+]
+```
+
+### Coverage Configuration
+
+```toml
+[tool.coverage.run]
+source = ["src"]
+omit = [
+ "tests/*",
+ "**/__pycache__/*",
+ "**/site-packages/*",
+]
+branch = true # Measure branch coverage
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
+exclude_lines = [
+ "pragma: no cover",
+ "def __repr__",
+ "raise AssertionError",
+ "raise NotImplementedError",
+ "if __name__ == .__main__.:",
+ "if TYPE_CHECKING:",
+ "@abstractmethod",
+]
+
+[tool.coverage.html]
+directory = "htmlcov"
+```
+
+## Test Markers
+
+### Using Markers
+
+```python
+import pytest
+
+@pytest.mark.slow
+def test_slow_operation() -> None:
+ """Test that takes a long time to run."""
+ # ... slow test ...
+
+@pytest.mark.integration
+def test_api_integration() -> None:
+ """Integration test with external API."""
+ # ... integration test ...
+
+@pytest.mark.unit
+def test_unit_calculation() -> None:
+ """Fast unit test."""
+ # ... unit test ...
+
+@pytest.mark.skip(reason="Not implemented yet")
+def test_future_feature() -> None:
+ """Test for future feature."""
+
+@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
+def test_unix_specific() -> None:
+ """Test that only runs on Unix."""
+
+@pytest.mark.xfail(reason="Known bug #123")
+def test_known_failure() -> None:
+ """Test expected to fail due to known bug."""
+```
+
+### Running Tests by Marker
+
+```bash
+# Run only unit tests
+uv run pytest -m unit
+
+# Run everything except slow tests
+uv run pytest -m "not slow"
+
+# Run integration tests only
+uv run pytest -m integration
+
+# Combine markers
+uv run pytest -m "unit and not slow"
+```
+
+## Continuous Testing
+
+### Watch Mode (with pytest-watch)
+
+```bash
+# Install pytest-watch
+uv add --dev pytest-watch
+
+# Watch for changes and re-run tests
+uv run ptw
+
+# Watch specific directory
+uv run ptw tests/
+
+# Watch with coverage
+uv run ptw -- --cov
+```
+
+### Pre-commit Hook
+
+```yaml
+# .pre-commit-config.yaml
+repos:
+ - repo: local
+ hooks:
+ - id: pytest
+ name: pytest
+ entry: uv run pytest
+ language: system
+ pass_filenames: false
+ always_run: true
+```
+
+## Common Test Commands
+
+```bash
+# Run tests with coverage and generate HTML report
+uv run pytest --cov --cov-report=html
+
+# Run only tests that failed last time
+uv run pytest --lf
+
+# Run tests in parallel (requires pytest-xdist)
+uv add --dev pytest-xdist
+uv run pytest -n auto
+
+# Generate JUnit XML report (for CI)
+uv run pytest --junit-xml=report.xml
+
+# Profile slow tests (requires pytest-profiling)
+uv add --dev pytest-profiling
+uv run pytest --profile
+
+# Debug tests with pdb
+uv run pytest --pdb # Drop into debugger on failure
+uv run pytest -x --pdb # Stop at first failure and debug
+```
+
+## Best Practices
+
+1. **Organize tests by module** - Mirror your src structure in tests
+2. **Use descriptive names** - Test names should describe what they test
+3. **One assertion per test** - Keep tests focused (when practical)
+4. **Use fixtures** - Share setup code across tests
+5. **Parametrize** - Test multiple inputs with one test function
+6. **Test edge cases** - Empty inputs, None, negative numbers, etc.
+7. **Mock external dependencies** - Don't hit real APIs or databases
+8. **Aim for >80% coverage** - But 100% coverage doesn't mean bug-free
+9. **Keep tests fast** - Mark slow tests and run them separately
+10. **Use markers** - Categorize tests for selective running
dots/.config/claude/skills/Python/workflows/Type.md
@@ -0,0 +1,304 @@
+# Type Checking Workflow
+
+Type check Python code with mypy for static type verification.
+
+## When to Use
+
+- "type check python"
+- "mypy"
+- "check type hints"
+- "static type checking"
+
+## Quick Commands
+
+```bash
+# Type check all files
+uv run mypy .
+
+# Type check specific file/directory
+uv run mypy src/myproject
+
+# Strict mode
+uv run mypy --strict .
+
+# Show error codes
+uv run mypy --show-error-codes .
+
+# Generate HTML report
+uv run mypy --html-report mypy-report .
+```
+
+## Configuration
+
+### pyproject.toml
+
+```toml
+[tool.mypy]
+python_version = "3.11"
+strict = true
+
+# Import discovery
+files = ["src", "tests"]
+namespace_packages = true
+explicit_package_bases = true
+
+# Warnings
+warn_return_any = true
+warn_unused_configs = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_no_return = true
+warn_unreachable = true
+
+# Strictness
+disallow_untyped_defs = true
+disallow_any_generics = true
+disallow_subclassing_any = true
+disallow_untyped_calls = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+no_implicit_optional = true
+strict_optional = true
+strict_equality = true
+
+# Error messages
+show_error_context = true
+show_column_numbers = true
+show_error_codes = true
+pretty = true
+
+# Per-module options
+[[tool.mypy.overrides]]
+module = "tests.*"
+disallow_untyped_defs = false
+
+[[tool.mypy.overrides]]
+module = "setuptools.*"
+ignore_missing_imports = true
+```
+
+## Type Hints Examples
+
+### Basic Types
+
+```python
+from typing import Any
+
+# Simple types
+name: str = "John"
+age: int = 30
+height: float = 5.9
+is_active: bool = True
+
+# Collections
+names: list[str] = ["John", "Jane"]
+scores: dict[str, int] = {"math": 90, "english": 85}
+coordinates: tuple[float, float] = (1.0, 2.0)
+unique_ids: set[int] = {1, 2, 3}
+
+# Optional
+optional_value: str | None = None
+```
+
+### Function Annotations
+
+```python
+def greet(name: str, age: int = 0) -> str:
+ """Greet a person."""
+ return f"Hello {name}, age {age}"
+
+def process_items(items: list[int]) -> dict[str, int]:
+ """Process list of items."""
+ return {"count": len(items), "sum": sum(items)}
+
+# No return value
+def log_message(msg: str) -> None:
+ """Log a message."""
+ print(msg)
+```
+
+### Type Aliases
+
+```python
+from typing import TypeAlias
+
+# Simple alias
+UserId: TypeAlias = int
+Username: TypeAlias = str
+
+# Complex alias
+UserData: TypeAlias = dict[str, str | int]
+ProcessingResult: TypeAlias = tuple[bool, str, list[int]]
+
+def get_user(user_id: UserId) -> UserData:
+ """Get user data."""
+ return {"id": user_id, "name": "John"}
+```
+
+### Generics
+
+```python
+from typing import TypeVar, Generic
+
+T = TypeVar("T")
+
+class Box(Generic[T]):
+ """Generic box container."""
+
+ def __init__(self, value: T) -> None:
+ self.value = value
+
+ def get(self) -> T:
+ return self.value
+
+# Usage
+int_box: Box[int] = Box(42)
+str_box: Box[str] = Box("hello")
+```
+
+### Protocols
+
+```python
+from typing import Protocol
+
+class Drawable(Protocol):
+ """Protocol for drawable objects."""
+
+ def draw(self) -> str:
+ """Draw the object."""
+ ...
+
+class Circle:
+ """Circle that implements Drawable protocol."""
+
+ def draw(self) -> str:
+ return "Drawing circle"
+
+def render(obj: Drawable) -> None:
+ """Render any drawable object."""
+ print(obj.draw())
+```
+
+### Callable Types
+
+```python
+from typing import Callable
+
+# Function that takes int, returns str
+Transformer: TypeAlias = Callable[[int], str]
+
+def apply_transform(
+ value: int,
+ transform: Transformer,
+) -> str:
+ """Apply transformation function."""
+ return transform(value)
+
+# Usage
+def int_to_str(x: int) -> str:
+ return str(x)
+
+result = apply_transform(42, int_to_str)
+```
+
+## Advanced Patterns
+
+### Literal Types
+
+```python
+from typing import Literal
+
+def set_mode(mode: Literal["read", "write", "append"]) -> None:
+ """Set file mode."""
+ pass
+
+# Only accepts these exact strings
+set_mode("read") # OK
+set_mode("delete") # Error!
+```
+
+### TypedDict
+
+```python
+from typing import TypedDict
+
+class User(TypedDict):
+ """User data structure."""
+ id: int
+ name: str
+ email: str
+ age: int | None # Optional field
+
+def create_user(user: User) -> None:
+ """Create a user."""
+ print(user["name"])
+
+# Usage
+user: User = {
+ "id": 1,
+ "name": "John",
+ "email": "john@example.com",
+ "age": None,
+}
+```
+
+### Union Types
+
+```python
+def process(value: int | str | None) -> str:
+ """Process value of multiple types."""
+ if value is None:
+ return "empty"
+ if isinstance(value, int):
+ return f"number: {value}"
+ return f"string: {value}"
+```
+
+## Type Checking Best Practices
+
+1. **Start with `--strict`** - Catches most issues
+2. **Add type hints everywhere** - Functions, variables, attributes
+3. **Use type aliases** - For complex types
+4. **Leverage protocols** - For structural subtyping
+5. **Check CI** - Run mypy in continuous integration
+6. **Fix incrementally** - Use per-file overrides if needed
+7. **Type stubs** - Install type stubs for third-party libraries
+
+```bash
+# Install type stubs
+uv add --dev types-requests types-pyyaml
+```
+
+## Common Issues
+
+### Ignoring Errors
+
+```python
+# Ignore error on specific line
+result = unsafe_function() # type: ignore
+
+# Ignore specific error code
+result = unsafe_function() # type: ignore[arg-type]
+
+# File-level ignore (use sparingly)
+# mypy: ignore-errors
+```
+
+### Dealing with `Any`
+
+```python
+from typing import Any, cast
+
+# Avoid Any when possible
+def bad(data: Any) -> Any:
+ return data
+
+# Better: Be specific
+def good(data: dict[str, str]) -> dict[str, str]:
+ return data
+
+# If Any is unavoidable, cast to specific type
+raw_data: Any = get_external_data()
+user_data: dict[str, str] = cast(dict[str, str], raw_data)
+```
dots/.config/claude/skills/Python/workflows/Workspace.md
@@ -0,0 +1,91 @@
+# Workspace Workflow
+
+Manage multiple Python packages in a monorepo with uv workspaces.
+
+## When to Use
+
+- "python monorepo"
+- "multiple packages"
+- "workspace"
+- "manage related packages"
+
+## Quick Setup
+
+### Create Workspace Structure
+
+```
+myworkspace/
+├── pyproject.toml # Workspace root
+├── packages/
+│ ├── core/
+│ │ ├── pyproject.toml
+│ │ └── src/core/
+│ ├── api/
+│ │ ├── pyproject.toml
+│ │ └── src/api/
+│ └── cli/
+│ ├── pyproject.toml
+│ └── src/cli/
+└── uv.lock # Shared lock file
+```
+
+### Root pyproject.toml
+
+```toml
+[tool.uv.workspace]
+members = ["packages/*"]
+
+[tool.uv.sources]
+# Workspace dependencies
+core = { workspace = true }
+api = { workspace = true }
+cli = { workspace = true }
+```
+
+### Package pyproject.toml
+
+```toml
+# packages/api/pyproject.toml
+[project]
+name = "myworkspace-api"
+version = "0.1.0"
+dependencies = [
+ "fastapi>=0.104.0",
+]
+
+[tool.uv.sources]
+core = { workspace = true } # Depend on workspace package
+```
+
+## Commands
+
+```bash
+# Sync all workspace packages
+uv sync
+
+# Build specific package
+cd packages/api
+uv build
+
+# Run from specific package
+cd packages/cli
+uv run myworkspace-cli
+
+# Add dependency to specific package
+cd packages/api
+uv add httpx
+```
+
+## Benefits
+
+1. **Single lock file** - Consistent versions across packages
+2. **Shared dependencies** - Avoid duplication
+3. **Local development** - Easy cross-package development
+4. **Atomic updates** - Update all packages together
+
+## Best Practices
+
+1. **Shared version** - Use same Python version
+2. **Consistent naming** - `workspace-package` pattern
+3. **Clear boundaries** - Each package has clear purpose
+4. **Minimal coupling** - Avoid circular dependencies
dots/.config/claude/skills/Python/README.md
@@ -0,0 +1,130 @@
+# Python Skill
+
+Modern Python development guidance using uv, ruff, mypy, and pytest.
+
+## Overview
+
+This skill provides comprehensive guidance for Python development following 2025 best practices with modern tooling:
+
+- **uv**: Fast, all-in-one package manager (replaces pip, poetry, pyenv, virtualenv)
+- **ruff**: Lightning-fast linter and formatter (replaces Black, isort, flake8, pylint)
+- **mypy**: Static type checker for type safety
+- **pytest**: Feature-rich testing framework
+
+## Workflows
+
+### Project Management
+- **[Project](workflows/Project.md)**: Create and configure Python projects
+- **[Script](workflows/Script.md)**: Single-file scripts with PEP 723 inline metadata
+- **[Workspace](workflows/Workspace.md)**: Manage monorepos with multiple packages
+
+### Code Quality
+- **[Lint](workflows/Lint.md)**: Lint and format code with ruff
+- **[Type](workflows/Type.md)**: Type check with mypy
+- **[Test](workflows/Test.md)**: Run tests with pytest and coverage
+
+### Dependencies
+- **[Deps](workflows/Deps.md)**: Manage dependencies with uv
+- **[Package](workflows/Package.md)**: Build and publish packages
+
+## Tools
+
+### python-lint
+Run ruff linter and formatter on your code:
+```bash
+./tools/python-lint [directory]
+```
+
+### python-check
+Run all quality checks (lint, type check, tests):
+```bash
+./tools/python-check [directory]
+```
+
+## Quick Start
+
+### New Project
+```bash
+# Create project
+uv init myproject
+cd myproject
+
+# Add dependencies
+uv add requests pydantic rich
+uv add --dev pytest ruff mypy
+
+# Run checks
+uv run ruff check --fix .
+uv run ruff format .
+uv run mypy .
+uv run pytest
+```
+
+### Single Script
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "requests>=2.31.0",
+# ]
+# ///
+import requests
+
+def main() -> int:
+ response = requests.get("https://api.github.com")
+ print(response.json())
+ return 0
+
+if __name__ == "__main__":
+ import sys
+ sys.exit(main())
+```
+
+```bash
+chmod +x script.py
+./script.py # uv handles dependencies automatically!
+```
+
+## Key Features
+
+### PEP 723 Support
+Inline script metadata allows standalone scripts to declare dependencies directly in the file. No separate requirements.txt needed!
+
+### Modern Type Hints
+Full support for Python 3.10+ type syntax:
+```python
+def process(items: list[str], config: dict[str, int] | None = None) -> list[int]:
+ """Process items with optional config."""
+ pass
+```
+
+### Fast Tooling
+- uv is 10-100x faster than pip
+- ruff is 10-100x faster than traditional linters
+- Both written in Rust for maximum performance
+
+## Best Practices
+
+1. **Use uv for everything** - Package management, venv, running scripts
+2. **Type hints everywhere** - Enable mypy strict mode
+3. **Lint with ruff** - Auto-fix before committing
+4. **Test with pytest** - Aim for >80% coverage
+5. **Lock dependencies** - Commit uv.lock to git
+6. **Use PEP 723** - For single-file scripts
+7. **Follow src layout** - Place code in `src/package/`
+
+## Resources
+
+### Documentation
+- [uv Documentation](https://docs.astral.sh/uv/)
+- [PEP 723 - Inline Script Metadata](https://peps.python.org/pep-0723/)
+- [Ruff Documentation](https://docs.astral.sh/ruff/)
+- [mypy Documentation](https://mypy.readthedocs.io/)
+- [pytest Documentation](https://docs.pytest.org/)
+
+### Articles
+- [Python UV: The Ultimate Guide (DataCamp)](https://www.datacamp.com/tutorial/python-uv)
+- [Managing Python Projects With uv (Real Python)](https://realpython.com/python-uv/)
+- [Share Python Scripts Like a Pro: uv and PEP 723](https://thisdavej.com/share-python-scripts-like-a-pro-uv-and-pep-723-for-easy-deployment/)
+- [Python Testing Best Practices](https://pytest-with-eric.com/introduction/python-unit-testing-best-practices/)
dots/.config/claude/skills/Python/SKILL.md
@@ -0,0 +1,528 @@
+---
+name: Python
+description: Python development best practices and modern tooling with uv. USE WHEN writing Python code, managing dependencies, testing, type checking, or working with Python projects.
+---
+
+# Python Development Best Practices
+
+## Purpose
+Guide Python development using modern tooling (uv, ruff, mypy, pytest) following Python Enhancement Proposals (PEPs), type hints best practices, and community standards for 2025.
+
+### Context Detection
+
+**This skill activates when:**
+- Current directory contains `pyproject.toml`, `requirements.txt`, or `uv.lock`
+- Git repository contains Python files (`.py`, `.pyi`)
+- User is working with Python code or virtual environments
+- Commands like `uv`, `pytest`, `ruff`, or `mypy` are mentioned
+- User explicitly asks about Python development
+
+## Workflow Routing
+
+**When executing a workflow, output this notification directly:**
+
+```
+Running the **WorkflowName** workflow from the **Python** skill...
+```
+
+| Workflow | Trigger | File |
+|----------|---------|------|
+| **Project** | "create python project", "new project", "uv init" | `workflows/Project.md` |
+| **Script** | "python script", "single file", "uv run", "PEP 723" | `workflows/Script.md` |
+| **Test** | "run tests", "pytest", "test coverage", "tox" | `workflows/Test.md` |
+| **Lint** | "lint", "format", "ruff check", "ruff format" | `workflows/Lint.md` |
+| **Type** | "type check", "mypy", "type hints", "pyright" | `workflows/Type.md` |
+| **Deps** | "dependencies", "uv add", "requirements", "lock file" | `workflows/Deps.md` |
+| **Package** | "build package", "publish", "pypi", "wheel" | `workflows/Package.md` |
+| **Workspace** | "monorepo", "workspace", "multiple packages" | `workflows/Workspace.md` |
+
+## Core Principles
+
+1. **Use uv for everything**: uv is the modern, fast, all-in-one tool replacing pip, pip-tools, poetry, pyenv, virtualenv, and more (10-100x faster)
+2. **Type hints everywhere**: Use type annotations for all functions and classes; run mypy in strict mode
+3. **Ruff for quality**: Use ruff for both linting and formatting (replaces Black, isort, flake8, and more)
+4. **Test with pytest**: Write comprehensive tests using pytest with fixtures and parametrization
+5. **Lock dependencies**: Always maintain `uv.lock` for reproducible builds
+6. **PEP 723 for scripts**: Use inline script metadata for single-file Python scripts
+
+## Modern Python Toolchain (2025)
+
+### Package Management: uv
+
+**Why uv?**
+- Written in Rust, 10-100x faster than pip
+- All-in-one: replaces pip, virtualenv, poetry, pyenv, pipx
+- Lockfile support for reproducible builds
+- Automatic Python version management
+- First-class support for PEP 723 inline script metadata
+
+**Installation:**
+```bash
+# On NixOS (preferred in this repo)
+# Already available via home-manager or system packages
+
+# Standalone (if needed elsewhere)
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+### Code Quality: ruff
+
+**Why ruff?**
+- Extremely fast (10-100x faster than traditional tools)
+- Replaces: Black, isort, flake8, pylint, pyupgrade, autoflake
+- Single tool for linting and formatting
+- Compatible with Black formatting
+
+**Usage:**
+```bash
+# Check for issues
+ruff check .
+
+# Auto-fix issues
+ruff check --fix .
+
+# Format code
+ruff format .
+```
+
+### Type Checking: mypy
+
+**Why mypy?**
+- Industry standard for static type checking
+- Catches bugs before runtime
+- Improves code documentation and IDE support
+- Works alongside ruff (not replaced by it)
+
+**Usage:**
+```bash
+# Type check entire project
+mypy .
+
+# Strict mode
+mypy --strict .
+
+# With specific Python version
+mypy --python-executable .venv/bin/python .
+```
+
+### Testing: pytest
+
+**Why pytest?**
+- Most popular testing framework
+- Simple syntax, powerful features
+- Excellent fixture system
+- Parametrization for data-driven tests
+- Rich plugin ecosystem
+
+**Usage:**
+```bash
+# Run all tests
+pytest
+
+# With coverage
+pytest --cov
+
+# Specific test
+pytest tests/test_auth.py::test_login
+```
+
+## Project Structure
+
+### Standard Layout
+```
+myproject/
+├── src/
+│ └── myproject/
+│ ├── __init__.py
+│ ├── main.py
+│ └── utils.py
+├── tests/
+│ ├── __init__.py
+│ ├── test_main.py
+│ └── test_utils.py
+├── docs/
+│ └── README.md
+├── .gitignore
+├── .python-version # Python version (managed by uv)
+├── pyproject.toml # Project configuration
+├── uv.lock # Locked dependencies
+└── README.md
+```
+
+### Key Files
+
+**`pyproject.toml`** - Project configuration (PEP 621)
+```toml
+[project]
+name = "myproject"
+version = "0.1.0"
+description = "Description of my project"
+readme = "README.md"
+requires-python = ">=3.11"
+dependencies = [
+ "requests>=2.31.0",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=8.0.0",
+ "ruff>=0.8.0",
+ "mypy>=1.13.0",
+]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.ruff]
+line-length = 100
+target-version = "py311"
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "N", "W", "B", "Q"]
+
+[tool.mypy]
+python_version = "3.11"
+strict = true
+warn_return_any = true
+warn_unused_configs = true
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = "test_*.py"
+python_functions = "test_*"
+```
+
+**`.python-version`** - Pin Python version
+```
+3.11
+```
+
+## Writing Python Code
+
+### Type Hints
+
+**Always use type hints:**
+```python
+from typing import List, Dict, Optional, Union
+from pathlib import Path
+
+def process_data(
+ items: list[str],
+ config: dict[str, int],
+ output_path: Path | None = None,
+) -> list[int]:
+ """Process items according to configuration.
+
+ Args:
+ items: List of items to process
+ config: Configuration dictionary
+ output_path: Optional path to write results
+
+ Returns:
+ List of processed integers
+
+ Raises:
+ ValueError: If items is empty
+ """
+ if not items:
+ raise ValueError("items cannot be empty")
+
+ results: list[int] = []
+ for item in items:
+ value = config.get(item, 0)
+ results.append(value)
+
+ if output_path is not None:
+ output_path.write_text(str(results))
+
+ return results
+```
+
+**Modern type syntax (Python 3.10+):**
+```python
+# Use | instead of Union
+def foo(x: int | str) -> str: ...
+
+# Use list, dict, tuple directly (no need for typing.List, etc.)
+def bar(items: list[int]) -> dict[str, list[int]]: ...
+```
+
+### Error Handling
+
+**Be explicit about errors:**
+```python
+from pathlib import Path
+import logging
+
+logger = logging.getLogger(__name__)
+
+def read_config(path: Path) -> dict[str, str]:
+ """Read configuration from file.
+
+ Args:
+ path: Path to configuration file
+
+ Returns:
+ Configuration dictionary
+
+ Raises:
+ FileNotFoundError: If config file doesn't exist
+ ValueError: If config file is invalid
+ """
+ if not path.exists():
+ raise FileNotFoundError(f"Config file not found: {path}")
+
+ try:
+ content = path.read_text()
+ # Parse content...
+ config = parse_config(content)
+ except Exception as e:
+ logger.error(f"Failed to parse config: {e}")
+ raise ValueError(f"Invalid config file: {path}") from e
+
+ return config
+```
+
+### Docstrings
+
+**Use Google or NumPy style docstrings:**
+```python
+def calculate_metrics(
+ data: list[float],
+ method: str = "mean",
+) -> dict[str, float]:
+ """Calculate statistical metrics for data.
+
+ Args:
+ data: List of numeric values
+ method: Calculation method ("mean", "median", "mode")
+
+ Returns:
+ Dictionary containing calculated metrics with keys:
+ - value: The calculated metric
+ - confidence: Confidence interval
+
+ Raises:
+ ValueError: If method is not supported or data is empty
+
+ Examples:
+ >>> calculate_metrics([1, 2, 3], method="mean")
+ {'value': 2.0, 'confidence': 0.95}
+ """
+ if not data:
+ raise ValueError("data cannot be empty")
+
+ # Implementation...
+```
+
+## Single-File Scripts (PEP 723)
+
+**Use uv with inline script metadata for standalone scripts:**
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "requests>=2.31.0",
+# "rich>=13.0.0",
+# ]
+# ///
+"""Script to fetch and display API data."""
+
+import requests
+from rich.console import Console
+
+console = Console()
+
+def main() -> None:
+ """Fetch and display data from API."""
+ response = requests.get("https://api.example.com/data")
+ response.raise_for_status()
+
+ data = response.json()
+ console.print(data)
+
+if __name__ == "__main__":
+ main()
+```
+
+**Make it executable:**
+```bash
+chmod +x script.py
+./script.py # uv automatically handles dependencies!
+```
+
+## Testing with pytest
+
+### Test Structure
+
+```python
+# tests/test_calculator.py
+import pytest
+from myproject.calculator import Calculator
+
+class TestCalculator:
+ """Test suite for Calculator class."""
+
+ @pytest.fixture
+ def calc(self) -> Calculator:
+ """Provide a Calculator instance."""
+ return Calculator()
+
+ def test_add(self, calc: Calculator) -> None:
+ """Test addition operation."""
+ result = calc.add(2, 3)
+ assert result == 5
+
+ @pytest.mark.parametrize("a,b,expected", [
+ (2, 3, 5),
+ (0, 0, 0),
+ (-1, 1, 0),
+ (10, -5, 5),
+ ])
+ def test_add_parametrized(
+ self,
+ calc: Calculator,
+ a: int,
+ b: int,
+ expected: int,
+ ) -> None:
+ """Test addition with multiple inputs."""
+ assert calc.add(a, b) == expected
+
+ def test_divide_by_zero(self, calc: Calculator) -> None:
+ """Test division by zero raises error."""
+ with pytest.raises(ValueError, match="Cannot divide by zero"):
+ calc.divide(10, 0)
+```
+
+### Coverage Configuration
+
+Add to `pyproject.toml`:
+```toml
+[tool.coverage.run]
+source = ["src"]
+omit = ["tests/*", "**/__pycache__/*"]
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
+
+[tool.coverage.html]
+directory = "htmlcov"
+```
+
+## Common Workflows
+
+### New Project
+```bash
+# Create new project
+uv init myproject
+cd myproject
+
+# Add dependencies
+uv add requests rich
+
+# Add dev dependencies
+uv add --dev pytest ruff mypy
+
+# Run tests
+uv run pytest
+```
+
+### Existing Project
+```bash
+# Sync environment with lock file
+uv sync
+
+# Run app
+uv run python -m myproject
+
+# Run tests
+uv run pytest
+
+# Lint and format
+uv run ruff check --fix .
+uv run ruff format .
+
+# Type check
+uv run mypy .
+```
+
+### Scripts
+```bash
+# Run script (with automatic dependency installation)
+uv run script.py
+
+# Add dependencies to existing script
+uv add --script script.py requests
+```
+
+## Integration with NixOS
+
+This repository uses NixOS with home-manager. Python tools are configured via:
+
+**System/Home packages:**
+```nix
+# home/common/dev/python.nix
+{ pkgs, ... }: {
+ home.packages = with pkgs; [
+ uv # Package manager
+ ruff # Linter/formatter
+ mypy # Type checker
+ python311 # Python interpreter
+ ];
+}
+```
+
+**Shell environment:**
+```bash
+# uv manages virtual environments automatically
+# No need for manual venv activation
+
+# Tools available system-wide
+uv --version
+ruff --version
+mypy --version
+```
+
+## Best Practices Checklist
+
+- [ ] Use `uv` for all package management
+- [ ] Add type hints to all functions
+- [ ] Run `mypy --strict` with no errors
+- [ ] Use `ruff` for linting and formatting
+- [ ] Write tests with `pytest` (aim for >80% coverage)
+- [ ] Use PEP 723 for single-file scripts
+- [ ] Pin Python version in `.python-version`
+- [ ] Lock dependencies in `uv.lock`
+- [ ] Document functions with docstrings
+- [ ] Handle errors explicitly
+- [ ] Use `pathlib.Path` instead of string paths
+- [ ] Follow PEP 8 naming conventions
+- [ ] Keep functions focused and small
+- [ ] Use descriptive variable names
+
+## Resources
+
+### Official Documentation
+- [uv Documentation](https://docs.astral.sh/uv/)
+- [PEP 723 - Inline Script Metadata](https://peps.python.org/pep-0723/)
+- [Ruff Documentation](https://docs.astral.sh/ruff/)
+- [mypy Documentation](https://mypy.readthedocs.io/)
+- [pytest Documentation](https://docs.pytest.org/)
+
+### Best Practices
+- [Python UV: The Ultimate Guide (DataCamp)](https://www.datacamp.com/tutorial/python-uv)
+- [uv: An In-Depth Guide](https://www.saaspegasus.com/guides/uv-deep-dive/)
+- [Managing Python Projects With uv (Real Python)](https://realpython.com/python-uv/)
+- [Python Testing Best Practices](https://pytest-with-eric.com/introduction/python-unit-testing-best-practices/)
+
+### References
+- Python Enhancement Proposals (PEPs)
+- Google Python Style Guide
+- Type Hints Best Practices
+- pytest Good Integration Practices