Initial commit
This commit is contained in:
89
skills/python-code-quality/SKILL.md
Normal file
89
skills/python-code-quality/SKILL.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Python Code Quality Skill
|
||||
|
||||
Ensure Python code follows best practices with automated linting, formatting, and type checking using ruff and mypy.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- User requests code linting or formatting
|
||||
- User mentions code quality, PEP 8, or style issues
|
||||
- User wants to fix linting errors
|
||||
- User asks about type checking
|
||||
- Code review processes require quality checks
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
1. **Linting with Ruff**
|
||||
- Check code for style violations
|
||||
- Identify potential bugs and code smells
|
||||
- Enforce PEP 8 compliance
|
||||
- Auto-fix issues where possible
|
||||
|
||||
2. **Code Formatting**
|
||||
- Format code consistently with ruff
|
||||
- Organize imports automatically
|
||||
- Ensure consistent indentation and line length
|
||||
|
||||
3. **Type Checking with Mypy**
|
||||
- Verify type annotations are correct
|
||||
- Catch type-related bugs before runtime
|
||||
- Ensure type safety across the codebase
|
||||
|
||||
4. **Code Quality Metrics**
|
||||
- Complexity analysis
|
||||
- Dead code detection
|
||||
- Unused import identification
|
||||
|
||||
## Context Files
|
||||
|
||||
This skill references the following context files in this directory:
|
||||
- `ruff-configuration.md` - Ruff linting and formatting rules
|
||||
- `mypy-configuration.md` - Type checking configuration
|
||||
- `common-issues.md` - Common Python code quality issues and fixes
|
||||
- `best-practices.md` - Python coding best practices
|
||||
|
||||
## Key Tools and Commands
|
||||
|
||||
```bash
|
||||
# Linting
|
||||
ruff check . # Check for issues
|
||||
ruff check --fix . # Auto-fix issues
|
||||
ruff check --watch . # Watch mode
|
||||
|
||||
# Formatting
|
||||
ruff format . # Format all files
|
||||
ruff format --check . # Check if formatting needed
|
||||
|
||||
# Type checking
|
||||
mypy src # Check types
|
||||
mypy --strict src # Strict mode
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Pre-commit Quality Check
|
||||
```bash
|
||||
ruff check --fix . && ruff format . && mypy src
|
||||
```
|
||||
|
||||
### CI/CD Pipeline
|
||||
```bash
|
||||
ruff check . # Fail if unfixed issues
|
||||
ruff format --check . # Fail if unformatted
|
||||
mypy src # Fail on type errors
|
||||
```
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
After using this skill:
|
||||
- All code follows PEP 8 style guidelines
|
||||
- Imports are properly organized
|
||||
- Type hints are correct and comprehensive
|
||||
- Code is consistently formatted
|
||||
- Common bugs and issues are identified
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
- Works with `python-project-setup` for initial configuration
|
||||
- Complements `python-testing` for comprehensive quality assurance
|
||||
- Used by `python-project-setup` agent for automated checks
|
||||
374
skills/python-code-quality/mypy-configuration.md
Normal file
374
skills/python-code-quality/mypy-configuration.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# Mypy Type Checking Configuration
|
||||
|
||||
Mypy is a static type checker for Python that helps catch bugs before runtime.
|
||||
|
||||
## Basic Configuration
|
||||
|
||||
Add to `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
check_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_no_return = true
|
||||
strict_equality = true
|
||||
show_error_codes = true
|
||||
show_column_numbers = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests.*"
|
||||
disallow_untyped_defs = false
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "third_party_package.*"
|
||||
ignore_missing_imports = true
|
||||
```
|
||||
|
||||
## Strictness Levels
|
||||
|
||||
### Minimal (Getting Started)
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = false
|
||||
warn_unused_configs = true
|
||||
```
|
||||
|
||||
### Moderate (Recommended)
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
check_untyped_defs = true
|
||||
```
|
||||
|
||||
### Strict (Maximum Safety)
|
||||
```toml
|
||||
[tool.mypy]
|
||||
strict = true
|
||||
python_version = "3.11"
|
||||
```
|
||||
|
||||
## Common Configuration Options
|
||||
|
||||
### Type Checking Strictness
|
||||
|
||||
```toml
|
||||
# Require type annotations
|
||||
disallow_untyped_defs = true # Functions must have type hints
|
||||
disallow_untyped_calls = true # Can't call untyped functions
|
||||
disallow_incomplete_defs = true # All params must be typed
|
||||
|
||||
# Optional handling
|
||||
no_implicit_optional = true # Optional must be explicit
|
||||
strict_optional = true # None checking enabled
|
||||
|
||||
# Any type usage
|
||||
disallow_any_unimported = false # Allow Any from untyped imports
|
||||
disallow_any_expr = false # Forbid Any in expressions
|
||||
disallow_any_decorated = false # Forbid Any in decorators
|
||||
disallow_any_explicit = false # Forbid explicit Any
|
||||
```
|
||||
|
||||
### Warning Messages
|
||||
|
||||
```toml
|
||||
warn_return_any = true # Warn on returning Any
|
||||
warn_redundant_casts = true # Warn on unnecessary casts
|
||||
warn_unused_ignores = true # Warn on unused type: ignore
|
||||
warn_unused_configs = true # Warn on unused config options
|
||||
warn_no_return = true # Warn on missing returns
|
||||
warn_unreachable = true # Warn on unreachable code
|
||||
```
|
||||
|
||||
### Error Reporting
|
||||
|
||||
```toml
|
||||
show_error_codes = true # Show error codes in messages
|
||||
show_column_numbers = true # Show column numbers
|
||||
pretty = true # Pretty print errors
|
||||
color_output = true # Colorize output
|
||||
error_summary = true # Show error summary
|
||||
```
|
||||
|
||||
## Type Hints Guide
|
||||
|
||||
### Basic Types
|
||||
|
||||
```python
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
# Simple types
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}"
|
||||
|
||||
# Optional (can be None)
|
||||
def find_user(user_id: int) -> Optional[User]:
|
||||
return user_map.get(user_id)
|
||||
|
||||
# Modern Optional syntax (Python 3.10+)
|
||||
def find_user(user_id: int) -> User | None:
|
||||
return user_map.get(user_id)
|
||||
|
||||
# Union types
|
||||
def process(value: Union[int, str]) -> bool:
|
||||
return True
|
||||
|
||||
# Modern Union syntax (Python 3.10+)
|
||||
def process(value: int | str) -> bool:
|
||||
return True
|
||||
```
|
||||
|
||||
### Collections
|
||||
|
||||
```python
|
||||
from typing import List, Dict, Set, Tuple
|
||||
|
||||
# Lists
|
||||
def process_items(items: List[str]) -> List[int]:
|
||||
return [len(item) for item in items]
|
||||
|
||||
# Modern syntax (Python 3.9+)
|
||||
def process_items(items: list[str]) -> list[int]:
|
||||
return [len(item) for item in items]
|
||||
|
||||
# Dictionaries
|
||||
def get_config() -> Dict[str, Any]:
|
||||
return {"key": "value"}
|
||||
|
||||
# Modern syntax
|
||||
def get_config() -> dict[str, Any]:
|
||||
return {"key": "value"}
|
||||
|
||||
# Sets
|
||||
def unique_items(items: Set[int]) -> int:
|
||||
return len(items)
|
||||
|
||||
# Tuples (fixed size)
|
||||
def get_coordinates() -> Tuple[float, float]:
|
||||
return (1.0, 2.0)
|
||||
|
||||
# Tuples (variable size)
|
||||
def get_numbers() -> Tuple[int, ...]:
|
||||
return (1, 2, 3, 4, 5)
|
||||
```
|
||||
|
||||
### Callable Types
|
||||
|
||||
```python
|
||||
from typing import Callable
|
||||
|
||||
# Function that takes int and returns str
|
||||
Handler = Callable[[int], str]
|
||||
|
||||
def process(handler: Handler) -> None:
|
||||
result = handler(42)
|
||||
|
||||
# Multiple parameters
|
||||
Callback = Callable[[str, int], bool]
|
||||
|
||||
# No parameters
|
||||
Factory = Callable[[], MyClass]
|
||||
```
|
||||
|
||||
### Generic Types
|
||||
|
||||
```python
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class Container(Generic[T]):
|
||||
def __init__(self, value: T) -> None:
|
||||
self.value = value
|
||||
|
||||
def get(self) -> T:
|
||||
return self.value
|
||||
|
||||
# Usage
|
||||
container: Container[int] = Container(42)
|
||||
value: int = container.get()
|
||||
```
|
||||
|
||||
### Protocol Types
|
||||
|
||||
```python
|
||||
from typing import Protocol
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> None: ...
|
||||
|
||||
def render(item: Drawable) -> None:
|
||||
item.draw()
|
||||
|
||||
# Any class with draw() method works
|
||||
class Circle:
|
||||
def draw(self) -> None:
|
||||
print("Drawing circle")
|
||||
|
||||
render(Circle()) # Type checks!
|
||||
```
|
||||
|
||||
## Commands Reference
|
||||
|
||||
```bash
|
||||
# Check all files
|
||||
mypy src
|
||||
|
||||
# Check specific file
|
||||
mypy src/module.py
|
||||
|
||||
# Strict mode
|
||||
mypy --strict src
|
||||
|
||||
# Show error codes
|
||||
mypy --show-error-codes src
|
||||
|
||||
# Generate type stubs
|
||||
stubgen -p mypackage -o stubs
|
||||
|
||||
# Check against installed packages
|
||||
mypy --install-types
|
||||
mypy --non-interactive --install-types
|
||||
```
|
||||
|
||||
## Common Errors and Fixes
|
||||
|
||||
### Error: "Argument has incompatible type"
|
||||
|
||||
```python
|
||||
# Problem
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}"
|
||||
|
||||
greet(123) # Error: Argument 1 has incompatible type "int"
|
||||
|
||||
# Fix: Pass correct type
|
||||
greet("World")
|
||||
|
||||
# Or: Convert type
|
||||
greet(str(123))
|
||||
```
|
||||
|
||||
### Error: "Function is missing a return statement"
|
||||
|
||||
```python
|
||||
# Problem
|
||||
def get_value() -> int:
|
||||
if condition:
|
||||
return 42
|
||||
# Missing return!
|
||||
|
||||
# Fix: Add return for all paths
|
||||
def get_value() -> int:
|
||||
if condition:
|
||||
return 42
|
||||
return 0
|
||||
```
|
||||
|
||||
### Error: "Need type annotation"
|
||||
|
||||
```python
|
||||
# Problem
|
||||
items = [] # Error: Need type annotation
|
||||
|
||||
# Fix: Add type annotation
|
||||
items: list[str] = []
|
||||
|
||||
# Or: Initialize with values
|
||||
items = ["a", "b", "c"]
|
||||
```
|
||||
|
||||
### Error: "Incompatible return type"
|
||||
|
||||
```python
|
||||
# Problem
|
||||
def get_name() -> str:
|
||||
return None # Error: Incompatible return
|
||||
|
||||
# Fix: Use Optional
|
||||
def get_name() -> str | None:
|
||||
return None
|
||||
```
|
||||
|
||||
## Ignoring Errors
|
||||
|
||||
```python
|
||||
# Ignore single line
|
||||
result = unsafe_function() # type: ignore
|
||||
|
||||
# Ignore with reason
|
||||
result = unsafe_function() # type: ignore[arg-type]
|
||||
|
||||
# Ignore entire file
|
||||
# mypy: ignore-errors
|
||||
```
|
||||
|
||||
## Third-Party Package Stubs
|
||||
|
||||
### Installing Type Stubs
|
||||
|
||||
```bash
|
||||
# Install stubs for requests
|
||||
uv add --dev types-requests
|
||||
|
||||
# Common stubs
|
||||
uv add --dev types-requests
|
||||
uv add --dev types-redis
|
||||
uv add --dev types-PyYAML
|
||||
```
|
||||
|
||||
### Handling Missing Stubs
|
||||
|
||||
```toml
|
||||
[[tool.mypy.overrides]]
|
||||
module = "untyped_package.*"
|
||||
ignore_missing_imports = true
|
||||
```
|
||||
|
||||
## VSCode Integration
|
||||
|
||||
Add to `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"python.linting.mypyEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start gradual**: Enable mypy gradually, module by module
|
||||
2. **Use strict mode**: `strict = true` for new projects
|
||||
3. **Add type hints everywhere**: Functions, methods, variables
|
||||
4. **Use Protocol for duck typing**: Better than inheritance
|
||||
5. **Leverage modern syntax**: Use `list` instead of `List` (Python 3.9+)
|
||||
6. **Install type stubs**: For all third-party packages
|
||||
7. **Run in CI**: Fail builds on type errors
|
||||
8. **Document with types**: Types serve as documentation
|
||||
|
||||
## Incremental Adoption
|
||||
|
||||
```toml
|
||||
# Start with specific directories
|
||||
[tool.mypy]
|
||||
files = ["src/critical_module"]
|
||||
strict = true
|
||||
|
||||
# Gradually expand
|
||||
files = ["src/critical_module", "src/api"]
|
||||
|
||||
# Eventually cover everything
|
||||
files = ["src"]
|
||||
```
|
||||
229
skills/python-code-quality/ruff-configuration.md
Normal file
229
skills/python-code-quality/ruff-configuration.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Ruff Configuration Guide
|
||||
|
||||
Ruff is a fast Python linter and formatter that replaces multiple tools (isort, flake8, pyupgrade, etc.).
|
||||
|
||||
## Basic Configuration
|
||||
|
||||
Add to `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[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
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
"ARG", # flake8-unused-arguments
|
||||
"SIM", # flake8-simplify
|
||||
"TCH", # flake8-type-checking
|
||||
"RUF", # Ruff-specific rules
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E501", # line too long (handled by formatter)
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**/*.py" = [
|
||||
"ARG", # Unused function arguments allowed in tests
|
||||
"S101", # Assert allowed in tests
|
||||
]
|
||||
"__init__.py" = [
|
||||
"F401", # Unused imports allowed in __init__.py
|
||||
]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
line-ending = "auto"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["project_name"]
|
||||
```
|
||||
|
||||
## Rule Categories
|
||||
|
||||
### Error Detection (E, W, F)
|
||||
- **E**: PEP 8 style errors
|
||||
- **W**: PEP 8 style warnings
|
||||
- **F**: PyFlakes logical errors
|
||||
|
||||
### Code Quality (B, C4, SIM)
|
||||
- **B**: Bugbear - likely bugs and design problems
|
||||
- **C4**: Comprehensions - simplify list/dict/set comprehensions
|
||||
- **SIM**: Simplify - code simplification suggestions
|
||||
|
||||
### Modernization (UP)
|
||||
- **UP**: PyUpgrade - use modern Python syntax
|
||||
- `typing.List` → `list`
|
||||
- `Optional[str]` → `str | None` (Python 3.10+)
|
||||
|
||||
### Organization (I)
|
||||
- **I**: isort - import sorting and organization
|
||||
|
||||
### Performance (ARG, TCH)
|
||||
- **ARG**: Detect unused function arguments
|
||||
- **TCH**: Type checking imports (move to TYPE_CHECKING block)
|
||||
|
||||
## Common Rules
|
||||
|
||||
### E/W Series (PEP 8)
|
||||
- `E111`: Indentation is not a multiple of 4
|
||||
- `E201`: Whitespace after '('
|
||||
- `E202`: Whitespace before ')'
|
||||
- `E203`: Whitespace before ':'
|
||||
- `E302`: Expected 2 blank lines
|
||||
- `E303`: Too many blank lines
|
||||
- `W291`: Trailing whitespace
|
||||
|
||||
### F Series (PyFlakes)
|
||||
- `F401`: Unused import
|
||||
- `F811`: Redefinition of unused name
|
||||
- `F821`: Undefined name
|
||||
- `F841`: Local variable assigned but never used
|
||||
|
||||
### B Series (Bugbear)
|
||||
- `B002`: Using * in exception handling
|
||||
- `B006`: Mutable default argument
|
||||
- `B007`: Loop variable not used
|
||||
- `B008`: Function calls in default arguments
|
||||
- `B904`: Use `raise ... from ...` for exception chaining
|
||||
|
||||
### UP Series (PyUpgrade)
|
||||
- `UP006`: Use `list` instead of `typing.List`
|
||||
- `UP007`: Use `X | Y` instead of `typing.Union[X, Y]`
|
||||
- `UP032`: Use f-strings instead of `.format()`
|
||||
|
||||
## Commands Reference
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Check all files
|
||||
ruff check .
|
||||
|
||||
# Check specific file
|
||||
ruff check src/module.py
|
||||
|
||||
# Auto-fix issues
|
||||
ruff check --fix .
|
||||
|
||||
# Show fixes without applying
|
||||
ruff check --diff .
|
||||
|
||||
# Watch mode (re-run on file changes)
|
||||
ruff check --watch .
|
||||
|
||||
# Output JSON format
|
||||
ruff check --output-format=json .
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
```bash
|
||||
# Format all files
|
||||
ruff format .
|
||||
|
||||
# Format specific file
|
||||
ruff format src/module.py
|
||||
|
||||
# Check if formatting needed (CI)
|
||||
ruff format --check .
|
||||
|
||||
# Show diff without applying
|
||||
ruff format --diff .
|
||||
```
|
||||
|
||||
### Configuration Testing
|
||||
|
||||
```bash
|
||||
# Show active configuration
|
||||
ruff check --show-settings
|
||||
|
||||
# Show all available rules
|
||||
ruff rule --all
|
||||
|
||||
# Show specific rule documentation
|
||||
ruff rule F401
|
||||
```
|
||||
|
||||
## Per-File Ignores
|
||||
|
||||
Ignore specific rules for specific files:
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**/*.py" = ["ARG", "S101"] # Tests can use assert and unused args
|
||||
"__init__.py" = ["F401"] # Unused imports OK in __init__
|
||||
"scripts/**/*.py" = ["T201"] # print() OK in scripts
|
||||
"migrations/**/*.py" = ["E501"] # Long lines OK in migrations
|
||||
```
|
||||
|
||||
## VSCode Integration
|
||||
|
||||
Add to `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit",
|
||||
"source.fixAll": "explicit"
|
||||
}
|
||||
},
|
||||
"ruff.enable": true,
|
||||
"ruff.lint.enable": true,
|
||||
"ruff.format.enable": true
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
Ruff is extremely fast (10-100x faster than alternatives):
|
||||
- Written in Rust for performance
|
||||
- Parallel processing by default
|
||||
- Incremental linting in watch mode
|
||||
- Caching for repeated runs
|
||||
|
||||
## Migration from Other Tools
|
||||
|
||||
### From Black
|
||||
Ruff's formatter is compatible with Black:
|
||||
```bash
|
||||
ruff format . # Replaces: black .
|
||||
```
|
||||
|
||||
### From isort
|
||||
```bash
|
||||
ruff check --select I --fix . # Replaces: isort .
|
||||
```
|
||||
|
||||
### From flake8
|
||||
```bash
|
||||
ruff check . # Replaces: flake8 .
|
||||
```
|
||||
|
||||
### From pyupgrade
|
||||
```bash
|
||||
ruff check --select UP --fix . # Replaces: pyupgrade .
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Run formatter last**: `ruff check --fix . && ruff format .`
|
||||
2. **Use in CI**: Check both linting and formatting
|
||||
3. **Configure per-file ignores**: For tests, init files, migrations
|
||||
4. **Enable auto-fix**: Most issues can be fixed automatically
|
||||
5. **Use with pre-commit**: Run checks before commits
|
||||
6. **Review rule changes**: Stay updated with new releases
|
||||
Reference in New Issue
Block a user