metacoding
Version:
Guided Development Workflow for GitHub Copilot - Transform your coding experience with AI-guided standards, structured workflows, and quality practices
340 lines (261 loc) • 9.83 kB
Markdown
---
description: 'Python-specific coding standards and best practices'
applyTo: '**/*.py'
language: 'python'
---
# Python Coding Standards and Best Practices
## Language and Framework Preferences
- **Primary Language:** Python 3.9+ for all Python projects
- **Code Style:** Follow PEP 8 with Black formatter for consistent formatting
- **Type Hints:** Use type hints for all function signatures and complex variables
- **Target Compatibility:** Python 3.9+ (use modern Python features appropriately)
## Code Quality Guidelines
- **Readability:** Follow "The Zen of Python" - explicit is better than implicit
- **Functions:** Keep functions focused, ideally under 30 lines for better readability
- **Magic Numbers:** Use named constants or configuration files instead of magic numbers
- **Error Handling:** Use specific exception types, avoid bare `except:` clauses
- **Memory Management:** Be mindful of memory usage, use generators for large datasets
- **Async Patterns:** Use `asyncio` for I/O-bound operations, avoid blocking operations
## Naming Conventions
- **Files:** Use snake_case for file names (e.g., `user_service.py`)
- **Classes:** PascalCase (e.g., `UserService`, `DatabaseConnection`)
- **Functions/Methods:** snake_case (e.g., `get_user_by_id`, `validate_input`)
- **Variables:** snake_case (e.g., `user_id`, `is_valid`)
- **Constants:** SCREAMING_SNAKE_CASE (e.g., `MAX_RETRY_ATTEMPTS`, `DEFAULT_TIMEOUT`)
- **Private Attributes:** Single underscore prefix (e.g., `_internal_method`)
- **Name Mangling:** Double underscore prefix only when necessary (e.g., `__private_attr`)
## Code Organization
- **Single Responsibility:** One class per file for complex classes, related utilities can be grouped
- **Imports:** Follow PEP 8 import order (standard library, third-party, local imports)
- **Module Structure:** Use `__init__.py` files for package initialization and clean imports
- **Entry Points:** Use `if __name__ == "__main__":` for script entry points
## Python-Specific Best Practices
### Type Hints and Documentation
```python
from typing import List, Dict, Optional, Union
from dataclasses import dataclass
def process_user_data(
users: List[Dict[str, Union[str, int]]],
active_only: bool = True
) -> List[str]:
"""Process user data and return list of usernames.
Args:
users: List of user dictionaries containing user information
active_only: If True, only include active users
Returns:
List of usernames matching the criteria
Raises:
ValueError: If user data format is invalid
"""
pass
```
### Error Handling Patterns
```python
# Good: Specific exception handling
try:
user = get_user_by_id(user_id)
except UserNotFoundError as e:
logger.warning(f"User {user_id} not found: {e}")
return None
except DatabaseConnectionError as e:
logger.error(f"Database connection failed: {e}")
raise
# Good: Use custom exceptions
class ValidationError(Exception):
"""Raised when data validation fails."""
pass
class UserNotFoundError(Exception):
"""Raised when requested user cannot be found."""
pass
```
### Resource Management
```python
# Good: Use context managers
with open('data.json', 'r') as file:
data = json.load(file)
# Good: Create custom context managers when needed
from contextlib import contextmanager
def database_transaction():
conn = get_connection()
trans = conn.begin()
try:
yield conn
trans.commit()
except Exception:
trans.rollback()
raise
finally:
conn.close()
```
### Performance Considerations
```python
# Good: Use generators for large datasets
def process_large_file(filename: str):
with open(filename, 'r') as file:
for line in file:
yield process_line(line)
# Good: Use list comprehensions for simple transformations
active_users = [user for user in users if user.is_active]
# Good: Use appropriate data structures
from collections import defaultdict, deque
user_groups = defaultdict(list)
```
## Testing Standards
### Test Framework Preferences
- **Primary Framework:** pytest for all testing
- **Fixtures:** Use pytest fixtures for test data and setup
- **Parametrized Tests:** Use `pytest.mark.parametrize` for multiple test cases
- **Mocking:** Use `unittest.mock` or `pytest-mock` for mocking dependencies
### Test File Organization
```python
# tests/test_user_service.py
import pytest
from unittest.mock import Mock, patch
from src.services.user_service import UserService
from src.exceptions import UserNotFoundError
class TestUserService:
def user_service(self):
return UserService(db_connection=Mock())
def test_user_exists(self, user_service, user_id, expected):
# Test implementation
pass
def test_get_user_not_found_raises_exception(self, user_service):
with pytest.raises(UserNotFoundError):
user_service.get_user_by_id(999)
```
## Dependency Management
### Package Management
- **Primary Tool:** Poetry for dependency management and packaging
- **Requirements:** Maintain both `pyproject.toml` and `requirements.txt`
- **Development Dependencies:** Separate dev dependencies (testing, linting, formatting)
- **Version Pinning:** Pin exact versions for applications, use ranges for libraries
### Virtual Environment Management
```bash
# Create and activate virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Or use Poetry
poetry install
poetry shell
```
## Code Quality Tools
### Linting and Formatting
- **Black:** Code formatting with line length 88
- **isort:** Import sorting and organization
- **flake8:** Linting and style checking
- **mypy:** Static type checking
- **pylint:** Additional code analysis
### Configuration Examples
```toml
# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py39']
[tool.isort]
profile = "black"
multi_line_output = 3
[tool.mypy]
python_version = "3.9"
strict = true
ignore_missing_imports = true
```
## Documentation Standards
### Docstring Format
Use Google-style docstrings:
```python
def calculate_user_score(
user_data: Dict[str, Any],
weights: Optional[Dict[str, float]] = None
) -> float:
"""Calculate a user's composite score based on various metrics.
This function computes a weighted score based on user activity,
engagement, and other factors.
Args:
user_data: Dictionary containing user metrics and information
weights: Optional custom weights for score calculation.
Defaults to standard weights if not provided.
Returns:
Calculated score as a float between 0.0 and 100.0
Raises:
ValueError: If user_data is missing required fields
TypeError: If weights contain non-numeric values
Example:
>>> user = {"activity": 85, "engagement": 92}
>>> calculate_user_score(user)
88.5
"""
pass
```
## Security Considerations
### Input Validation
```python
import re
from typing import Any
def validate_email(email: str) -> bool:
"""Validate email format using regex."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
def sanitize_user_input(user_input: str) -> str:
"""Sanitize user input to prevent injection attacks."""
# Remove potentially dangerous characters
safe_input = re.sub(r'[<>"\';]', '', user_input)
return safe_input.strip()
```
### Environment Configuration
```python
import os
from dataclasses import dataclass
class Config:
"""Application configuration from environment variables."""
database_url: str = os.getenv('DATABASE_URL', 'sqlite:///default.db')
secret_key: str = os.getenv('SECRET_KEY', 'dev-key-change-in-production')
debug: bool = os.getenv('DEBUG', 'False').lower() == 'true'
def __post_init__(self):
if self.secret_key == 'dev-key-change-in-production' and not self.debug:
raise ValueError("SECRET_KEY must be set in production")
```
## Common Anti-Patterns to Avoid
- **Mutable Default Arguments:** Use `None` and check inside function
- **Broad Exception Catching:** Avoid bare `except:` clauses
- **Global Variables:** Minimize global state, use dependency injection
- **String Concatenation in Loops:** Use `join()` for multiple strings
- **Not Using Context Managers:** Always use `with` for file operations
- **Ignoring PEP 8:** Follow Python style guidelines consistently
- **Missing Type Hints:** Add type hints for better code documentation
- **Circular Imports:** Structure modules to avoid circular dependencies
## Performance Optimization
### Memory Efficiency
```python
# Use generators for large datasets
def read_large_file(filename: str):
with open(filename, 'r') as file:
for line in file:
yield line.strip()
# Use __slots__ for classes with many instances
class Point:
__slots__ = ['x', 'y']
def __init__(self, x: float, y: float):
self.x = x
self.y = y
```
### Concurrency Patterns
```python
import asyncio
from concurrent.futures import ThreadPoolExecutor
async def process_data_async(data_list: List[str]) -> List[str]:
"""Process data asynchronously for I/O bound operations."""
tasks = [process_item_async(item) for item in data_list]
return await asyncio.gather(*tasks)
def process_cpu_intensive_data(data_list: List[str]) -> List[str]:
"""Use thread pool for CPU-intensive operations."""
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(cpu_intensive_process, data_list))
return results
```