mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 17:47:16 +00:00
fix(conductor): move plugin to plugins/ directory for proper discovery
Conductor plugin was at root level instead of plugins/ directory, causing slash commands to not be recognized by Claude Code.
This commit is contained in:
566
plugins/conductor/templates/code_styleguides/python.md
Normal file
566
plugins/conductor/templates/code_styleguides/python.md
Normal file
@@ -0,0 +1,566 @@
|
||||
# Python Style Guide
|
||||
|
||||
Python conventions following PEP 8 and modern best practices.
|
||||
|
||||
## PEP 8 Fundamentals
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
```python
|
||||
# Variables and functions: snake_case
|
||||
user_name = "John"
|
||||
def calculate_total(items):
|
||||
pass
|
||||
|
||||
# Constants: SCREAMING_SNAKE_CASE
|
||||
MAX_CONNECTIONS = 100
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
# Classes: PascalCase
|
||||
class UserAccount:
|
||||
pass
|
||||
|
||||
# Private: single underscore prefix
|
||||
class User:
|
||||
def __init__(self):
|
||||
self._internal_state = {}
|
||||
|
||||
# Name mangling: double underscore prefix
|
||||
class Base:
|
||||
def __init__(self):
|
||||
self.__private = "truly private"
|
||||
|
||||
# Module-level "private": single underscore
|
||||
_module_cache = {}
|
||||
```
|
||||
|
||||
### Indentation and Line Length
|
||||
|
||||
```python
|
||||
# 4 spaces per indentation level
|
||||
def function():
|
||||
if condition:
|
||||
do_something()
|
||||
|
||||
# Line length: 88 characters (Black) or 79 (PEP 8)
|
||||
# Break long lines appropriately
|
||||
result = some_function(
|
||||
argument_one,
|
||||
argument_two,
|
||||
argument_three,
|
||||
)
|
||||
|
||||
# Implicit line continuation in brackets
|
||||
users = [
|
||||
"alice",
|
||||
"bob",
|
||||
"charlie",
|
||||
]
|
||||
```
|
||||
|
||||
### Imports
|
||||
|
||||
```python
|
||||
# Standard library
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
|
||||
# Third-party
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Local application
|
||||
from myapp.models import User
|
||||
from myapp.utils import format_date
|
||||
|
||||
# Avoid wildcard imports
|
||||
# Bad: from module import *
|
||||
# Good: from module import specific_item
|
||||
```
|
||||
|
||||
## Type Hints
|
||||
|
||||
### Basic Type Annotations
|
||||
|
||||
```python
|
||||
from typing import Optional, List, Dict, Tuple, Union, Any
|
||||
|
||||
# Variables
|
||||
name: str = "John"
|
||||
age: int = 30
|
||||
active: bool = True
|
||||
scores: List[int] = [90, 85, 92]
|
||||
|
||||
# Functions
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
def find_user(user_id: int) -> Optional[User]:
|
||||
"""Returns User or None if not found."""
|
||||
pass
|
||||
|
||||
def process_items(items: List[str]) -> Dict[str, int]:
|
||||
"""Returns count of each item."""
|
||||
pass
|
||||
```
|
||||
|
||||
### Advanced Type Hints
|
||||
|
||||
```python
|
||||
from typing import (
|
||||
TypeVar, Generic, Protocol, Callable,
|
||||
Literal, TypedDict, Final
|
||||
)
|
||||
|
||||
# TypeVar for generics
|
||||
T = TypeVar('T')
|
||||
def first(items: List[T]) -> Optional[T]:
|
||||
return items[0] if items else None
|
||||
|
||||
# Protocol for structural typing
|
||||
class Renderable(Protocol):
|
||||
def render(self) -> str: ...
|
||||
|
||||
def display(obj: Renderable) -> None:
|
||||
print(obj.render())
|
||||
|
||||
# Literal for specific values
|
||||
Status = Literal["pending", "active", "completed"]
|
||||
|
||||
def set_status(status: Status) -> None:
|
||||
pass
|
||||
|
||||
# TypedDict for dictionary shapes
|
||||
class UserDict(TypedDict):
|
||||
id: int
|
||||
name: str
|
||||
email: Optional[str]
|
||||
|
||||
# Final for constants
|
||||
MAX_SIZE: Final = 100
|
||||
```
|
||||
|
||||
### Type Hints in Classes
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Self
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
id: int
|
||||
name: str
|
||||
email: str
|
||||
active: bool = True
|
||||
|
||||
# Class variable
|
||||
_instances: ClassVar[Dict[int, 'User']] = {}
|
||||
|
||||
def deactivate(self) -> Self:
|
||||
self.active = False
|
||||
return self
|
||||
|
||||
class Builder:
|
||||
def __init__(self) -> None:
|
||||
self._value: str = ""
|
||||
|
||||
def append(self, text: str) -> Self:
|
||||
self._value += text
|
||||
return self
|
||||
```
|
||||
|
||||
## Docstrings
|
||||
|
||||
### Function Docstrings
|
||||
|
||||
```python
|
||||
def calculate_discount(
|
||||
price: float,
|
||||
discount_percent: float,
|
||||
min_price: float = 0.0
|
||||
) -> float:
|
||||
"""Calculate the discounted price.
|
||||
|
||||
Args:
|
||||
price: Original price of the item.
|
||||
discount_percent: Discount percentage (0-100).
|
||||
min_price: Minimum price floor. Defaults to 0.0.
|
||||
|
||||
Returns:
|
||||
The discounted price, not less than min_price.
|
||||
|
||||
Raises:
|
||||
ValueError: If discount_percent is not between 0 and 100.
|
||||
|
||||
Example:
|
||||
>>> calculate_discount(100.0, 20.0)
|
||||
80.0
|
||||
"""
|
||||
if not 0 <= discount_percent <= 100:
|
||||
raise ValueError("Discount must be between 0 and 100")
|
||||
|
||||
discounted = price * (1 - discount_percent / 100)
|
||||
return max(discounted, min_price)
|
||||
```
|
||||
|
||||
### Class Docstrings
|
||||
|
||||
```python
|
||||
class UserService:
|
||||
"""Service for managing user operations.
|
||||
|
||||
This service handles user CRUD operations and authentication.
|
||||
It requires a database connection and optional cache.
|
||||
|
||||
Attributes:
|
||||
db: Database connection instance.
|
||||
cache: Optional cache for user lookups.
|
||||
|
||||
Example:
|
||||
>>> service = UserService(db_connection)
|
||||
>>> user = service.get_user(123)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: DatabaseConnection,
|
||||
cache: Optional[Cache] = None
|
||||
) -> None:
|
||||
"""Initialize the UserService.
|
||||
|
||||
Args:
|
||||
db: Active database connection.
|
||||
cache: Optional cache instance for performance.
|
||||
"""
|
||||
self.db = db
|
||||
self.cache = cache
|
||||
```
|
||||
|
||||
## Virtual Environments
|
||||
|
||||
### Setup Commands
|
||||
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python -m venv .venv
|
||||
|
||||
# Activate (Unix/macOS)
|
||||
source .venv/bin/activate
|
||||
|
||||
# Activate (Windows)
|
||||
.venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Freeze dependencies
|
||||
pip freeze > requirements.txt
|
||||
```
|
||||
|
||||
### Modern Tools
|
||||
|
||||
```bash
|
||||
# Using uv (recommended)
|
||||
uv venv
|
||||
uv pip install -r requirements.txt
|
||||
|
||||
# Using poetry
|
||||
poetry init
|
||||
poetry add requests
|
||||
poetry install
|
||||
|
||||
# Using pipenv
|
||||
pipenv install
|
||||
pipenv install requests
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── .venv/ # Virtual environment (gitignored)
|
||||
├── src/
|
||||
│ └── myapp/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── utils.py
|
||||
├── tests/
|
||||
│ ├── __init__.py
|
||||
│ └── test_main.py
|
||||
├── pyproject.toml # Modern project config
|
||||
├── requirements.txt # Pinned dependencies
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### pytest Basics
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from myapp.calculator import add, divide
|
||||
|
||||
def test_add_positive_numbers():
|
||||
assert add(2, 3) == 5
|
||||
|
||||
def test_add_negative_numbers():
|
||||
assert add(-1, -1) == -2
|
||||
|
||||
def test_divide_by_zero_raises():
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
divide(10, 0)
|
||||
|
||||
# Parametrized tests
|
||||
@pytest.mark.parametrize("a,b,expected", [
|
||||
(1, 1, 2),
|
||||
(0, 0, 0),
|
||||
(-1, 1, 0),
|
||||
])
|
||||
def test_add_parametrized(a, b, expected):
|
||||
assert add(a, b) == expected
|
||||
```
|
||||
|
||||
### Fixtures
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from myapp.database import Database
|
||||
from myapp.models import User
|
||||
|
||||
@pytest.fixture
|
||||
def db():
|
||||
"""Provide a clean database for each test."""
|
||||
database = Database(":memory:")
|
||||
database.create_tables()
|
||||
yield database
|
||||
database.close()
|
||||
|
||||
@pytest.fixture
|
||||
def sample_user(db):
|
||||
"""Create a sample user in the database."""
|
||||
user = User(name="Test User", email="test@example.com")
|
||||
db.save(user)
|
||||
return user
|
||||
|
||||
def test_user_creation(db, sample_user):
|
||||
found = db.find_user(sample_user.id)
|
||||
assert found.name == "Test User"
|
||||
```
|
||||
|
||||
### Mocking
|
||||
|
||||
```python
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import pytest
|
||||
|
||||
def test_api_client_with_mock():
|
||||
# Create mock
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {"id": 1, "name": "Test"}
|
||||
mock_response.status_code = 200
|
||||
|
||||
with patch('requests.get', return_value=mock_response) as mock_get:
|
||||
result = fetch_user(1)
|
||||
|
||||
mock_get.assert_called_once_with('/users/1')
|
||||
assert result['name'] == "Test"
|
||||
|
||||
@patch('myapp.service.external_api')
|
||||
def test_with_patch_decorator(mock_api):
|
||||
mock_api.get_data.return_value = {"status": "ok"}
|
||||
result = process_data()
|
||||
assert result["status"] == "ok"
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exception Patterns
|
||||
|
||||
```python
|
||||
# Define custom exceptions
|
||||
class AppError(Exception):
|
||||
"""Base exception for application errors."""
|
||||
pass
|
||||
|
||||
class ValidationError(AppError):
|
||||
"""Raised when validation fails."""
|
||||
def __init__(self, field: str, message: str):
|
||||
self.field = field
|
||||
self.message = message
|
||||
super().__init__(f"{field}: {message}")
|
||||
|
||||
class NotFoundError(AppError):
|
||||
"""Raised when a resource is not found."""
|
||||
def __init__(self, resource: str, identifier: Any):
|
||||
self.resource = resource
|
||||
self.identifier = identifier
|
||||
super().__init__(f"{resource} '{identifier}' not found")
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
```python
|
||||
def get_user(user_id: int) -> User:
|
||||
try:
|
||||
user = db.find_user(user_id)
|
||||
if user is None:
|
||||
raise NotFoundError("User", user_id)
|
||||
return user
|
||||
except DatabaseError as e:
|
||||
logger.error(f"Database error: {e}")
|
||||
raise AppError("Unable to fetch user") from e
|
||||
|
||||
# Context managers for cleanup
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def database_transaction(db):
|
||||
try:
|
||||
yield db
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Dataclasses
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
id: int
|
||||
name: str
|
||||
email: str
|
||||
active: bool = True
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
tags: List[str] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
self.email = self.email.lower()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Point:
|
||||
"""Immutable point."""
|
||||
x: float
|
||||
y: float
|
||||
|
||||
def distance_to(self, other: 'Point') -> float:
|
||||
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
|
||||
```
|
||||
|
||||
### Context Managers
|
||||
|
||||
```python
|
||||
from contextlib import contextmanager
|
||||
from typing import Generator
|
||||
|
||||
@contextmanager
|
||||
def timer(name: str) -> Generator[None, None, None]:
|
||||
"""Time a block of code."""
|
||||
import time
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
elapsed = time.perf_counter() - start
|
||||
print(f"{name}: {elapsed:.3f}s")
|
||||
|
||||
# Usage
|
||||
with timer("data processing"):
|
||||
process_large_dataset()
|
||||
|
||||
# Class-based context manager
|
||||
class DatabaseConnection:
|
||||
def __init__(self, connection_string: str):
|
||||
self.connection_string = connection_string
|
||||
self.connection = None
|
||||
|
||||
def __enter__(self):
|
||||
self.connection = connect(self.connection_string)
|
||||
return self.connection
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
return False # Don't suppress exceptions
|
||||
```
|
||||
|
||||
### Decorators
|
||||
|
||||
```python
|
||||
from functools import wraps
|
||||
from typing import Callable, TypeVar, ParamSpec
|
||||
import time
|
||||
|
||||
P = ParamSpec('P')
|
||||
R = TypeVar('R')
|
||||
|
||||
def retry(max_attempts: int = 3, delay: float = 1.0):
|
||||
"""Retry decorator with exponential backoff."""
|
||||
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(func)
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
last_exception = None
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(delay * (2 ** attempt))
|
||||
raise last_exception
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@retry(max_attempts=3, delay=0.5)
|
||||
def fetch_data(url: str) -> dict:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
## Code Quality Tools
|
||||
|
||||
### Ruff Configuration
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # Pyflakes
|
||||
"I", # isort
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
ignore = ["E501"] # Line too long (handled by formatter)
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["myapp"]
|
||||
```
|
||||
|
||||
### Type Checking with mypy
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
strict = true
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
ignore_missing_imports = true
|
||||
```
|
||||
Reference in New Issue
Block a user