Quick Start
This guide will get you up and running with SmartSwitch in minutes.
Installation
pip install smartswitch
The Two Core Patterns
SmartSwitch provides two main capabilities. Let’s explore them in order.
Pattern 1: Named Handler Registry
Your situation: You have operations and want to call them by name.
Basic Registration
Register functions using decorators:
from smartswitch import Switcher
ops = Switcher()
@ops
def save_data(data):
return f"Saved: {data}"
@ops
def load_data(data):
return f"Loaded: {data}"
# Call by name
result = ops('save_data')("my_file.txt")
print(result) # → "Saved: my_file.txt"
Why it’s better than manual dictionaries:
Functions self-register with decorator
No manual dictionary maintenance
Clear, declarative code
Collision detection built-in
Custom Aliases
Your situation: Function names are long, but you want short command names.
ops = Switcher()
@ops('reset')
def destroy_all_data():
return "Everything destroyed"
@ops('clear')
def remove_cache():
return "Cache cleared"
# Call with friendly alias
result = ops('reset')()
print(result) # → "Everything destroyed"
Key insight: Alias is defined right where the function is defined - no separate mapping needed.
Prefix-Based Auto-Naming
Your situation: You follow naming conventions and want automatic name derivation.
# Set prefix for automatic name stripping
protocols = Switcher(prefix='protocol_')
@protocols # Auto-registers as 's3_aws'
def protocol_s3_aws():
return {"type": "s3", "region": "us-east-1"}
@protocols # Auto-registers as 'gcs'
def protocol_gcs():
return {"type": "gcs", "bucket": "data"}
# Call by derived name (prefix removed)
result = protocols('s3_aws')()
print(result) # → {"type": "s3", "region": "us-east-1"}
Why use prefixes:
Maintain Python naming conventions (
protocol_*functions in IDE)Get clean handler names automatically (
s3_aws, notprotocol_s3_aws)Consistent with method name patterns
Hierarchical Organization
Your situation: You have multiple related registries and want to organize them.
from smartswitch import Switcher
class MyAPI:
# Main switcher
main = Switcher(name="main")
# Child switchers
users = Switcher(name="users", parent=main, prefix="user_")
products = Switcher(name="products", parent=main, prefix="product_")
@users
def user_list(self):
return ["alice", "bob"]
@users
def user_create(self, name):
return f"Created user: {name}"
@products
def product_list(self):
return ["laptop", "phone"]
# Direct access to child switchers
api = MyAPI()
api.users('list')() # → ["alice", "bob"]
# Hierarchical access via parent
api.main('users.list')() # → ["alice", "bob"]
api.main('users.create')("charlie") # → "Created user: charlie"
# Discover structure
for child in api.main._children.values():
print(f"{child.name}: {list(child._methods.keys())}")
# Output:
# users: ['list', 'create']
# products: ['list']
Benefits:
Clear namespace organization
Both direct and hierarchical access
Easy to discover available handlers
Parent can route to children with dotted paths
Pattern 2: Plugin System
Your situation: You need cross-cutting functionality (logging, caching, validation) without modifying handlers.
Basic Plugin Usage
from smartswitch import Switcher
# Add logging plugin
sw = Switcher().plug('logging', flags='print,enabled,after,time')
@sw
def calculate(x, y):
return x + y
# Use normally - logs automatically
result = sw('calculate')(10, 5) # → 15
# Output:
# → calculate(10, 5)
# ← calculate() → 15 (0.0001s)
Why plugins:
Add functionality without modifying handler code
Composable - chain multiple plugins
Each plugin focused on one concern
Enable/disable at runtime
Creating Custom Plugins
from smartswitch import BasePlugin
class ValidationPlugin(BasePlugin):
"""Validate arguments before handler execution."""
def wrap_handler(self, switch, entry, call_next):
def wrapper(*args, **kwargs):
# Custom validation
if not args and not kwargs:
raise ValueError("No arguments provided")
# Call actual handler
return call_next(*args, **kwargs)
return wrapper
# Use custom plugin
sw = Switcher().plug(ValidationPlugin())
@sw
def process(data):
return f"Processed: {data}"
# Plugin validates automatically
result = sw('process')("test") # → "Processed: test"
sw('process')() # Raises ValueError: No arguments provided
Chaining Multiple Plugins
from smartswitch import Switcher
# Chain plugins - they execute in order
sw = (Switcher()
.plug('logging', flags='print,enabled')
.plug('validation')
.plug('cache'))
@sw
def expensive_operation(x):
# Execution order: logging → validation → cache → handler
return x * 2
result = sw('expensive_operation')(5)
Execution flow:
Logging plugin records start
Validation plugin checks arguments
Cache plugin checks for cached result
Handler executes (if not cached)
Plugins unwind in reverse order
Real-World Example: API Router
Combining both patterns for a practical application:
from smartswitch import Switcher
# Create API with logging
api = Switcher(name="api").plug('logging', flags='print,enabled,after,time')
@api('list_users')
def get_users(page=1):
# Simulate database query
return {"users": ["Alice", "Bob", "Charlie"], "page": page}
@api('create_user')
def create_user(name):
# Simulate user creation
return {"id": 123, "name": name, "created": True}
@api('delete_user')
def delete_user(user_id):
return {"id": user_id, "deleted": True}
@api('not_found')
def handle_404():
return {"error": "Not Found", "status": 404}
# Request handler
def handle_request(endpoint, **kwargs):
"""Route requests to appropriate handlers."""
handler = api._methods.get(endpoint)
if handler:
return api(endpoint)(**kwargs)
return api('not_found')()
# Use it
response = handle_request('list_users', page=2)
print(response) # → {"users": [...], "page": 2}
response = handle_request('create_user', name='Dave')
print(response) # → {"id": 123, "name": "Dave", "created": True}
response = handle_request('unknown_endpoint')
print(response) # → {"error": "Not Found", "status": 404}
# Analyze performance
print("\nSlowest operations:")
for entry in api.logging.history(slowest=3):
print(f"{entry['handler']}: {entry['duration']:.4f}s")
Key Concepts Summary
Named Handler Registry
Register by name:
@swuses function nameRegister with alias:
@sw('alias')uses custom nameAuto-naming:
Switcher(prefix='do_')strips prefix automaticallyCall by name:
sw('handler_name')(args)Hierarchical:
parent('child.handler')(args)for organized systems
Plugin System
Add plugins:
sw.plug('logging')orsw.plug(CustomPlugin())Access plugins:
sw.logging.method()orsw.plugin('logging').method()Chain plugins:
sw.plug(A).plug(B).plug(C)Custom plugins: Inherit from
BasePlugin, implementwrap_handler()
Next Steps
Now that you understand the basics:
Basic Usage Guide - Deeper dive into patterns and edge cases
Named Handlers Guide - Advanced registry patterns
Plugin Development - Build your own plugins
Examples - More real-world examples
Quick Reference
from smartswitch import Switcher, BasePlugin
# Create switcher
sw = Switcher()
sw = Switcher(name="api", prefix="api_") # with name and prefix
sw = Switcher(parent=parent_sw) # with parent
# Add plugins
sw.plug('logging', flags='print,enabled,after,time')
sw.plug('custom') # Assuming CustomPlugin is registered
# Register handlers
@sw
def handler_name(arg): pass
@sw('custom_alias')
def handler_function(arg): pass
# Call handlers
result = sw('handler_name')(arg)
result = sw('custom_alias')(arg)
result = parent_sw('child.handler')(arg) # hierarchical
# Access plugins
sw.plugin('logging') # Get plugin instance
sw.logging # shorthand via attribute
# Introspection
sw._methods # dict of registered handlers
sw._children # dict of child switchers
sw.describe() # full description