Skip to main content

Intelligent rule-based function dispatch for Python

Project description

SmartSwitch Logo

SmartSwitch

Intelligent rule-based function dispatch for Python

PyPI version Tests codecov Python 3.10+ License: MIT Documentation


Replace messy if-elif chains and duplicated logic with clean, maintainable function registries.

Installation

pip install smartswitch

The Problem-Solution Approach

SmartSwitch helps you organize code that needs to handle different cases. Let's see how, step by step.

1. Function Registry Pattern

Problem: You have several operations and want to call them by name.

Traditional approach - Dictionary of functions:

# Hard to maintain, easy to make mistakes
operations = {
    'save': save_data,
    'load': load_data,
    'delete': delete_data
}

# Calling
op = operations.get(action)
if op:
    op(data)

With SmartSwitch - Clean registration:

from smartswitch import Switcher

ops = Switcher()

@ops
def save_data(data):
    # Save logic
    pass

@ops
def load_data(data):
    # Load logic
    pass

@ops
def delete_data(data):
    # Delete logic
    pass

# Call by name
ops('save_data')(data)

2. Custom Action Names

Problem: You want friendly names different from function names.

Traditional approach - Manual mapping:

actions = {
    'reset': destroy_all_data,
    'clear': remove_cache,
    'wipe': erase_history
}

action = actions[command]
action()

With SmartSwitch - Alias registration:

ops = Switcher()

@ops('reset')
def destroy_all_data():
    pass

@ops('clear')
def remove_cache():
    pass

# Call with alias
ops('reset')()

Or use prefix-based auto-naming for convention-driven naming:

# Set a prefix to auto-derive handler names
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 auto-derived names
protocols('s3_aws')()
protocols('gcs')()

3. Value-Based Dispatch

Problem: Choose handler based on actual data values.

Traditional approach - Long if-elif chains:

def process_user(user_type, reason):
    if user_type == 'to_delete' and reason == 'no_payment':
        # Remove user
        pass
    elif reason == 'no_payment':
        # Send reminder
        pass
    elif user_type == 'to_delete':
        # Archive
        pass
    else:
        # Default
        pass

With SmartSwitch - Declarative rules:

users = Switcher()

@users(valrule=lambda user_type, reason:
       user_type == 'to_delete' and reason == 'no_payment')
def remove_user(user_type, reason):
    # Remove user
    pass

@users(valrule=lambda reason: reason == 'no_payment')
def send_payment_reminder(user_type, reason):
    # Send reminder
    pass

@users(valrule=lambda user_type: user_type == 'to_delete')
def archive_user(user_type, reason):
    # Archive
    pass

@users
def handle_default(user_type, reason):
    # Default
    pass

# Automatic dispatch
users()(user_type='to_delete', reason='no_payment')

Tip: For multi-parameter conditions, you can use compact dict-style lambda:

@users(valrule=lambda kw: kw['user_type'] == 'to_delete' and kw['reason'] == 'no_payment')
def remove_user(user_type, reason):
    pass

4. Type-Based Dispatch

Problem: Handle different data types differently.

Traditional approach - Multiple isinstance checks:

def process(data):
    if isinstance(data, str):
        return data.upper()
    elif isinstance(data, int):
        return data * 2
    elif isinstance(data, list):
        return len(data)
    else:
        return None

With SmartSwitch - Type rules:

processor = Switcher()

@processor(typerule={'data': str})
def process_string(data):
    return data.upper()

@processor(typerule={'data': int})
def process_number(data):
    return data * 2

@processor(typerule={'data': list})
def process_list(data):
    return len(data)

@processor
def process_other(data):
    return None

# Automatic dispatch based on type
processor()(data="hello")  # → HELLO
processor()(data=42)       # → 84

Real-World Examples

API Routing

api = Switcher()

@api(valrule=lambda method, path: method == 'GET' and path == '/users')
def get_users(method, path, data=None):
    return list_all_users()

@api(valrule=lambda method, path: method == 'POST' and path == '/users')
def create_user(method, path, data=None):
    return create_new_user(data)

@api
def not_found(method, path, data=None):
    return {"error": "Not Found", "status": 404}

# Dispatch
response = api()('GET', '/users')

Payment Processing

payments = Switcher()

@payments(typerule={'amount': int | float},
          valrule=lambda method, amount: method == 'crypto' and amount > 1000)
