11 KiB
11 KiB
name, description
| name | description |
|---|---|
| docstring-format | Automatically applies when writing function docstrings. Uses Google-style format with Args, Returns, Raises, Examples, and Security Note sections for proper documentation. |
Docstring Format Enforcer
Use Google-style docstrings with proper sections for all functions and classes.
✅ Standard Function Docstring
def calculate_total(items: List[Item], tax_rate: float, discount: Optional[float] = None) -> Decimal:
"""
Calculate total price including tax and optional discount.
Computes the subtotal from items, applies discount if provided,
then adds tax to get final total.
Args:
items: List of items with price and quantity
tax_rate: Tax rate as decimal (e.g., 0.08 for 8%)
discount: Optional discount as decimal (e.g., 0.10 for 10% off)
Returns:
Total price as Decimal with 2 decimal places
Raises:
ValueError: If tax_rate is negative or > 1
ValueError: If discount is negative or > 1
Example:
>>> items = [Item(price=10.00, quantity=2)]
>>> calculate_total(items, tax_rate=0.08)
Decimal('21.60')
"""
if tax_rate < 0 or tax_rate > 1:
raise ValueError("tax_rate must be between 0 and 1")
subtotal = sum(item.price * item.quantity for item in items)
if discount:
if discount < 0 or discount > 1:
raise ValueError("discount must be between 0 and 1")
subtotal = subtotal * (1 - discount)
total = subtotal * (1 + tax_rate)
return round(total, 2)
✅ Async Function with Security Note
async def fetch_user_payment_methods(user_id: str, include_expired: bool = False) -> List[PaymentMethod]:
"""
Fetch payment methods for a user.
Retrieves all payment methods from database, optionally filtering
out expired cards. Payment tokens are included for transaction use.
Args:
user_id: User's unique identifier (MongoDB ObjectId)
include_expired: Whether to include expired payment methods
Returns:
List of PaymentMethod objects containing:
- token: Payment token for transactions (handle securely)
- last_four: Last 4 digits of card
- expiry: Expiration date (MM/YY format)
- brand: Card brand (visa, mastercard, etc.)
Raises:
UserNotFoundError: If user_id doesn't exist
DatabaseError: If database connection fails
Security Note:
Returns payment tokens that can be used for transactions.
- Never log tokens in full
- Always use HTTPS for transmission
- Tokens expire after 1 hour of inactivity
Example:
>>> methods = await fetch_user_payment_methods("user_123")
>>> for method in methods:
... print(f"Card ending in {method.last_four}")
"""
user = await db.users.find_one({"_id": user_id})
if not user:
raise UserNotFoundError(f"User {user_id} not found")
methods = await db.payment_methods.find({"user_id": user_id}).to_list()
if not include_expired:
methods = [m for m in methods if not m.is_expired()]
return methods
✅ Class Docstring
class UserRepository:
"""
Repository for user data access.
Provides CRUD operations for user entities with caching
and automatic cache invalidation on updates.
Attributes:
db: Database connection
cache: Redis cache instance
cache_ttl: Cache time-to-live in seconds (default: 3600)
Example:
>>> repo = UserRepository(db_conn, redis_client)
>>> user = await repo.get_by_id("user_123")
>>> await repo.update(user_id, {"name": "New Name"})
"""
def __init__(self, db: Database, cache: Redis, cache_ttl: int = 3600):
"""
Initialize repository with database and cache.
Args:
db: Database connection instance
cache: Redis cache instance
cache_ttl: Cache time-to-live in seconds
"""
self.db = db
self.cache = cache
self.cache_ttl = cache_ttl
✅ Property Docstring
class User:
"""User model."""
@property
def full_name(self) -> str:
"""
Get user's full name.
Combines first and last name with a space. Returns empty
string if both names are missing.
Returns:
Full name as string, or empty string if no names set
"""
if not self.first_name and not self.last_name:
return ""
return f"{self.first_name} {self.last_name}".strip()
@full_name.setter
def full_name(self, value: str) -> None:
"""
Set user's full name.
Splits on first space to set first_name and last_name.
If no space, sets only first_name.
Args:
value: Full name to parse and set
Raises:
ValueError: If value is empty or only whitespace
"""
if not value or not value.strip():
raise ValueError("Name cannot be empty")
parts = value.strip().split(" ", 1)
self.first_name = parts[0]
self.last_name = parts[1] if len(parts) > 1 else ""
✅ Tool/API Function Docstring
@tool
async def search_products(
query: str,
category: Optional[str] = None,
max_results: int = 10
) -> str:
"""
Search for products in catalog.
Performs full-text search across product names and descriptions.
Results are ranked by relevance and limited to max_results.
Use this when customers ask to:
- Find products by name or description
- Search within a specific category
- Browse available products
Args:
query: Search query string (e.g., "wireless headphones")
category: Optional category filter (e.g., "electronics")
max_results: Maximum results to return (1-100, default: 10)
Returns:
JSON string containing:
- products: List of matching products
- total: Total number of matches
- query: Original search query
- request_id: Request identifier for debugging
Example Response:
{
"products": [
{
"id": "prod_123",
"name": "Wireless Headphones",
"price": 99.99,
"in_stock": true
}
],
"total": 1,
"query": "wireless headphones",
"request_id": "req_abc123"
}
Security Note:
Logs are PII-redacted. User ID is logged but not included
in response to maintain privacy.
"""
# Implementation
Required Sections
Always include:
- ✅ Brief description (one-line summary)
- ✅ Extended description (what it does, how it works)
- ✅
Args:section (if has parameters) - ✅
Returns:section (if returns value)
Include when applicable:
- ✅
Raises:section (if raises exceptions) - ✅
Example:orExample Response:section - ✅
Security Note:(if handles PII, payment data, auth) - ✅
Note:orWarning:for important caveats - ✅
Attributes:(for classes) - ✅ Use cases (for tools: "Use this when...")
Args Section Format
def function(
required_param: str,
optional_param: Optional[int] = None,
flag: bool = False
) -> dict:
"""
Function description.
Args:
required_param: Description of required parameter.
Can span multiple lines with 4-space indent.
optional_param: Description of optional parameter.
Default: None
flag: Whether to enable feature. Default: False
Returns:
Dictionary containing results
"""
Returns Section Format
def get_user_stats(user_id: str) -> dict:
"""
Get user statistics.
Returns:
Dictionary containing:
- total_orders: Total number of orders (int)
- total_spent: Total amount spent (Decimal)
- last_order_date: Date of last order (datetime)
- loyalty_tier: Current loyalty tier (str)
"""
def process_payment(amount: Decimal) -> Tuple[bool, Optional[str]]:
"""
Process payment transaction.
Returns:
Tuple of (success, error_message) where:
- success: True if payment succeeded, False otherwise
- error_message: Error description if failed, None if succeeded
"""
Raises Section Format
def divide(a: float, b: float) -> float:
"""
Divide two numbers.
Args:
a: Numerator
b: Denominator
Returns:
Result of division
Raises:
ValueError: If b is zero
TypeError: If a or b are not numeric
"""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Security Note Guidelines
Add Security Note when function:
- Handles payment tokens/cards
- Logs or processes PII data
- Accesses customer data
- Performs financial transactions
- Requires authentication/authorization
- Handles secrets or API keys
Security Note should mention:
"""
Security Note:
Handles customer payment data (PCI-DSS Level 1).
- All PII is redacted in logs
- Payment tokens expire after 1 hour
- Requires user authentication
- Never log full card numbers
- Always use HTTPS for transmission
"""
❌ Anti-Patterns
# ❌ No docstring
def calculate_total(items, tax):
return sum(items) * (1 + tax)
# ❌ Minimal/unhelpful docstring
def calculate_total(items, tax):
"""Calculate total."""
return sum(items) * (1 + tax)
# ❌ Wrong format (not Google-style)
def calculate_total(items, tax):
"""
Calculate total.
:param items: The items
:param tax: The tax
:return: The total
"""
# ❌ No type information
def calculate_total(items, tax):
"""
Calculate total.
Args:
items: List of items
tax: Tax rate
Returns:
Total
"""
# Types should be in signature AND described in docstring!
# ❌ Vague descriptions
def process(data):
"""
Process data.
Args:
data: The data
Returns:
The result
"""
# Not helpful! What kind of data? What processing? What result?
Best Practices Checklist
- ✅ Start with brief one-line summary
- ✅ Add detailed description for complex functions
- ✅ Document all parameters with clear descriptions
- ✅ Specify parameter types (should match type hints)
- ✅ Document return value structure and type
- ✅ List all exceptions that can be raised
- ✅ Add examples for non-obvious usage
- ✅ Include Security Note for sensitive operations
- ✅ Use complete sentences with proper punctuation
- ✅ Be specific about formats (ISO dates, decimals, etc.)
- ✅ Mention side effects (logs, DB writes, API calls)
- ✅ Document default values for optional parameters
Auto-Apply
When writing functions:
- Start with brief one-line description
- Add extended description if needed
- Add
Args:section with all parameters - Add
Returns:section describing output - Add
Raises:if throws exceptions - Add
Security Note:if handling sensitive data - Add
Example:for complex usage - Use complete sentences
- Be specific about data types and formats
Related Skills
- pydantic-models - Document model fields
- structured-errors - Document error responses
- tool-design-pattern - Document tool usage
- pytest-patterns - Write test docstrings