dignified-python-313
This skill should be used when editing Python code in the erk codebase. Use when writing, reviewing, or refactoring Python to ensure adherence to LBYL exception handling patterns, Python 3.13+ type syntax (list[str], str | None), pathlib operations, ABC-based interfaces, absolute imports, and explicit error boundaries at CLI level. Also provides production-tested code smell patterns from Dagster Labs for API design, parameter complexity, and code organization. Essential for maintaining erk's dignified Python standards.
$ Installer
git clone https://github.com/C00ldudeNoonan/economic-data-project /tmp/economic-data-project && cp -r /tmp/economic-data-project/.claude/skills/dignified-python ~/.claude/skills/economic-data-project// tip: Run this command in your terminal to install the skill
name: dignified-python-313 description: This skill should be used when editing Python code in the erk codebase. Use when writing, reviewing, or refactoring Python to ensure adherence to LBYL exception handling patterns, Python 3.13+ type syntax (list[str], str | None), pathlib operations, ABC-based interfaces, absolute imports, and explicit error boundaries at CLI level. Also provides production-tested code smell patterns from Dagster Labs for API design, parameter complexity, and code organization. Essential for maintaining erk's dignified Python standards.
Dignified Python - Python 3.13+ Coding Standards
Write explicit, predictable code that fails fast at proper boundaries.
Quick Reference - Check Before Coding
| If you're about to write... | Check this rule |
|---|---|
try: or except: | → Exception Handling - Default: let exceptions bubble |
from __future__ import annotations | → FORBIDDEN - Python 3.13+ doesn't need it |
List[...], Dict[...], Union[...] | → Use list[...], dict[...], X | Y |
dict[key] without checking | → Use if key in dict: or .get() |
path.resolve() or path.is_relative_to() | → Check path.exists() first |
typing.Protocol | → Use abc.ABC instead |
from .module import | → Use absolute imports only |
__all__ = ["..."] in __init__.py | → See references/core-standards.md#code-in-initpy-and-all-exports |
print(...) in CLI code | → Use click.echo() |
subprocess.run(...) | → Add check=True |
@property with I/O or expensive computation | → See references/core-standards.md#performance-expectations |
| Function with many optional parameters | → See references/code-smells-dagster.md |
repr() for sorting or hashing | → See references/code-smells-dagster.md |
| Context object passed everywhere | → See references/code-smells-dagster.md |
| Function with 10+ local variables | → See references/code-smells-dagster.md |
| Class with 50+ methods | → See references/code-smells-dagster.md |
CRITICAL RULES (Top 6)
1. Exception Handling - NEVER for Control Flow 🔴
ALWAYS use LBYL (Look Before You Leap), NEVER EAFP
# ✅ CORRECT: Check before acting
if key in mapping:
value = mapping[key]
else:
handle_missing_key()
# ❌ WRONG: Using exceptions for control flow
try:
value = mapping[key]
except KeyError:
handle_missing_key()
Details: See references/core-standards.md#exception-handling for complete patterns
2. Type Annotations - Python 3.13+ Syntax Only 🔴
FORBIDDEN: from __future__ import annotations
# ✅ CORRECT: Modern Python 3.13+ syntax
def process(items: list[str]) -> dict[str, int]: ...
def find_user(id: int) -> User | None: ...
# ❌ WRONG: Legacy syntax
from typing import List, Dict, Optional
def process(items: List[str]) -> Dict[str, int]: ...
Details: See references/core-standards.md#type-annotations for all patterns
3. Path Operations - Check Exists First 🔴
# ✅ CORRECT: Check exists first
if path.exists():
resolved = path.resolve()
# ❌ WRONG: Using exceptions
try:
resolved = path.resolve()
except OSError:
pass
Details: See references/core-standards.md#path-operations
4. Dependency Injection - ABC Not Protocol 🔴
# ✅ CORRECT: Use ABC
from abc import ABC, abstractmethod
class MyOps(ABC):
@abstractmethod
def operation(self) -> None: ...
# ❌ WRONG: Using Protocol
from typing import Protocol
Details: See references/core-standards.md#dependency-injection
5. Imports - Module-Level and Absolute 🔴
ALL imports must be at module level unless preventing circular imports
# ✅ CORRECT: Module-level, absolute imports
from erk.config import load_config
from pathlib import Path
import click
# ❌ WRONG: Inline imports (unless for circular import prevention)
def my_function():
from erk.config import load_config # WRONG unless circular import
return load_config()
# ❌ WRONG: Relative imports
from .config import load_config
Exception: Inline imports are ONLY acceptable when preventing circular imports. Always document why:
def create_context():
# Inline import to avoid circular dependency with tests
from tests.fakes.gitops import FakeGitOps
return FakeGitOps()
Details: See references/core-standards.md#imports
6. No Silent Fallback Behavior 🔴
# ❌ WRONG: Silent fallback
try:
result = primary_method()
except:
result = fallback_method() # Untested, brittle
# ✅ CORRECT: Let error bubble up
result = primary_method()
Details: See references/core-standards.md#anti-patterns
When to Load References
Load references/core-standards.md when:
- Writing exception handling code (LBYL patterns)
- Working with type annotations (Python 3.13+ syntax)
- Implementing path operations (exists() checks)
- Creating ABC interfaces (dependency injection)
- Organizing imports (absolute imports, module-level)
- Working with CLI code (Click patterns)
- Using dataclasses and immutability
- Avoiding anti-patterns (silent fallback, exception swallowing)
- Implementing
@propertyor__len__(performance expectations)
Load references/code-smells-dagster.md when:
- Designing function APIs (default parameters, keyword arguments)
- Managing parameter complexity (parameter anxiety, invalid combinations)
- Refactoring large functions/classes (god classes, local variables)
- Working with context managers (assignment patterns)
- Using
repr()programmatically (string representation abuse) - Passing context objects (context coupling)
- Dealing with error boundaries (early validation)
Load references/patterns-reference.md when:
- Developing CLI commands with Click
- Working with file I/O and pathlib
- Implementing dataclasses and frozen structures
- Managing subprocess operations
- Reducing code nesting (early returns, helper functions)
Progressive Disclosure Guide
This skill uses a three-level loading system:
- This file (SKILL.md): Core rules and navigation (~350 lines)
- Reference files: Detailed patterns and examples (loaded as needed)
- Quick lookup: Use the tables above to find what you need
Claude loads reference files only when needed based on the current task. The reference files contain:
core-standards.md: Foundational Python patterns from this skillcode-smells-dagster.md: Production-tested anti-patterns from Dagster Labspatterns-reference.md: Common implementation patterns and examples
Philosophy
Write dignified Python code that:
- Fails fast at proper boundaries (not deep in the stack)
- Makes invalid states unrepresentable (use the type system)
- Expresses intent clearly (LBYL over EAFP)
- Minimizes cognitive load (explicit over implicit)
- Enables confident refactoring (test what you build)
Default stances:
- Let exceptions bubble up (handle at boundaries only)
- Break APIs and migrate immediately (no unnecessary backwards compatibility)
- Check conditions proactively (LBYL)
- Use modern Python 3.13+ syntax
Quick Decision Tree
About to write Python code?
-
Using
try/except?- Can you use LBYL instead? → Do that
- Is this an error boundary? → OK to handle
- Otherwise → Let it bubble
-
Using type hints?
- Use
list[str],str | None, notList,Optional - NO
from __future__ import annotations
- Use
-
Working with paths?
- Check
.exists()before.resolve() - Use
pathlib.Path, notos.path
- Check
-
Writing CLI code?
- Use
click.echo(), notprint() - Exit with
raise SystemExit(1)
- Use
-
Too many parameters?
- See
references/code-smells-dagster.md#parameter-anxiety
- See
-
Class getting large?
- See
references/code-smells-dagster.md#god-classes
- See
Checklist Before Writing Code
Before writing try/except:
- Can I check the condition proactively? (LBYL)
- Is this at an error boundary? (CLI/API level)
- Am I adding meaningful context or just hiding the error?
Before using type hints:
- Am I using Python 3.13+ syntax? (
list,dict,|) - Have I removed all
typingimports except essentials?
Before path operations:
- Did I check
.exists()before.resolve()? - Am I using
pathlib.Path? - Did I specify
encoding="utf-8"?
Before adding backwards compatibility:
- Did the user explicitly request it?
- Is this a public API?
- Default: Break and migrate immediately
Common Patterns Summary
| Scenario | Preferred Approach | Avoid |
|---|---|---|
| Dictionary access | if key in dict: or .get(key, default) | try: dict[key] except KeyError: |
| File existence | if path.exists(): | try: open(path) except FileNotFoundError: |
| Type checking | if isinstance(obj, Type): | try: obj.method() except AttributeError: |
| Value validation | if is_valid(value): | try: process(value) except ValueError: |
| Path resolution | if path.exists(): path.resolve() | try: path.resolve() except OSError: |
References
- Core Standards:
references/core-standards.md- Detailed LBYL patterns, type annotations, imports - Code Smells:
references/code-smells-dagster.md- Production-tested anti-patterns - Pattern Reference:
references/patterns-reference.md- CLI, file I/O, dataclasses - Python 3.13 docs: https://docs.python.org/3.13/
Repository