def process_large_crypto(method, amount, details):
    return {"processor": "crypto_large", "fee": amount * 0.01}

@payments(valrule=lambda method, **kw: method == 'credit_card')
def process_card(method, amount, details):
    return {"processor": "credit_card", "fee": amount * 0.03}

@payments
def process_generic(method, amount, details):
    return {"error": "Unsupported payment method"}

When to Use

Good for:

  • API handlers and request routers
  • Business logic with multiple branches
  • Plugin systems and extensible architectures
  • State machines and workflow engines
  • When you need type + value checks together

⚠️ Consider alternatives for:

  • Simple 2-3 case switches → use if/elif
  • Pure type dispatch → use functools.singledispatch
  • Very high-performance code (< 10μs functions called millions of times)

Key Features

Core Dispatch Mechanisms

Handler Management

Developer Experience

  • 🧩 Modular & testable: Each handler is an independent function
  • Clean API: Pythonic decorators with zero boilerplate
  • 🚀 Efficient: Optimized with caching (~1-2μs overhead)
  • 🛡️ Type-safe: Full type annotation support

API Discovery and Introspection

Logging and Observability

Organizing multiple Switchers in a class:

from smartswitch import Switcher

class MyAPI:
    # Main switcher
    mainswitch = Switcher(name="main")

    # Add child switchers
    users = mainswitch.add(Switcher(name="users", prefix="user_"))
    products = mainswitch.add(Switcher(name="products", prefix="product_"))

    @users
    def user_list(self):
        return ["alice", "bob"]

    @products
    def product_list(self):
        return ["laptop", "phone"]

# Use directly
api = MyAPI()
api.users('list')()  # Direct access

# Or via mainswitch with dot notation
api.mainswitch('users.list')()  # Hierarchical access
api.mainswitch('products.list')()

Discovering child Switchers:

# Iterate all children
for child in api.mainswitch.children:
    print(f"{child.name}: {child.entries()}")
# Output:
# users: ['list']
# products: ['list']

See the API Discovery Guide for details, or smpub for production usage.

Documentation

📚 Full documentation: smartswitch.readthedocs.io

Guides:

All examples in documentation are tested - They come directly from our test suite with 95% coverage.

Performance

SmartSwitch adds ~1-2 microseconds per dispatch. For real-world functions (API calls, DB queries, business logic), this overhead is negligible:

Function time: 50ms (API call)
Dispatch overhead: 0.002ms
Impact: 0.004% ✅

See Performance Best Practices for more details.

Thread Safety

SmartSwitch is designed for typical Python usage patterns:

  • ✅ Handler dispatch (calling sw()(args)) is fully thread-safe - uses read-only operations
  • ⚠️ Decorator registration should be done at module import time (single-threaded)

Recommended usage:

# Module level - executed once at import (safe)
switch = Switcher()

@switch(typerule={'x': int})
def handle_int(x):
    return x * 2

# Runtime - called many times (thread-safe)
result = switch()(x=42)

For advanced scenarios requiring runtime registration in multi-threaded applications, external synchronization is needed.

License

MIT

Contributing

Contributions welcome! Please feel free to submit a Pull Request.


✨ Part of the genro-libs family of developer tools ✨

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smartswitch-0.4.0.tar.gz (30.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smartswitch-0.4.0-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

Details for the file smartswitch-0.4.0.tar.gz.

File metadata

  • Download URL: smartswitch-0.4.0.tar.gz
  • Upload date:
  • Size: 30.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smartswitch-0.4.0.tar.gz
Algorithm Hash digest
SHA256 92a7e74570db0039632e1e2afb1705965e655517165ae55c2c963eaaaedfc057
MD5 e6ab829c07d3c32be0c4288384d0d123
BLAKE2b-256 522e86044ca0a3a83f4b9b1cb3d7fffa2b7c5a39499894783ec0228072bbf5dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartswitch-0.4.0.tar.gz:

Publisher: publish.yml on genropy/smartswitch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file smartswitch-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: smartswitch-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 14.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smartswitch-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e6deac2e9f8525bd4404b175bb9a12e99217b0c4e1428616ff5ddb434d1467d1
MD5 0bd9831f5c6d04043856df30a904c1b0
BLAKE2b-256 8cc0c2659d174ce0b51e47bdb47d6a5df974f7e0e465be40b10768f798457cf2

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartswitch-0.4.0-py3-none-any.whl:

Publisher: publish.yml on genropy/smartswitch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page