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
|
||||
76
skills/python-project-setup/SKILL.md
Normal file
76
skills/python-project-setup/SKILL.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Python Project Setup Skill
|
||||
|
||||
Set up new Python projects following modern best practices with uv, ruff, pytest, and proper project structure.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- User requests to create a new Python project
|
||||
- User wants to initialize Python tooling in an existing directory
|
||||
- User mentions setting up a Python development environment
|
||||
- User asks about Python project structure or best practices
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
1. **Project Initialization**
|
||||
- Initialize projects with `uv init`
|
||||
- Set up proper directory structure
|
||||
- Configure Python version with pyenv
|
||||
|
||||
2. **Dependency Management**
|
||||
- Install and configure uv package manager
|
||||
- Add essential dependencies (pytest, ruff, mypy, pydantic)
|
||||
- Set up development vs runtime dependencies
|
||||
|
||||
3. **Configuration Files**
|
||||
- Create `pyproject.toml` with tool configurations
|
||||
- Set up `.gitignore` for Python projects
|
||||
- Configure VSCode settings for Python development
|
||||
- Create `.python-version` for version management
|
||||
|
||||
4. **Project Structure**
|
||||
- Create `src/<project_name>/` for source code
|
||||
- Set up `tests/` directory for test files
|
||||
- Create `docs/` for documentation
|
||||
- Set up `.vscode/` for editor configuration
|
||||
|
||||
## Context Files
|
||||
|
||||
This skill references the following context files in this directory:
|
||||
- `project-structure-template.md` - Standard directory layout
|
||||
- `pyproject-toml-template.md` - Configuration template
|
||||
- `vscode-settings-template.json` - Editor configuration
|
||||
- `gitignore-template.md` - Python gitignore patterns
|
||||
- `readme-template.md` - README structure
|
||||
|
||||
## Key Tools and Commands
|
||||
|
||||
```bash
|
||||
# Project initialization
|
||||
uv init <project-name>
|
||||
|
||||
# Dependency management
|
||||
uv add pytest --dev
|
||||
uv add ruff --dev
|
||||
uv add mypy --dev
|
||||
uv add pydantic
|
||||
|
||||
# Sync dependencies
|
||||
uv sync
|
||||
```
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
After using this skill, the user should have:
|
||||
- A fully initialized Python project with modern tooling
|
||||
- Proper project structure following best practices
|
||||
- All essential dependencies installed
|
||||
- Configuration files set up correctly
|
||||
- Version control initialized (git)
|
||||
- Documentation scaffolding in place
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
- Works with `python-code-quality` skill for linting setup
|
||||
- Works with `python-testing` skill for test framework configuration
|
||||
- Complements the `python-project-setup` agent for full orchestration
|
||||
209
skills/python-project-setup/gitignore-template.md
Normal file
209
skills/python-project-setup/gitignore-template.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Python .gitignore Template
|
||||
|
||||
```gitignore
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# poetry
|
||||
poetry.lock
|
||||
|
||||
# pdm
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# Ruff
|
||||
.ruff_cache/
|
||||
|
||||
# uv
|
||||
uv.lock
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Project specific
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
data/
|
||||
logs/
|
||||
tmp/
|
||||
temp/
|
||||
```
|
||||
|
||||
## Explanation of Key Patterns
|
||||
|
||||
### Python Runtime Files
|
||||
- `__pycache__/` - Compiled bytecode cache
|
||||
- `*.pyc`, `*.pyo`, `*.pyd` - Compiled Python files
|
||||
- `*.so` - Compiled C extensions
|
||||
|
||||
### Package/Build Artifacts
|
||||
- `dist/`, `build/` - Build output directories
|
||||
- `*.egg-info/` - Package metadata
|
||||
- `.eggs/` - Installed packages directory
|
||||
|
||||
### Virtual Environments
|
||||
- `.venv/`, `venv/`, `env/` - Virtual environment directories
|
||||
- Always create virtual environments, never commit them
|
||||
|
||||
### Testing & Coverage
|
||||
- `.pytest_cache/` - Pytest cache
|
||||
- `.coverage` - Coverage data files
|
||||
- `htmlcov/` - HTML coverage reports
|
||||
|
||||
### Type Checking & Linting
|
||||
- `.mypy_cache/` - Mypy cache
|
||||
- `.ruff_cache/` - Ruff cache
|
||||
- `.pytype/` - Pytype cache
|
||||
|
||||
### Documentation
|
||||
- `docs/_build/` - Sphinx build output
|
||||
- `/site` - MkDocs build output
|
||||
|
||||
### Environment Variables
|
||||
- `.env` - Environment variable files (NEVER commit these)
|
||||
- Should contain secrets, API keys, database URLs
|
||||
|
||||
### IDE Files
|
||||
- `.vscode/` - VSCode settings (some teams commit this)
|
||||
- `.idea/` - PyCharm settings
|
||||
- `.DS_Store` - macOS file system metadata
|
||||
|
||||
## Customization Tips
|
||||
|
||||
1. **Commit `.vscode/` if desired**: Remove from .gitignore to share editor settings
|
||||
2. **Project-specific data**: Add custom directories for data files, logs, etc.
|
||||
3. **Lock files**: Consider keeping `uv.lock` for reproducible builds
|
||||
4. **Documentation**: Remove `docs/_build/` if you want to commit built docs
|
||||
112
skills/python-project-setup/project-structure-template.md
Normal file
112
skills/python-project-setup/project-structure-template.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Python Project Structure Template
|
||||
|
||||
## Standard Directory Layout
|
||||
|
||||
```
|
||||
project_name/
|
||||
├── .vscode/ # VSCode editor configuration
|
||||
│ ├── settings.json # Python, ruff, mypy settings
|
||||
│ └── launch.json # Debug configurations
|
||||
├── src/ # Source code directory
|
||||
│ └── project_name/ # Main package (use underscores)
|
||||
│ ├── __init__.py # Package initialization
|
||||
│ ├── main.py # Entry point (if applicable)
|
||||
│ ├── models/ # Data models (Pydantic)
|
||||
│ ├── services/ # Business logic
|
||||
│ ├── api/ # API routes (if using FastAPI)
|
||||
│ └── utils/ # Utility functions
|
||||
├── tests/ # Test directory
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Pytest fixtures
|
||||
│ ├── test_main.py # Test files (prefix with test_)
|
||||
│ └── integration/ # Integration tests
|
||||
├── docs/ # Documentation
|
||||
│ ├── index.md
|
||||
│ └── api/ # API documentation
|
||||
├── .github/ # GitHub configuration
|
||||
│ ├── workflows/ # CI/CD workflows
|
||||
│ │ └── test.yml
|
||||
│ └── dependabot.yml # Dependency updates
|
||||
├── .gitignore # Git ignore patterns
|
||||
├── .python-version # Python version for pyenv
|
||||
├── pyproject.toml # Project metadata and dependencies
|
||||
├── README.md # Project documentation
|
||||
└── uv.lock # Locked dependencies (generated)
|
||||
```
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
- **Python packages**: Use underscores (e.g., `my_package`)
|
||||
- **Project names**: Use underscores (e.g., `my_project`)
|
||||
- **Test files**: Prefix with `test_` (e.g., `test_models.py`)
|
||||
- **Private modules**: Prefix with `_` (e.g., `_internal.py`)
|
||||
|
||||
## Directory Purpose
|
||||
|
||||
### `src/project_name/`
|
||||
Main application code organized by function:
|
||||
- `models/` - Data classes, Pydantic models, database models
|
||||
- `services/` - Business logic and service layer
|
||||
- `api/` - API endpoints and route handlers (FastAPI)
|
||||
- `utils/` - Helper functions and utilities
|
||||
- `config/` - Configuration management
|
||||
|
||||
### `tests/`
|
||||
Test files mirroring the structure of `src/`:
|
||||
- Unit tests for each module
|
||||
- Integration tests in `integration/` subdirectory
|
||||
- `conftest.py` for shared fixtures
|
||||
- Use pytest conventions
|
||||
|
||||
### `docs/`
|
||||
Project documentation:
|
||||
- User guides
|
||||
- API documentation
|
||||
- Architecture decisions
|
||||
- Setup instructions
|
||||
|
||||
## Module Organization
|
||||
|
||||
Each Python module should follow this pattern:
|
||||
|
||||
```python
|
||||
# frozen_string_literal equivalent in Python
|
||||
"""Module docstring describing purpose."""
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
import third_party_package
|
||||
|
||||
from project_name import local_module
|
||||
|
||||
|
||||
class MyClass:
|
||||
"""Class docstring."""
|
||||
|
||||
def __init__(self, param: str) -> None:
|
||||
"""Initialize with param."""
|
||||
self.param = param
|
||||
|
||||
def method(self) -> str:
|
||||
"""Method docstring."""
|
||||
return self.param
|
||||
|
||||
|
||||
def public_function() -> None:
|
||||
"""Public function docstring."""
|
||||
pass
|
||||
|
||||
|
||||
def _private_function() -> None:
|
||||
"""Private helper function."""
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use `src/` layout**: Prevents accidental imports of local code
|
||||
2. **Type hints everywhere**: Add type annotations to all functions
|
||||
3. **Docstrings**: Document all public classes and functions
|
||||
4. **Test organization**: Mirror source structure in tests
|
||||
5. **Configuration**: Use environment variables or config files, never hardcode
|
||||
6. **Dependencies**: Separate dev dependencies from runtime dependencies
|
||||
226
skills/python-project-setup/pyproject-toml-template.md
Normal file
226
skills/python-project-setup/pyproject-toml-template.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# pyproject.toml Template
|
||||
|
||||
## Complete Configuration Template
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "project-name"
|
||||
version = "0.1.0"
|
||||
description = "Project description"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "Your Name", email = "you@example.com"}
|
||||
]
|
||||
keywords = ["python", "example"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"pydantic>=2.0.0",
|
||||
# Add runtime dependencies here
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.0.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"ruff>=0.3.0",
|
||||
"mypy>=1.8.0",
|
||||
]
|
||||
api = [
|
||||
"fastapi>=0.110.0",
|
||||
"uvicorn[standard]>=0.27.0",
|
||||
]
|
||||
docs = [
|
||||
"mkdocs>=1.5.0",
|
||||
"mkdocs-material>=9.5.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/username/project-name"
|
||||
Repository = "https://github.com/username/project-name"
|
||||
Documentation = "https://project-name.readthedocs.io"
|
||||
Issues = "https://github.com/username/project-name/issues"
|
||||
|
||||
[project.scripts]
|
||||
# Define CLI entry points
|
||||
project-cli = "project_name.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/project_name"]
|
||||
|
||||
# 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
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
"ARG", # flake8-unused-arguments
|
||||
"SIM", # flake8-simplify
|
||||
"TCH", # flake8-type-checking
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line too long (handled by formatter)
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
"B904", # raise from None
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**/*.py" = [
|
||||
"ARG", # Unused function arguments allowed in tests
|
||||
"S101", # Assert allowed in tests
|
||||
]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
line-ending = "auto"
|
||||
|
||||
# Pytest configuration
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = [
|
||||
"--strict-markers",
|
||||
"--strict-config",
|
||||
"--showlocals",
|
||||
"-ra",
|
||||
]
|
||||
markers = [
|
||||
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
||||
"integration: marks tests as integration tests",
|
||||
]
|
||||
|
||||
# Coverage configuration
|
||||
[tool.coverage.run]
|
||||
source = ["src"]
|
||||
branch = true
|
||||
omit = [
|
||||
"*/tests/*",
|
||||
"*/__init__.py",
|
||||
]
|
||||
|
||||
[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",
|
||||
]
|
||||
|
||||
# Mypy configuration
|
||||
[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
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests.*"
|
||||
disallow_untyped_defs = false
|
||||
|
||||
# UV-specific configuration (if needed)
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"pytest>=8.0.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"ruff>=0.3.0",
|
||||
"mypy>=1.8.0",
|
||||
]
|
||||
```
|
||||
|
||||
## Configuration Sections Explained
|
||||
|
||||
### `[project]`
|
||||
Basic project metadata used by build tools and package indexes.
|
||||
|
||||
### `[project.optional-dependencies]`
|
||||
Groups of optional dependencies for different use cases:
|
||||
- `dev` - Development tools (testing, linting)
|
||||
- `api` - API framework dependencies (FastAPI, uvicorn)
|
||||
- `docs` - Documentation generation tools
|
||||
|
||||
### `[tool.ruff]`
|
||||
Ruff linter and formatter configuration:
|
||||
- Fast Python linter combining multiple tools
|
||||
- Replaces isort, flake8, pyupgrade, and more
|
||||
- Automatic code formatting
|
||||
|
||||
### `[tool.pytest.ini_options]`
|
||||
Pytest test framework configuration:
|
||||
- Test discovery patterns
|
||||
- Default command-line options
|
||||
- Custom test markers
|
||||
|
||||
### `[tool.coverage]`
|
||||
Code coverage settings for pytest-cov:
|
||||
- Which files to measure
|
||||
- Coverage reporting options
|
||||
- Lines to exclude from coverage
|
||||
|
||||
### `[tool.mypy]`
|
||||
Type checking configuration:
|
||||
- Strictness settings
|
||||
- Type checking rules
|
||||
- Per-directory overrides
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# Install project with dev dependencies
|
||||
uv sync --extra dev
|
||||
|
||||
# Install with API dependencies
|
||||
uv sync --extra api
|
||||
|
||||
# Install all optional dependencies
|
||||
uv sync --all-extras
|
||||
|
||||
# Run tests with coverage
|
||||
uv run pytest --cov
|
||||
|
||||
# Lint code
|
||||
uv run ruff check .
|
||||
|
||||
# Format code
|
||||
uv run ruff format .
|
||||
|
||||
# Type check
|
||||
uv run mypy src
|
||||
```
|
||||
332
skills/python-project-setup/readme-template.md
Normal file
332
skills/python-project-setup/readme-template.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# README.md Template
|
||||
|
||||
```markdown
|
||||
# Project Name
|
||||
|
||||
Brief one-line description of what this project does.
|
||||
|
||||
## Overview
|
||||
|
||||
A more detailed description of the project, its purpose, and key features.
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1: Description
|
||||
- Feature 2: Description
|
||||
- Feature 3: Description
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.11 or higher
|
||||
- uv package manager
|
||||
|
||||
## Installation
|
||||
|
||||
### Using uv (Recommended)
|
||||
|
||||
\`\`\`bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/username/project-name.git
|
||||
cd project-name
|
||||
|
||||
# Install dependencies
|
||||
uv sync
|
||||
|
||||
# Activate virtual environment (optional, uv runs commands automatically)
|
||||
source .venv/bin/activate # On Unix/macOS
|
||||
.venv\Scripts\activate # On Windows
|
||||
\`\`\`
|
||||
|
||||
### Using pip
|
||||
|
||||
\`\`\`bash
|
||||
pip install -e .
|
||||
\`\`\`
|
||||
|
||||
## Quick Start
|
||||
|
||||
\`\`\`python
|
||||
from project_name import main_function
|
||||
|
||||
# Example usage
|
||||
result = main_function()
|
||||
print(result)
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
\`\`\`bash
|
||||
# Run the main application
|
||||
uv run python -m project_name
|
||||
|
||||
# Or if you installed with pip
|
||||
project-cli --help
|
||||
\`\`\`
|
||||
|
||||
### As a Library
|
||||
|
||||
\`\`\`python
|
||||
from project_name import SomeClass
|
||||
|
||||
# Create an instance
|
||||
instance = SomeClass(param="value")
|
||||
|
||||
# Use the instance
|
||||
result = instance.method()
|
||||
\`\`\`
|
||||
|
||||
## Development
|
||||
|
||||
### Setup Development Environment
|
||||
|
||||
\`\`\`bash
|
||||
# Install with development dependencies
|
||||
uv sync --extra dev
|
||||
|
||||
# Or install all extras
|
||||
uv sync --all-extras
|
||||
\`\`\`
|
||||
|
||||
### Running Tests
|
||||
|
||||
\`\`\`bash
|
||||
# Run all tests
|
||||
uv run pytest
|
||||
|
||||
# Run with coverage
|
||||
uv run pytest --cov
|
||||
|
||||
# Run specific test file
|
||||
uv run pytest tests/test_specific.py
|
||||
|
||||
# Run with verbose output
|
||||
uv run pytest -v
|
||||
\`\`\`
|
||||
|
||||
### Code Quality
|
||||
|
||||
\`\`\`bash
|
||||
# Lint code
|
||||
uv run ruff check .
|
||||
|
||||
# Fix auto-fixable issues
|
||||
uv run ruff check --fix .
|
||||
|
||||
# Format code
|
||||
uv run ruff format .
|
||||
|
||||
# Type checking
|
||||
uv run mypy src
|
||||
\`\`\`
|
||||
|
||||
### Pre-commit Checks
|
||||
|
||||
Before committing, ensure all checks pass:
|
||||
|
||||
\`\`\`bash
|
||||
# Run all checks
|
||||
uv run ruff check . && uv run ruff format . && uv run mypy src && uv run pytest
|
||||
\`\`\`
|
||||
|
||||
## Project Structure
|
||||
|
||||
\`\`\`
|
||||
project-name/
|
||||
├── src/
|
||||
│ └── project_name/ # Main package
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # Entry point
|
||||
│ ├── models/ # Data models
|
||||
│ ├── services/ # Business logic
|
||||
│ └── utils/ # Utilities
|
||||
├── tests/ # Test files
|
||||
├── docs/ # Documentation
|
||||
├── pyproject.toml # Project configuration
|
||||
└── README.md # This file
|
||||
\`\`\`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
\`\`\`env
|
||||
API_KEY=your_api_key_here
|
||||
DATABASE_URL=postgresql://user:pass@localhost/dbname
|
||||
DEBUG=false
|
||||
\`\`\`
|
||||
|
||||
### Configuration File
|
||||
|
||||
Alternatively, create a `config.yaml`:
|
||||
|
||||
\`\`\`yaml
|
||||
api:
|
||||
key: your_api_key
|
||||
timeout: 30
|
||||
|
||||
database:
|
||||
url: postgresql://user:pass@localhost/dbname
|
||||
pool_size: 5
|
||||
\`\`\`
|
||||
|
||||
## API Documentation
|
||||
|
||||
### Main Classes
|
||||
|
||||
#### `SomeClass`
|
||||
|
||||
Description of the class.
|
||||
|
||||
\`\`\`python
|
||||
from project_name import SomeClass
|
||||
|
||||
instance = SomeClass(param="value")
|
||||
result = instance.method()
|
||||
\`\`\`
|
||||
|
||||
**Parameters:**
|
||||
- `param` (str): Description of parameter
|
||||
|
||||
**Returns:**
|
||||
- `str`: Description of return value
|
||||
|
||||
### Functions
|
||||
|
||||
#### `main_function()`
|
||||
|
||||
Description of the function.
|
||||
|
||||
\`\`\`python
|
||||
from project_name import main_function
|
||||
|
||||
result = main_function()
|
||||
\`\`\`
|
||||
|
||||
## API Endpoints (if applicable)
|
||||
|
||||
### Start the Server
|
||||
|
||||
\`\`\`bash
|
||||
uv run uvicorn project_name.api.main:app --reload
|
||||
\`\`\`
|
||||
|
||||
### Endpoints
|
||||
|
||||
#### GET `/api/items`
|
||||
|
||||
Get all items.
|
||||
|
||||
**Response:**
|
||||
\`\`\`json
|
||||
{
|
||||
"items": [
|
||||
{"id": 1, "name": "Item 1"},
|
||||
{"id": 2, "name": "Item 2"}
|
||||
]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
#### POST `/api/items`
|
||||
|
||||
Create a new item.
|
||||
|
||||
**Request Body:**
|
||||
\`\`\`json
|
||||
{
|
||||
"name": "New Item"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
**Response:**
|
||||
\`\`\`json
|
||||
{
|
||||
"id": 3,
|
||||
"name": "New Item"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please follow these steps:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (\`git checkout -b feature/amazing-feature\`)
|
||||
3. Make your changes
|
||||
4. Run tests and linting (\`uv run pytest && uv run ruff check .\`)
|
||||
5. Commit your changes (\`git commit -m 'Add amazing feature'\`)
|
||||
6. Push to the branch (\`git push origin feature/amazing-feature\`)
|
||||
7. Open a Pull Request
|
||||
|
||||
### Code Style
|
||||
|
||||
- Follow PEP 8 guidelines
|
||||
- Use type hints for all functions
|
||||
- Write docstrings for all public APIs
|
||||
- Maintain test coverage above 80%
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Authors
|
||||
|
||||
- Your Name - [@yourhandle](https://github.com/yourhandle)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Library or resource that inspired this project
|
||||
- Contributors who helped
|
||||
- Any other acknowledgments
|
||||
|
||||
## Changelog
|
||||
|
||||
### [0.1.0] - 2024-01-01
|
||||
|
||||
#### Added
|
||||
- Initial release
|
||||
- Basic functionality
|
||||
|
||||
#### Changed
|
||||
- Nothing yet
|
||||
|
||||
#### Fixed
|
||||
- Nothing yet
|
||||
|
||||
## Support
|
||||
|
||||
For support, email support@example.com or open an issue on GitHub.
|
||||
|
||||
## Links
|
||||
|
||||
- [Documentation](https://project-name.readthedocs.io)
|
||||
- [Issue Tracker](https://github.com/username/project-name/issues)
|
||||
- [Changelog](CHANGELOG.md)
|
||||
```
|
||||
|
||||
## Sections Explained
|
||||
|
||||
1. **Title & Description**: Clear project name and one-line summary
|
||||
2. **Overview**: More detailed description of purpose and features
|
||||
3. **Installation**: Step-by-step setup instructions
|
||||
4. **Quick Start**: Minimal example to get started quickly
|
||||
5. **Usage**: Detailed usage examples (CLI and library)
|
||||
6. **Development**: Instructions for contributors
|
||||
7. **Project Structure**: Overview of codebase organization
|
||||
8. **Configuration**: How to configure the application
|
||||
9. **API Documentation**: Documentation of public API
|
||||
10. **Contributing**: Guidelines for contributions
|
||||
11. **License & Authors**: Legal and attribution information
|
||||
12. **Support**: How to get help
|
||||
|
||||
## Tips for Good READMEs
|
||||
|
||||
- **Keep it concise**: Users should understand the project in 30 seconds
|
||||
- **Working examples**: All code examples should be copy-paste ready
|
||||
- **Clear installation**: Step-by-step instructions that actually work
|
||||
- **Visual aids**: Consider adding screenshots, diagrams, or GIFs
|
||||
- **Badges**: Add CI status, coverage, and version badges
|
||||
- **Update regularly**: Keep it in sync with the actual codebase
|
||||
54
skills/python-project-setup/vscode-settings-template.json
Normal file
54
skills/python-project-setup/vscode-settings-template.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.analysis.autoImportCompletions": true,
|
||||
"python.analysis.diagnosticMode": "workspace",
|
||||
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit",
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"editor.rulers": [100]
|
||||
},
|
||||
|
||||
"ruff.enable": true,
|
||||
"ruff.lint.enable": true,
|
||||
"ruff.format.enable": true,
|
||||
"ruff.organizeImports": true,
|
||||
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"tests"
|
||||
],
|
||||
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/*.pyc": true,
|
||||
"**/*.pyo": true,
|
||||
"**/.pytest_cache": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.ruff_cache": true,
|
||||
"**/*.egg-info": true
|
||||
},
|
||||
|
||||
"files.watcherExclude": {
|
||||
"**/__pycache__/**": true,
|
||||
"**/.venv/**": true,
|
||||
"**/.pytest_cache/**": true,
|
||||
"**/.mypy_cache/**": true,
|
||||
"**/.ruff_cache/**": true
|
||||
},
|
||||
|
||||
"search.exclude": {
|
||||
"**/.venv": true,
|
||||
"**/node_modules": true,
|
||||
"**/__pycache__": true,
|
||||
"**/.pytest_cache": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.ruff_cache": true
|
||||
}
|
||||
}
|
||||
111
skills/python-testing/SKILL.md
Normal file
111
skills/python-testing/SKILL.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Python Testing Skill
|
||||
|
||||
Comprehensive testing with pytest including unit tests, integration tests, fixtures, and coverage reporting.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- User requests to run tests
|
||||
- User wants to create new tests
|
||||
- User asks about test coverage
|
||||
- User mentions pytest, unit tests, or integration tests
|
||||
- Debugging test failures
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
1. **Test Execution**
|
||||
- Run all tests or specific test files
|
||||
- Run tests matching patterns
|
||||
- Run only failed tests
|
||||
- Generate test reports
|
||||
|
||||
2. **Test Coverage**
|
||||
- Measure code coverage
|
||||
- Generate HTML coverage reports
|
||||
- Identify untested code
|
||||
- Set coverage thresholds
|
||||
|
||||
3. **Test Writing**
|
||||
- Create unit tests for functions and classes
|
||||
- Write integration tests
|
||||
- Use pytest fixtures effectively
|
||||
- Implement parametrized tests
|
||||
|
||||
4. **Test Organization**
|
||||
- Structure test files properly
|
||||
- Use conftest.py for shared fixtures
|
||||
- Organize tests by feature or module
|
||||
- Mark tests for selective execution
|
||||
|
||||
## Context Files
|
||||
|
||||
This skill references the following context files in this directory:
|
||||
- `pytest-configuration.md` - Pytest setup and configuration
|
||||
- `test-patterns.md` - Common testing patterns and examples
|
||||
- `fixtures-guide.md` - Using pytest fixtures
|
||||
- `coverage-guide.md` - Code coverage best practices
|
||||
|
||||
## Key Tools and Commands
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
uv run pytest # All tests
|
||||
uv run pytest tests/test_file.py # Specific file
|
||||
uv run pytest -k "pattern" # Tests matching pattern
|
||||
uv run pytest --lf # Last failed only
|
||||
|
||||
# Coverage
|
||||
uv run pytest --cov # With coverage
|
||||
uv run pytest --cov --cov-report=html # HTML report
|
||||
uv run pytest --cov --cov-report=term-missing # Show missing lines
|
||||
|
||||
# Output control
|
||||
uv run pytest -v # Verbose
|
||||
uv run pytest -s # Show print statements
|
||||
uv run pytest -x # Stop on first failure
|
||||
```
|
||||
|
||||
## Common Test Patterns
|
||||
|
||||
### Unit Test Example
|
||||
```python
|
||||
def test_addition():
|
||||
assert add(2, 3) == 5
|
||||
assert add(-1, 1) == 0
|
||||
```
|
||||
|
||||
### Fixture Usage
|
||||
```python
|
||||
@pytest.fixture
|
||||
def sample_data():
|
||||
return {"key": "value"}
|
||||
|
||||
def test_with_fixture(sample_data):
|
||||
assert sample_data["key"] == "value"
|
||||
```
|
||||
|
||||
### Parametrized Tests
|
||||
```python
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
(2, 4),
|
||||
(3, 9),
|
||||
(4, 16),
|
||||
])
|
||||
def test_square(input, expected):
|
||||
assert square(input) == expected
|
||||
```
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
After using this skill:
|
||||
- Comprehensive test suite covering critical functionality
|
||||
- High test coverage (ideally > 80%)
|
||||
- Well-organized test files
|
||||
- Clear test failure messages
|
||||
- Fast test execution
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
- Works with `python-project-setup` for test directory structure
|
||||
- Complements `python-code-quality` for comprehensive QA
|
||||
- Used by `python-project-setup` agent for automated testing
|
||||
447
skills/python-testing/pytest-configuration.md
Normal file
447
skills/python-testing/pytest-configuration.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Pytest Configuration Guide
|
||||
|
||||
Pytest is a powerful testing framework for Python that makes it easy to write simple and scalable tests.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
uv add --dev pytest
|
||||
uv add --dev pytest-cov # For coverage
|
||||
uv add --dev pytest-mock # For mocking
|
||||
uv add --dev pytest-asyncio # For async tests
|
||||
```
|
||||
|
||||
## Basic Configuration
|
||||
|
||||
Add to `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py", "*_test.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = [
|
||||
"--strict-markers",
|
||||
"--strict-config",
|
||||
"--showlocals",
|
||||
"-ra",
|
||||
]
|
||||
markers = [
|
||||
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
||||
"integration: marks tests as integration tests",
|
||||
"unit: marks tests as unit tests",
|
||||
]
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── src/
|
||||
│ └── project_name/
|
||||
│ └── module.py
|
||||
├── tests/
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Shared fixtures
|
||||
│ ├── test_module.py # Unit tests
|
||||
│ ├── integration/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── test_api.py # Integration tests
|
||||
│ └── fixtures/
|
||||
│ └── sample_data.json
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
### Basic Test Function
|
||||
|
||||
```python
|
||||
def test_simple_addition():
|
||||
"""Test that addition works correctly."""
|
||||
result = add(2, 3)
|
||||
assert result == 5
|
||||
```
|
||||
|
||||
### Test Class
|
||||
|
||||
```python
|
||||
class TestCalculator:
|
||||
"""Tests for Calculator class."""
|
||||
|
||||
def test_addition(self):
|
||||
calc = Calculator()
|
||||
assert calc.add(2, 3) == 5
|
||||
|
||||
def test_subtraction(self):
|
||||
calc = Calculator()
|
||||
assert calc.subtract(5, 3) == 2
|
||||
```
|
||||
|
||||
### Using Assertions
|
||||
|
||||
```python
|
||||
def test_assertions():
|
||||
# Equality
|
||||
assert result == expected
|
||||
|
||||
# Boolean
|
||||
assert is_valid()
|
||||
assert not is_invalid()
|
||||
|
||||
# Membership
|
||||
assert item in collection
|
||||
assert key in dictionary
|
||||
|
||||
# Type checking
|
||||
assert isinstance(obj, MyClass)
|
||||
|
||||
# Exceptions
|
||||
with pytest.raises(ValueError):
|
||||
raise_error()
|
||||
|
||||
# Approximate equality (floats)
|
||||
assert result == pytest.approx(expected, rel=1e-5)
|
||||
```
|
||||
|
||||
## Fixtures
|
||||
|
||||
### Basic Fixture
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def sample_user():
|
||||
"""Create a sample user for testing."""
|
||||
return User(name="Alice", age=30)
|
||||
|
||||
def test_user_greeting(sample_user):
|
||||
assert sample_user.greet() == "Hello, I'm Alice"
|
||||
```
|
||||
|
||||
### Fixture Scopes
|
||||
|
||||
```python
|
||||
@pytest.fixture(scope="function") # Default, new instance per test
|
||||
def function_scope():
|
||||
return setup()
|
||||
|
||||
@pytest.fixture(scope="class") # One instance per test class
|
||||
def class_scope():
|
||||
return setup()
|
||||
|
||||
@pytest.fixture(scope="module") # One instance per module
|
||||
def module_scope():
|
||||
return setup()
|
||||
|
||||
@pytest.fixture(scope="session") # One instance per test session
|
||||
def session_scope():
|
||||
return setup()
|
||||
```
|
||||
|
||||
### Fixture Cleanup
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def resource():
|
||||
# Setup
|
||||
res = acquire_resource()
|
||||
yield res
|
||||
# Teardown
|
||||
res.cleanup()
|
||||
|
||||
# Or using context manager
|
||||
@pytest.fixture
|
||||
def database():
|
||||
with create_database() as db:
|
||||
yield db
|
||||
# Automatic cleanup
|
||||
```
|
||||
|
||||
### Fixture Dependencies
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def database():
|
||||
return Database()
|
||||
|
||||
@pytest.fixture
|
||||
def user_repository(database):
|
||||
return UserRepository(database)
|
||||
|
||||
def test_find_user(user_repository):
|
||||
user = user_repository.find(1)
|
||||
assert user is not None
|
||||
```
|
||||
|
||||
## Parametrized Tests
|
||||
|
||||
### Basic Parametrization
|
||||
|
||||
```python
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
(2, 4),
|
||||
(3, 9),
|
||||
(4, 16),
|
||||
(5, 25),
|
||||
])
|
||||
def test_square(input, expected):
|
||||
assert square(input) == expected
|
||||
```
|
||||
|
||||
### Multiple Parameters
|
||||
|
||||
```python
|
||||
@pytest.mark.parametrize("a,b,expected", [
|
||||
(1, 1, 2),
|
||||
(2, 3, 5),
|
||||
(10, -5, 5),
|
||||
])
|
||||
def test_addition(a, b, expected):
|
||||
assert add(a, b) == expected
|
||||
```
|
||||
|
||||
### Parametrizing Fixtures
|
||||
|
||||
```python
|
||||
@pytest.fixture(params=[1, 2, 3])
|
||||
def number(request):
|
||||
return request.param
|
||||
|
||||
def test_with_different_numbers(number):
|
||||
assert number > 0
|
||||
```
|
||||
|
||||
## Test Markers
|
||||
|
||||
### Built-in Markers
|
||||
|
||||
```python
|
||||
@pytest.mark.skip(reason="Not implemented yet")
|
||||
def test_future_feature():
|
||||
pass
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
|
||||
def test_new_feature():
|
||||
pass
|
||||
|
||||
@pytest.mark.xfail(reason="Known bug")
|
||||
def test_buggy_feature():
|
||||
pass
|
||||
```
|
||||
|
||||
### Custom Markers
|
||||
|
||||
```python
|
||||
# Define in pyproject.toml
|
||||
# markers = ["slow: marks tests as slow"]
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_expensive_operation():
|
||||
# Long-running test
|
||||
pass
|
||||
|
||||
# Run with: pytest -m "not slow"
|
||||
```
|
||||
|
||||
## Exception Testing
|
||||
|
||||
```python
|
||||
def test_raises_value_error():
|
||||
with pytest.raises(ValueError):
|
||||
raise ValueError("Invalid value")
|
||||
|
||||
def test_raises_with_message():
|
||||
with pytest.raises(ValueError, match="Invalid"):
|
||||
raise ValueError("Invalid value")
|
||||
|
||||
def test_exception_info():
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
raise ValueError("Invalid value")
|
||||
assert "Invalid" in str(exc_info.value)
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
### Using pytest-mock
|
||||
|
||||
```python
|
||||
def test_with_mock(mocker):
|
||||
# Mock a function
|
||||
mock = mocker.patch('module.function')
|
||||
mock.return_value = 42
|
||||
|
||||
result = call_function()
|
||||
assert result == 42
|
||||
mock.assert_called_once()
|
||||
|
||||
def test_mock_method(mocker):
|
||||
# Mock a method
|
||||
mock = mocker.patch.object(MyClass, 'method')
|
||||
mock.return_value = "mocked"
|
||||
|
||||
obj = MyClass()
|
||||
assert obj.method() == "mocked"
|
||||
```
|
||||
|
||||
### Using unittest.mock
|
||||
|
||||
```python
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
def test_with_mock():
|
||||
with patch('module.function') as mock:
|
||||
mock.return_value = 42
|
||||
result = call_function()
|
||||
assert result == 42
|
||||
```
|
||||
|
||||
## Async Tests
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_function():
|
||||
result = await async_operation()
|
||||
assert result == expected
|
||||
|
||||
@pytest.fixture
|
||||
async def async_client():
|
||||
client = AsyncClient()
|
||||
yield client
|
||||
await client.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_with_async_fixture(async_client):
|
||||
result = await async_client.get("/endpoint")
|
||||
assert result.status_code == 200
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
pytest
|
||||
|
||||
# Specific file
|
||||
pytest tests/test_module.py
|
||||
|
||||
# Specific test
|
||||
pytest tests/test_module.py::test_function
|
||||
|
||||
# Specific class
|
||||
pytest tests/test_module.py::TestClass
|
||||
|
||||
# Pattern matching
|
||||
pytest -k "test_user"
|
||||
|
||||
# Markers
|
||||
pytest -m slow # Only slow tests
|
||||
pytest -m "not slow" # Exclude slow tests
|
||||
|
||||
# Last failed
|
||||
pytest --lf
|
||||
|
||||
# Failed first
|
||||
pytest --ff
|
||||
|
||||
# Stop on first failure
|
||||
pytest -x
|
||||
|
||||
# Stop after N failures
|
||||
pytest --maxfail=3
|
||||
|
||||
# Verbose output
|
||||
pytest -v
|
||||
|
||||
# Show print statements
|
||||
pytest -s
|
||||
|
||||
# Show locals in tracebacks
|
||||
pytest --showlocals
|
||||
|
||||
# Parallel execution (requires pytest-xdist)
|
||||
pytest -n auto
|
||||
```
|
||||
|
||||
## conftest.py
|
||||
|
||||
Shared configuration and fixtures:
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def database():
|
||||
"""Create database for entire test session."""
|
||||
db = create_test_database()
|
||||
yield db
|
||||
db.drop()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_database(database):
|
||||
"""Reset database before each test."""
|
||||
database.clear()
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Pytest configuration hook."""
|
||||
config.addinivalue_line(
|
||||
"markers", "integration: mark test as integration test"
|
||||
)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **One assertion per test**: Keep tests focused
|
||||
2. **Descriptive names**: Use `test_user_creation_with_valid_data`
|
||||
3. **AAA pattern**: Arrange, Act, Assert
|
||||
4. **Use fixtures**: Avoid duplicating setup code
|
||||
5. **Test edge cases**: Not just happy paths
|
||||
6. **Fast tests**: Keep unit tests under 100ms
|
||||
7. **Independent tests**: Tests should not depend on each other
|
||||
8. **Clear assertions**: Make failures obvious
|
||||
|
||||
## Example Test File
|
||||
|
||||
```python
|
||||
"""Tests for user module."""
|
||||
import pytest
|
||||
from project_name.models import User
|
||||
|
||||
@pytest.fixture
|
||||
def valid_user_data():
|
||||
"""Valid user data for testing."""
|
||||
return {
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"age": 30
|
||||
}
|
||||
|
||||
class TestUser:
|
||||
"""Tests for User model."""
|
||||
|
||||
def test_create_user(self, valid_user_data):
|
||||
"""Test creating a user with valid data."""
|
||||
# Arrange & Act
|
||||
user = User(**valid_user_data)
|
||||
|
||||
# Assert
|
||||
assert user.name == "Alice"
|
||||
assert user.email == "alice@example.com"
|
||||
assert user.age == 30
|
||||
|
||||
def test_user_greeting(self, valid_user_data):
|
||||
"""Test user greeting message."""
|
||||
user = User(**valid_user_data)
|
||||
assert user.greet() == "Hello, I'm Alice"
|
||||
|
||||
@pytest.mark.parametrize("age", [-1, 0, 151])
|
||||
def test_invalid_age(self, valid_user_data, age):
|
||||
"""Test that invalid ages raise ValueError."""
|
||||
valid_user_data["age"] = age
|
||||
with pytest.raises(ValueError, match="Invalid age"):
|
||||
User(**valid_user_data)
|
||||
```
|
||||
Reference in New Issue
Block a user