Best Practices
Test Source: Examples in this guide are from test_complete.py
This guide provides production-ready patterns and best practices for using SmartSwitch effectively.
Design Principles
1. Use Descriptive Names
Make handler names self-documenting:
# ✅ Good: Clear intent
@sw
def handle_payment_success(transaction):
...
@sw
def handle_payment_failure(transaction):
...
# ❌ Bad: Unclear purpose
@sw
def handler1(transaction):
...
@sw
def handler2(transaction):
...
2. Use Prefix Conventions
When you have many related handlers, use a consistent prefix and the prefix parameter:
# ✅ Good: Consistent prefix with auto-stripping
storage = Switcher(
name='storage',
description='Storage backend dispatcher',
prefix='backend_'
)
@storage
def backend_s3(data):
...
@storage
def backend_gcs(data):
...
@storage
def backend_local(data):
...
# Access by short names
storage('s3')(data)
storage('gcs')(data)
# ❌ Bad: Inconsistent or no prefix
storage = Switcher()
@storage
def s3_handler(data):
...
@storage
def handle_gcs(data):
...
@storage
def local(data):
...
Performance Best Practices
When to Use SmartSwitch
SmartSwitch is optimized for functions that do real work (>1ms):
# ✅ Good: I/O-bound operation
@sw
def fetch_from_api(url):
response = requests.get(url) # Milliseconds
return response.json()
# ✅ Good: Business logic
@sw
def process_large_transaction(amount):
# Complex validation, database queries, etc.
...
# ❌ Bad: Ultra-fast inner loop
for i in range(1_000_000):
result = sw('handler')(x=i) # Overhead dominates
Overhead: ~1-2 microseconds per dispatch
Use for:
✅ API routing
✅ Event handling
✅ Data validation
✅ Business logic dispatch
Avoid for:
❌ Nanosecond-level operations
❌ Inner loops with millions of iterations
Use Direct Named Access for Hot Paths
For performance-critical code, retrieve and cache handlers:
# ✅ Good: Direct access (minimal overhead)
handler = sw('process_payment')
result = handler(transaction)
# Also good: Direct call
result = sw('process_payment')(transaction)
Use case: When you know the handler name at runtime.
Cache Switchers
Create Switchers once and reuse them:
# ✅ Good: Module-level switcher
api = Switcher(prefix='handle_')
@api
def handle_get_users():
...
# ❌ Bad: Creating switcher per request
def handle_request(method, path):
api = Switcher() # Unnecessary overhead
@api
def handler():
...
Error Handling Best Practices
Validate Inputs in Handlers
Always validate inputs within your handlers:
# ✅ Good: Comprehensive validation in handler
@sw
def process_payment(amount):
if not isinstance(amount, (int, float)):
raise TypeError("Amount must be a number")
if amount <= 0:
raise ValueError("Amount must be positive")
if amount > 1_000_000:
raise ValueError("Amount too large")
# Process payment
...
# ❌ Bad: No validation in handler
@sw
def process_payment(amount):
# Assumes amount is valid
charge_card(amount) # What if amount is negative or wrong type?
Handle Missing Handlers Gracefully
When using named dispatch with user input:
# ✅ Good: Defensive check
def execute_command(command_name, *args):
if command_name in commands._handlers:
return commands(command_name)(*args)
else:
return {"error": f"Unknown command: {command_name}"}
# ❌ Bad: Unhandled error
def execute_command(command_name, *args):
return commands(command_name)(*args) # May fail
Provide Helpful Error Messages
Use custom error messages in default handlers:
# ✅ Good: Helpful error message
@sw
def handle_unsupported(data):
return {
"error": "Unsupported data type",
"type": type(data).__name__,
"hint": "Supported types: int, str, list"
}
# ❌ Bad: Generic error
@sw
def handle_unsupported(data):
return {"error": "Invalid"}
Testing Best Practices
Test Each Handler Independently
# ✅ Good: Test handlers independently
def test_handle_int():
result = handle_int(x=42)
assert result == "int"
def test_handle_string():
result = handle_string(x="hello")
assert result == "string"
# Test dispatch
def test_dispatch():
assert sw()(x=42) == "int"
assert sw()(x="hello") == "string"
Test Rule Priority
def test_rule_priority():
"""Ensure specific rules match before general ones."""
# Specific rule should match
assert sw()(x=150) == "very large"
# General rule should match when specific doesn't
assert sw()(x=50) == "large"
Test Default Handler
def test_default_handler():
"""Ensure default handler catches unmatched cases."""
result = sw()(x=3.14) # Not int or str
assert result == "default"
Test Edge Cases
def test_edge_cases():
"""Test boundary conditions."""
assert sw()(x=0) == "zero"
assert sw()(x=-1) == "negative"
assert sw()(x=1) == "positive"
Documentation Best Practices
Document Your Switcher
Use the description parameter:
# ✅ Good: Self-documenting
api = Switcher(
name='api',
description='HTTP API request router',
prefix='handle_'
)
Document Handler Intent
Use docstrings for complex handlers:
# ✅ Good: Clear documentation
@sw
def handle_admin_request(user, action):
"""
Handle administrative actions.
Args:
user: Authenticated admin user
action: Admin action to perform (create, delete, modify)
Returns:
dict: Action result with status and message
Raises:
PermissionError: If user lacks required permissions
"""
...
Organization Best Practices
Use Separate Files for Large Switchers
# handlers/user.py
user_api = Switcher(prefix='handle_')
@user_api
def handle_create():
...
# handlers/payment.py
payment_api = Switcher(prefix='handle_')
@payment_api
def handle_charge():
...
# main.py
from handlers.user import user_api
from handlers.payment import payment_api
Security Best Practices
Validate User Input
Never trust user input when selecting handlers:
# ✅ Good: Whitelist approach
ALLOWED_COMMANDS = ['start', 'stop', 'restart']
def execute_user_command(command, *args):
if command not in ALLOWED_COMMANDS:
raise ValueError(f"Unauthorized command: {command}")
return commands(command)(*args)
# ❌ Bad: Direct user input
def execute_user_command(command, *args):
return commands(command)(*args) # Dangerous!
Sanitize All Inputs
Always validate and sanitize inputs in your handlers:
# ✅ Good: Safe string handling
@sw
def handle_api_request(path):
if not path.startswith('/api/'):
raise ValueError("Invalid API path")
if '..' in path:
raise ValueError("Path traversal detected")
...
# ❌ Bad: No sanitization
@sw
def handle_api_request(path):
... # Vulnerable to path traversal
Avoid Exposing Internal Handlers
Don’t expose all handlers to external callers:
# ✅ Good: Public API wrapper
class APIServer:
_internal = Switcher() # Private
public = Switcher() # Public
@public
def handle_get_users(self):
return self._internal('fetch_users')()
@_internal
def fetch_users(self):
# Internal implementation
...
SmartPublisher Integration
If you plan to publish your handlers as APIs using SmartPublisher, follow these practices to enable automatic API generation:
Use Complete Type Hints
Type hints enable automatic parameter validation and API documentation:
from __future__ import annotations
# ✅ Good: Complete type hints
@sw
def create_user(name: str, email: str, age: int | None = None) -> dict[str, Any]:
"""Create a new user account."""
return {"id": 123, "name": name, "email": email, "age": age}
# ❌ Bad: Missing type hints
@sw
def create_user(name, email, age=None):
"""Create a new user account."""
return {"id": 123, "name": name, "email": email, "age": age}
Why it matters:
SmartPublisher generates API schemas from type hints
Enables automatic request validation
Provides clear API documentation
Improves IDE autocompletion
Write Comprehensive Docstrings
Use Google-style docstrings with Args, Returns, and Raises sections:
# ✅ Good: Complete documentation
@sw
def transfer_funds(
from_account: str,
to_account: str,
amount: float,
currency: str = "USD"
) -> dict[str, Any]:
"""
Transfer funds between two accounts.
Args:
from_account: Source account identifier (e.g., 'ACC123')
to_account: Destination account identifier (e.g., 'ACC456')
amount: Amount to transfer (must be positive)
currency: Currency code (ISO 4217), defaults to 'USD'
Returns:
Transaction result with status and transaction ID:
{
"transaction_id": str,
"status": "completed" | "pending" | "failed",
"amount": float,
"currency": str
}
Raises:
ValueError: If amount is negative or zero
PermissionError: If user lacks transfer permissions
AccountError: If account is frozen or doesn't exist
"""
if amount <= 0:
raise ValueError("Amount must be positive")
# Process transfer...
return {"transaction_id": "TXN789", "status": "completed"}
# ❌ Bad: Minimal documentation
@sw
def transfer_funds(from_account: str, to_account: str, amount: float) -> dict:
"""Transfer money."""
return {"transaction_id": "TXN789"}
Why it matters:
SmartPublisher extracts docstrings for API documentation
Clear parameter descriptions help API consumers
Raises section documents error handling
Return type description shows response structure
Provide Clear Parameter Descriptions
Be specific about parameter constraints and formats:
# ✅ Good: Specific constraints
@sw
def schedule_meeting(
title: str, # "Team Standup", max 100 chars
date: str, # ISO 8601 format: "2024-01-15T10:00:00Z"
duration_minutes: int # Must be 15, 30, 45, or 60
) -> dict[str, Any]:
"""
Schedule a meeting.
Args:
title: Meeting title (1-100 characters)
date: Meeting date/time in ISO 8601 format (UTC)
duration_minutes: Meeting duration (must be 15, 30, 45, or 60)
Returns:
Meeting details with generated meeting ID
Raises:
ValueError: If duration not in allowed values or title too long
"""
if duration_minutes not in (15, 30, 45, 60):
raise ValueError("Duration must be 15, 30, 45, or 60 minutes")
...
Use Modern Type Hint Syntax
Always import annotations to enable modern syntax:
from __future__ import annotations
# ✅ Good: Modern syntax with union operator
@sw
def process_data(
items: list[dict[str, Any]],
callback: Callable[[dict], bool] | None = None
) -> list[dict[str, Any]]:
"""Process data items with optional callback."""
...
# ❌ Bad: Old syntax (without __future__ import)
from typing import List, Dict, Any, Optional, Callable
@sw
def process_data(
items: List[Dict[str, Any]],
callback: Optional[Callable[[Dict], bool]] = None
) -> List[Dict[str, Any]]:
"""Process data items with optional callback."""
...
Why it matters:
Modern syntax is more readable
Required for Python 3.10+ union syntax (
|operator)Consistent with Python 3.10+ best practices
Example: Well-Documented Handler
Here’s a complete example ready for SmartPublisher:
from __future__ import annotations
from typing import Any
@sw
def create_product(
name: str,
price: float,
category: str,
tags: list[str] | None = None,
available: bool = True
) -> dict[str, Any]:
"""
Create a new product in the catalog.
Args:
name: Product name (1-200 characters, must be unique)
price: Product price in USD (must be positive)
category: Product category (must exist in catalog)
tags: Optional list of search tags (max 10 tags, 50 chars each)
available: Whether product is available for purchase
Returns:
Created product with generated ID and timestamps:
{
"id": str, # Product ID (e.g., "PROD123")
"name": str,
"price": float,
"category": str,
"tags": list[str],
"available": bool,
"created_at": str, # ISO 8601 timestamp
"updated_at": str # ISO 8601 timestamp
}
Raises:
ValueError: If price <= 0, name too long, or too many tags
CategoryError: If category doesn't exist
DuplicateError: If product name already exists
"""
if price <= 0:
raise ValueError("Price must be positive")
if len(name) > 200:
raise ValueError("Name too long (max 200 characters)")
if tags and len(tags) > 10:
raise ValueError("Too many tags (max 10)")
# Create product...
return {
"id": "PROD123",
"name": name,
"price": price,
"category": category,
"tags": tags or [],
"available": available,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
Benefits for SmartPublisher:
✅ Complete type hints enable automatic validation
✅ Detailed docstring generates clear API documentation
✅ Return structure is documented for API consumers
✅ All exceptions are documented for error handling
✅ Parameter constraints are clear for validation
Next Steps
Review Named Handlers Guide
Explore Plugin System
Check out Real-World Examples
Read the API Reference