Skip to main content

Intelligent rule-based function dispatch for Python

Project description

SmartSwitch Logo

SmartSwitch 🧠

Intelligent rule-based function dispatch for Python

Python Version PyPI version PyPI Downloads License: MIT Development Status

Tests codecov Documentation Code style: black Ruff

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')()

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)

Features

  • 🎯 Value-based dispatch: Match on runtime values
  • 📦 Named handler registry: Look up by name or alias
  • 🔢 Type-based dispatch: Match on argument types
  • 🧩 Modular: Each handler is separate and testable
  • Clean API: Pythonic decorators
  • 🚀 Efficient: Optimized with caching (~1-2μs overhead)

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% ✅

License

MIT

Contributing

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

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.1.0.tar.gz (15.8 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.1.0-py3-none-any.whl (8.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for smartswitch-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e67420eedfa20df4e180270eee1a6c5b059a5789c5f440eb5e4f8551aef62100
MD5 a5df26c87ff5dfb7d38d684b1ef677f4
BLAKE2b-256 94d94b0e047ac508a12d680bae906c2cbfccb0aa817ae88873898e7e8771e719

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartswitch-0.1.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.1.0-py3-none-any.whl.

File metadata

  • Download URL: smartswitch-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.6 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8844f7e22ccdc6fccc6642fba8fdc494dbcfbc86ee168873dbce4f682a2b88ff
MD5 a817fa4daf27ffd65d718d4303f894a0
BLAKE2b-256 4da46e54b357672d1e285976717e76bb319e49e7eda525a805d820c3e2c4a1d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartswitch-0.1.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