# Action Dispatch
[](https://badge.fury.io/py/action-dispatch)
[](https://pypi.org/project/action-dispatch/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/eowl/action-dispatch/actions)
A powerful and flexible Python library for multi-dimensional action dispatching. Route function calls dynamically based on context dimensions like user roles, environments, API versions, or any custom attributes.
## Features
**Multi-dimensional Routing** - Dispatch based on multiple context attributes simultaneously
**Dynamic Handler Registration** - Register handlers using decorators or programmatically
**Flexible Context Matching** - Support for exact matches and fallback strategies
## Installation
**Requirements:** Python 3.9+
```bash
pip install action-dispatch
```
## Quick Start
```python
from action_dispatch import ActionDispatcher
# Create dispatcher with dimensions
dispatcher = ActionDispatcher(['role', 'environment'])
# Register handlers using decorators
@dispatcher.handler("create_user", role="admin")
def admin_create_user(params):
return f"Admin creating user: {params.get('username')}"
@dispatcher.handler("create_user", role="manager")
def manager_create_user(params):
return f"Manager creating user: {params.get('username')}"
# Define context class
class RequestContext:
def __init__(self, role, environment):
self.role = role
self.environment = environment
# Dispatch actions based on context
admin_context = RequestContext("admin", "production")
result = dispatcher.dispatch(admin_context, "create_user", username="john")
print(result) # "Admin creating user: john"
```
## Advanced Usage
### Multi-dimensional Routing
```python
dispatcher = ActionDispatcher(['role', 'environment', 'feature_flag'])
@dispatcher.handler("process_payment",
role="admin",
environment="production",
feature_flag="new_payment_system")
def new_payment_handler(params):
return "Processing with new payment system"
@dispatcher.handler("process_payment",
role="admin",
environment="production")
def default_payment_handler(params):
return "Processing with default system"
```
### Global Handlers
```python
# Global handlers work across all contexts
@dispatcher.global_handler("health_check")
def health_check(params):
return {"status": "healthy", "timestamp": time.time()}
# Global handlers have priority over scoped handlers
result = dispatcher.dispatch(any_context, "health_check")
```
### Programmatic Registration
```python
def custom_handler(params):
return "Custom response"
# Register without decorators
dispatcher.register("custom_action", custom_handler, role="user")
# Register global handler
dispatcher.register_global("system_status", lambda p: "OK")
```
### Error Handling
```python
from action_dispatch import HandlerNotFoundError, InvalidDimensionError
try:
result = dispatcher.dispatch(context, "unknown_action")
except HandlerNotFoundError as e:
print(f"No handler found for action: {e.action}")
print(f"Context rules: {e.rules}")
try:
dispatcher.register("action", handler, invalid_dimension="value")
except InvalidDimensionError as e:
print(f"Invalid dimension: {e.dimension}")
print(f"Available dimensions: {e.available_dimensions}")
```
## Real-World Examples
### Web API with Role-Based Access Control
```python
from action_dispatch import ActionDispatcher
# Set up dispatcher for API routing
api_dispatcher = ActionDispatcher(['role', 'api_version'])
@api_dispatcher.handler("get_users", role="admin", api_version="v2")
def get_users_admin_v2(params):
return {
"users": get_all_users(),
"total": count_all_users(),
"permissions": ["read", "write", "delete"]
}
@api_dispatcher.handler("get_users", role="user", api_version="v2")
def get_users_regular_v2(params):
return {
"users": get_user_own_data(params['context_object']),
"permissions": ["read"]
}
# API endpoint
def api_get_users(request):
try:
result = api_dispatcher.dispatch(
request.user,
"get_users",
request_id=request.id
)
return JsonResponse(result)
except HandlerNotFoundError:
return JsonResponse({"error": "Forbidden"}, status=403)
```
### Microservices Environment Routing
```python
service_dispatcher = ActionDispatcher(['environment', 'service_version'])
@service_dispatcher.handler("process_order",
environment="production",
service_version="v2")
def process_order_prod_v2(params):
# Use production database and new algorithm
return production_order_processor.process(params['order_data'])
@service_dispatcher.handler("process_order",
environment="staging")
def process_order_staging(params):
# Use staging database with verbose logging
return staging_order_processor.process(params['order_data'])
```
### Plugin System
```python
plugin_dispatcher = ActionDispatcher(['plugin_type', 'version'])
# Plugins can register their handlers
@plugin_dispatcher.handler("transform_data",
plugin_type="image_processor",
version="2.0")
def image_transform_v2(params):
return enhanced_image_transform(params['data'])
# Dynamic plugin loading
for plugin in load_plugins():
plugin.register_handlers(plugin_dispatcher)
# Process with appropriate plugin
result = plugin_dispatcher.dispatch(context, "transform_data", data=input_data)
```
## API Reference
### ActionDispatcher
#### `__init__(dimensions=None)`
Create a new dispatcher with optional dimensions.
- `dimensions` (list, optional): List of dimension names for routing
#### `@handler(action, **kwargs)`
Decorator to register a handler for specific action and dimensions.
- `action` (str): Action name
- `**kwargs`: Dimension values for routing
#### `@global_handler(action)`
Decorator to register a global handler that works across all contexts.
- `action` (str): Action name
#### `register(action, handler, **kwargs)`
Programmatically register a handler.
- `action` (str): Action name
- `handler` (callable): Handler function
- `**kwargs`: Dimension values for routing
#### `dispatch(context_object, action_name, **kwargs)`
Dispatch an action based on context.
- `context_object`: Object with dimension attributes
- `action_name` (str): Action to dispatch
- `**kwargs`: Additional parameters passed to handler
### Exceptions
- `ActionDispatchError`: Base exception class
- `InvalidDimensionError`: Raised for invalid dimension parameters
- `HandlerNotFoundError`: Raised when no handler is found
- `InvalidActionError`: Raised for invalid action names
## Development
### Setting up Development Environment
```bash
# Clone the repository
git clone https://github.com/eowl/action-dispatch.git
cd action-dispatch
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
```
### Running Tests
```bash
# Run all tests
python -m unittest discover tests -v
# Run with coverage
coverage run -m unittest discover tests
coverage report
coverage html
# Run specific test file
python -m unittest tests.test_action_dispatcher -v
# Run specific test class
python -m unittest tests.test_action_dispatcher.TestActionDispatcher -v
```
### Code Quality
```bash
# Format code
black action_dispatch tests
# Lint code
flake8 action_dispatch tests
# Type checking
mypy action_dispatch
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Add tests for your changes
5. Ensure all tests pass (`python -m pytest`)
6. Commit your changes (`git commit -m 'Add amazing feature'`)
7. Push to the branch (`git push origin feature/amazing-feature`)
8. Open a Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a list of changes and releases.
<!-- ## Support
- 📖 [Documentation](https://action-dispatch.readthedocs.io)
- 🐛 [Issue Tracker](https://github.com/eowl/action-dispatch/issues)
- 💬 [Discussions](https://github.com/eowl/action-dispatch/discussions) -->
## Acknowledgments
- Inspired by the need for flexible routing in complex applications
- Built with modern Python best practices
- Thoroughly tested and documented
Raw data
{
"_id": null,
"home_page": null,
"name": "action-dispatch",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": "eowl <eowl@me.com>",
"keywords": "action, context-based, dispatch, framework, handler, middleware, multi-dimensional, routing",
"author": null,
"author_email": "eowl <eowl@me.com>",
"download_url": "https://files.pythonhosted.org/packages/23/5d/9a3bdfa633dbffb7e24d8fa52e20e1c1573309a926dbe0ada95e104116c2/action_dispatch-0.1.0.tar.gz",
"platform": null,
"description": "# Action Dispatch\n\n[](https://badge.fury.io/py/action-dispatch)\n[](https://pypi.org/project/action-dispatch/)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/eowl/action-dispatch/actions)\n\nA powerful and flexible Python library for multi-dimensional action dispatching. Route function calls dynamically based on context dimensions like user roles, environments, API versions, or any custom attributes.\n\n## Features\n\n**Multi-dimensional Routing** - Dispatch based on multiple context attributes simultaneously \n**Dynamic Handler Registration** - Register handlers using decorators or programmatically \n**Flexible Context Matching** - Support for exact matches and fallback strategies \n\n## Installation\n\n**Requirements:** Python 3.9+\n\n```bash\npip install action-dispatch\n```\n\n## Quick Start\n\n```python\nfrom action_dispatch import ActionDispatcher\n\n# Create dispatcher with dimensions\ndispatcher = ActionDispatcher(['role', 'environment'])\n\n# Register handlers using decorators\n@dispatcher.handler(\"create_user\", role=\"admin\")\ndef admin_create_user(params):\n return f\"Admin creating user: {params.get('username')}\"\n\n@dispatcher.handler(\"create_user\", role=\"manager\") \ndef manager_create_user(params):\n return f\"Manager creating user: {params.get('username')}\"\n\n# Define context class\nclass RequestContext:\n def __init__(self, role, environment):\n self.role = role\n self.environment = environment\n\n# Dispatch actions based on context\nadmin_context = RequestContext(\"admin\", \"production\")\nresult = dispatcher.dispatch(admin_context, \"create_user\", username=\"john\")\nprint(result) # \"Admin creating user: john\"\n```\n\n## Advanced Usage\n\n### Multi-dimensional Routing\n\n```python\ndispatcher = ActionDispatcher(['role', 'environment', 'feature_flag'])\n\n@dispatcher.handler(\"process_payment\", \n role=\"admin\", \n environment=\"production\", \n feature_flag=\"new_payment_system\")\ndef new_payment_handler(params):\n return \"Processing with new payment system\"\n\n@dispatcher.handler(\"process_payment\", \n role=\"admin\", \n environment=\"production\")\ndef default_payment_handler(params):\n return \"Processing with default system\"\n```\n\n### Global Handlers\n\n```python\n# Global handlers work across all contexts\n@dispatcher.global_handler(\"health_check\")\ndef health_check(params):\n return {\"status\": \"healthy\", \"timestamp\": time.time()}\n\n# Global handlers have priority over scoped handlers\nresult = dispatcher.dispatch(any_context, \"health_check\")\n```\n\n### Programmatic Registration\n\n```python\ndef custom_handler(params):\n return \"Custom response\"\n\n# Register without decorators\ndispatcher.register(\"custom_action\", custom_handler, role=\"user\")\n\n# Register global handler\ndispatcher.register_global(\"system_status\", lambda p: \"OK\")\n```\n\n### Error Handling\n\n```python\nfrom action_dispatch import HandlerNotFoundError, InvalidDimensionError\n\ntry:\n result = dispatcher.dispatch(context, \"unknown_action\")\nexcept HandlerNotFoundError as e:\n print(f\"No handler found for action: {e.action}\")\n print(f\"Context rules: {e.rules}\")\n\ntry:\n dispatcher.register(\"action\", handler, invalid_dimension=\"value\")\nexcept InvalidDimensionError as e:\n print(f\"Invalid dimension: {e.dimension}\")\n print(f\"Available dimensions: {e.available_dimensions}\")\n```\n\n## Real-World Examples\n\n### Web API with Role-Based Access Control\n\n```python\nfrom action_dispatch import ActionDispatcher\n\n# Set up dispatcher for API routing\napi_dispatcher = ActionDispatcher(['role', 'api_version'])\n\n@api_dispatcher.handler(\"get_users\", role=\"admin\", api_version=\"v2\")\ndef get_users_admin_v2(params):\n return {\n \"users\": get_all_users(),\n \"total\": count_all_users(),\n \"permissions\": [\"read\", \"write\", \"delete\"]\n }\n\n@api_dispatcher.handler(\"get_users\", role=\"user\", api_version=\"v2\") \ndef get_users_regular_v2(params):\n return {\n \"users\": get_user_own_data(params['context_object']),\n \"permissions\": [\"read\"]\n }\n\n# API endpoint\ndef api_get_users(request):\n try:\n result = api_dispatcher.dispatch(\n request.user, \n \"get_users\",\n request_id=request.id\n )\n return JsonResponse(result)\n except HandlerNotFoundError:\n return JsonResponse({\"error\": \"Forbidden\"}, status=403)\n```\n\n### Microservices Environment Routing\n\n```python\nservice_dispatcher = ActionDispatcher(['environment', 'service_version'])\n\n@service_dispatcher.handler(\"process_order\", \n environment=\"production\", \n service_version=\"v2\")\ndef process_order_prod_v2(params):\n # Use production database and new algorithm\n return production_order_processor.process(params['order_data'])\n\n@service_dispatcher.handler(\"process_order\", \n environment=\"staging\")\ndef process_order_staging(params):\n # Use staging database with verbose logging\n return staging_order_processor.process(params['order_data'])\n```\n\n### Plugin System\n\n```python\nplugin_dispatcher = ActionDispatcher(['plugin_type', 'version'])\n\n# Plugins can register their handlers\n@plugin_dispatcher.handler(\"transform_data\", \n plugin_type=\"image_processor\", \n version=\"2.0\")\ndef image_transform_v2(params):\n return enhanced_image_transform(params['data'])\n\n# Dynamic plugin loading\nfor plugin in load_plugins():\n plugin.register_handlers(plugin_dispatcher)\n\n# Process with appropriate plugin\nresult = plugin_dispatcher.dispatch(context, \"transform_data\", data=input_data)\n```\n\n## API Reference\n\n### ActionDispatcher\n\n#### `__init__(dimensions=None)`\nCreate a new dispatcher with optional dimensions.\n\n- `dimensions` (list, optional): List of dimension names for routing\n\n#### `@handler(action, **kwargs)`\nDecorator to register a handler for specific action and dimensions.\n\n- `action` (str): Action name\n- `**kwargs`: Dimension values for routing\n\n#### `@global_handler(action)`\nDecorator to register a global handler that works across all contexts.\n\n- `action` (str): Action name\n\n#### `register(action, handler, **kwargs)`\nProgrammatically register a handler.\n\n- `action` (str): Action name \n- `handler` (callable): Handler function\n- `**kwargs`: Dimension values for routing\n\n#### `dispatch(context_object, action_name, **kwargs)`\nDispatch an action based on context.\n\n- `context_object`: Object with dimension attributes\n- `action_name` (str): Action to dispatch\n- `**kwargs`: Additional parameters passed to handler\n\n### Exceptions\n\n- `ActionDispatchError`: Base exception class\n- `InvalidDimensionError`: Raised for invalid dimension parameters\n- `HandlerNotFoundError`: Raised when no handler is found\n- `InvalidActionError`: Raised for invalid action names\n\n## Development\n\n### Setting up Development Environment\n\n```bash\n# Clone the repository\ngit clone https://github.com/eowl/action-dispatch.git\ncd action-dispatch\n\n# Create virtual environment\npython -m venv venv\nsource venv/bin/activate # On Windows: venv\\Scripts\\activate\n\n# Install development dependencies\npip install -e \".[dev]\"\n\n# Install pre-commit hooks\npre-commit install\n```\n\n### Running Tests\n\n```bash\n# Run all tests\npython -m unittest discover tests -v\n\n# Run with coverage\ncoverage run -m unittest discover tests\ncoverage report\ncoverage html\n\n# Run specific test file\npython -m unittest tests.test_action_dispatcher -v\n\n# Run specific test class\npython -m unittest tests.test_action_dispatcher.TestActionDispatcher -v\n```\n\n### Code Quality\n\n```bash\n# Format code\nblack action_dispatch tests\n\n# Lint code \nflake8 action_dispatch tests\n\n# Type checking\nmypy action_dispatch\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Make your changes\n4. Add tests for your changes\n5. Ensure all tests pass (`python -m pytest`)\n6. Commit your changes (`git commit -m 'Add amazing feature'`)\n7. Push to the branch (`git push origin feature/amazing-feature`)\n8. Open a Pull Request\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a list of changes and releases.\n\n<!-- ## Support\n\n- \ud83d\udcd6 [Documentation](https://action-dispatch.readthedocs.io)\n- \ud83d\udc1b [Issue Tracker](https://github.com/eowl/action-dispatch/issues)\n- \ud83d\udcac [Discussions](https://github.com/eowl/action-dispatch/discussions) -->\n\n## Acknowledgments\n\n- Inspired by the need for flexible routing in complex applications\n- Built with modern Python best practices\n- Thoroughly tested and documented\n",
"bugtrack_url": null,
"license": null,
"summary": "A flexible action dispatching system with multi-dimensional routing capabilities",
"version": "0.1.0",
"project_urls": {
"Bug Tracker": "https://github.com/eowl/action-dispatch/issues",
"Changelog": "https://github.com/eowl/action-dispatch/blob/main/CHANGELOG.md",
"Documentation": "https://action-dispatch.readthedocs.io",
"Homepage": "https://github.com/eowl/action-dispatch",
"Repository": "https://github.com/eowl/action-dispatch.git"
},
"split_keywords": [
"action",
" context-based",
" dispatch",
" framework",
" handler",
" middleware",
" multi-dimensional",
" routing"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "6a19c3d485a6101d7afaf6639c9682a191b303ef4b54d2697135caa4b7e4cd85",
"md5": "22cefac501618fdf80a6fe8575484e65",
"sha256": "1d7632aba2699cfcf950e746b4265090f6e5a3d5a0b52cd35e208026d5e54cde"
},
"downloads": -1,
"filename": "action_dispatch-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "22cefac501618fdf80a6fe8575484e65",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 8168,
"upload_time": "2025-07-20T18:27:12",
"upload_time_iso_8601": "2025-07-20T18:27:12.710894Z",
"url": "https://files.pythonhosted.org/packages/6a/19/c3d485a6101d7afaf6639c9682a191b303ef4b54d2697135caa4b7e4cd85/action_dispatch-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "235d9a3bdfa633dbffb7e24d8fa52e20e1c1573309a926dbe0ada95e104116c2",
"md5": "d71e241bf0d9257dac193bc8388ce6b5",
"sha256": "d99d5a932642df1959550549bd41926d35d7c22b5d6e945d7445e1edef7c5dc4"
},
"downloads": -1,
"filename": "action_dispatch-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "d71e241bf0d9257dac193bc8388ce6b5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 93306,
"upload_time": "2025-07-20T18:27:14",
"upload_time_iso_8601": "2025-07-20T18:27:14.220155Z",
"url": "https://files.pythonhosted.org/packages/23/5d/9a3bdfa633dbffb7e24d8fa52e20e1c1573309a926dbe0ada95e104116c2/action_dispatch-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-20 18:27:14",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "eowl",
"github_project": "action-dispatch",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "action-dispatch"
}