Basic Usage

This guide covers the fundamental concepts and patterns for using SmartSwitch effectively.

Creating a Switcher

from smartswitch import Switcher

sw = Switcher()

You can also give your switcher a name and description for better organization:

sw = Switcher(
    name="api_handlers",
    description="HTTP API request handlers"
)

Registering Handlers

Simple Registration

The most basic way to register a handler is using the @sw decorator:

sw = Switcher()

@sw
def get_users():
    """Fetch all users from database."""
    return ["Alice", "Bob", "Charlie"]

@sw
def get_posts():
    """Fetch all posts from database."""
    return ["Post 1", "Post 2", "Post 3"]

@sw
def create_user(name: str, email: str):
    """Create a new user."""
    return {"name": name, "email": email, "id": 123}

Each function is automatically registered by its function name.

Handlers with Arguments

Handlers can accept any number of arguments with type hints:

@sw
def update_user(user_id: int, name: str, email: str):
    """Update user information."""
    return {
        "user_id": user_id,
        "name": name,
        "email": email,
        "updated": True
    }

@sw
def delete_user(user_id: int):
    """Delete a user by ID."""
    return {"deleted": user_id}

Calling Handlers

By Name

Call handlers by their function name:

# Retrieve and call in one line
users = sw("get_users")()

# Or retrieve first, then call
handler = sw("get_users")
users = handler()

# With arguments
new_user = sw("create_user")(name="Dave", email="dave@example.com")
user_updated = sw("update_user")(user_id=123, name="David", email="david@example.com")

Error Handling

If a handler doesn’t exist, SmartSwitch raises a KeyError:

try:
    result = sw("nonexistent_handler")()
except KeyError:
    print("Handler not found")

You can check if a handler exists before calling:

if "get_users" in sw._handlers:
    users = sw("get_users")()

Custom Aliases

Register handlers with custom names different from the function name:

@sw("reset")
def destroy_all_data():
    """Dangerous operation - use with caution!"""
    return "All data destroyed"

@sw("clear")
def remove_cache():
    """Clear application cache."""
    return "Cache cleared"

# Call by alias
result = sw("reset")()
result = sw("clear")()

This is useful for:

  • User-facing command names

  • API endpoint mapping

  • Backward compatibility

Prefix Conventions

When you have many related handlers, use a consistent prefix:

storage = Switcher(
    name='storage',
    description='Storage backend dispatcher',
    prefix='backend_'
)

@storage
def backend_s3(bucket: str, key: str):
    """Store file in AWS S3."""
    return f"Stored in S3: {bucket}/{key}"

@storage
def backend_gcs(bucket: str, key: str):
    """Store file in Google Cloud Storage."""
    return f"Stored in GCS: {bucket}/{key}"

@storage
def backend_local(path: str):
    """Store file locally."""
    return f"Stored locally: {path}"

# Access by short names (prefix automatically stripped)
result = storage("s3")(bucket="my-bucket", key="file.txt")
result = storage("gcs")(bucket="my-bucket", key="file.txt")
result = storage("local")(path="/tmp/file.txt")

Using Plugins

SmartSwitch supports a powerful plugin system for extending functionality:

Logging Plugin

Automatically log function entry, exit, and errors:

from smartswitch import Switcher
from smartswitch.plugins import LoggingPlugin

api = Switcher()
api.plug(LoggingPlugin(logger_name="api"))

@api
def process_payment(amount: float, currency: str):
    """Process a payment transaction."""
    # Plugin logs entry, exit, and any errors
    return {"amount": amount, "currency": currency, "status": "completed"}

result = api("process_payment")(amount=100.0, currency="USD")
# Logs: "Calling process_payment with ..."
# Logs: "process_payment returned ..."

Custom Plugins

Create your own plugins by inheriting from BasePlugin:

from smartswitch import BasePlugin

class TimingPlugin(BasePlugin):
    """Measure execution time of handlers."""

    def wrap_handler(self, switch, entry, call_next):
        import time

        def wrapper(*args, **kwargs):
            start = time.time()
            try:
                result = call_next(*args, **kwargs)
                elapsed = time.time() - start
                print(f"{entry.name} took {elapsed:.3f}s")
                return result
            except Exception as e:
                elapsed = time.time() - start
                print(f"{entry.name} failed after {elapsed:.3f}s")
                raise

        return wrapper

# Use custom plugin
api = Switcher()
api.plug(TimingPlugin())

@api
def slow_operation():
    import time
    time.sleep(1)
    return "done"

result = api("slow_operation")()
# Prints: "slow_operation took 1.001s"

Descriptor Protocol

Switchers can be used as class attributes and work with the descriptor protocol:

class APIServer:
    handlers = Switcher()

    def __init__(self, base_url):
        self.base_url = base_url

@APIServer.handlers
def get_status(self):
    return {"url": self.base_url, "status": "running"}

server = APIServer("https://api.example.com")
status = server.handlers("get_status")(server)
# → {"url": "https://api.example.com", "status": "running"}

Inspecting Handlers

List all registered handlers:

sw = Switcher()

@sw
def handler1(): pass

@sw
def handler2(): pass

@sw("custom")
def handler3(): pass

# Check registered names
print("handler1" in sw._handlers)  # True
print("handler2" in sw._handlers)  # True
print("custom" in sw._handlers)    # True
print("handler3" in sw._handlers)  # False (registered as "custom")

# List all handler names
print(list(sw._handlers.keys()))
# → ["handler1", "handler2", "custom"]

Best Practices

1. Use Descriptive Names

Make handler names self-documenting:

# ✅ Good
@sw
def send_password_reset_email(user_id: int):
    ...

# ❌ Bad
@sw
def handler1(id: int):
    ...

2. Validate Inputs

Always validate inputs in your handlers:

@sw
def transfer_funds(from_account: int, to_account: int, amount: float):
    if amount <= 0:
        raise ValueError("Amount must be positive")
    if from_account == to_account:
        raise ValueError("Cannot transfer to same account")
    # Process transfer...

3. Document Handlers

Use docstrings to document what each handler does:

@sw
def calculate_shipping(weight: float, distance: float) -> float:
    """
    Calculate shipping cost based on package weight and distance.

    Args:
        weight: Package weight in kilograms
        distance: Shipping distance in kilometers

    Returns:
        Shipping cost in USD

    Raises:
        ValueError: If weight or distance is negative
    """
    if weight < 0 or distance < 0:
        raise ValueError("Weight and distance must be non-negative")
    return weight * 0.5 + distance * 0.1

Next Steps