Skip to main content

Lightweight Universal API Router

Project description

agnomux2 - Universal API Multiplexer

Python 3.11+ License: PolyForm NC

โš ๏ธ Alpha Release - This is the debut release of agnomux2. While functional, the API may change in future versions.

Lightweight Universal API Router - Route requests to any API through configuration instead of hardcoded clients. Works with any data type and transport protocol. Plug in your own transports and parsers as needed.

Part of the Agnosweaverโ„ข project suite.

๐Ÿ”ฅ Why agnomux2?

Tired of maintaining dozens of API client libraries? Frustrated with hardcoded HTTP clients, GraphQL clients, gRPC clients, and custom API wrappers? agnomux2 eliminates this complexity by providing a single, configurable router for all your API needs.

# Configure once, route anywhere
router = Router()
router.add_endpoint('weather', {
    'transport': 'http',
    'parser': 'json',
    'request_template': {'url': 'https://api.weather.com/{city}'}
})

router.add_endpoint('payment', {
    'transport': 'grpc', 
    'parser': 'protobuf',
    'request_template': {'service': 'PaymentService'}
})

# Same interface for different APIs and protocols
weather = router.route('weather', city="London")
payment = router.route('payment', amount=100, currency="USD")

โœจ Features

  • ๐Ÿ”„ Transport Agnostic: HTTP, gRPC, WebSockets, message queues, or any transport protocol
  • ๐Ÿ“Š Data Type Agnostic: Handle JSON, XML, binary, protobuf, or any data format seamlessly
  • โš™๏ธ Configuration-Driven: Define endpoints through config instead of hardcoded clients
  • ๐Ÿ—๏ธ Extensible Architecture: Bring-your-own transports and parsers
  • โšก Sync & Async: Full support for both synchronous and asynchronous operations
  • ๐ŸŒŠ Streaming Support: Real-time streaming responses when supported by transports
  • ๐Ÿ“ฆ Minimal Core: Lightweight router without bloat
  • ๐ŸŽฏ Standardized Responses: Consistent response format across all endpoints and data types
  • ๐Ÿ”Œ Plugin System: Registry-based component discovery and registration
  • ๐Ÿ›ก๏ธ Error Transparency: Exceptions bubble up naturally - handle errors your way

๐Ÿ“ฆ Installation

# PyPI
pip install agnomux2

# GitHub
pip install git+https://github.com/agnosweaver/agnomux2.git

Requirements: Python 3.11 or higher

๐Ÿš€ Quick Start

1. Basic Setup

from agnomux2 import Router, TransportRegistry, ParserRegistry

# Create router
router = Router()

2. Implement Transport & Parser

Since agnomux2 provides the core routing logic, you implement transports and parsers for your specific needs:

import requests
import json
from agnomux2 import BaseTransport, BaseParser, RawResponse

class HTTPTransport(BaseTransport):
    def send(self, data, **kwargs):
        response = requests.post(data['url'], json=data.get('payload'))
        return RawResponse(response.text, response.headers, response.status_code)
    
    async def asend(self, data, **kwargs):
        # Your async HTTP implementation
        pass

class JSONParser(BaseParser):
    def parse_request(self, data, template):
        # Build request from template
        return {
            'url': template['url'].format(**data),
            'payload': data
        }
    
    def parse_response(self, raw_response):
        return json.loads(raw_response.data)
    
    def get_content_type(self, raw_response):
        return raw_response.headers.get('content-type', 'application/json')

# Register your components
TransportRegistry.register('http', HTTPTransport)
ParserRegistry.register('json', JSONParser)

3. Configure & Route!

# Add endpoints
router.add_endpoint('weather', {
    'transport': 'http',
    'parser': 'json',
    'request_template': {
        'url': 'https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}'
    }
})

router.add_endpoint('users', {
    'transport': 'http', 
    'parser': 'json',
    'request_template': {
        'url': 'https://jsonplaceholder.typicode.com/users/{user_id}'
    }
})

# Route requests
weather_response = router.route('weather', city="London", api_key="your-key")
user_response = router.route('users', user_id=1)

print(f"Weather: {weather_response.content}")
print(f"User: {user_response.content}")

๐ŸŽฏ Common Use Cases

API Gateway Pattern

# Route to different backends based on endpoint
router.add_endpoint('v1/payments', {
    'transport': 'http',
    'parser': 'json', 
    'request_template': {'url': 'https://payments-v1.internal.com/{endpoint}'}
})

router.add_endpoint('v2/payments', {
    'transport': 'grpc',
    'parser': 'protobuf',
    'request_template': {'service': 'PaymentsV2Service'}
})

# Same interface, different backend implementations
old_payment = router.route('v1/payments', endpoint='charge', amount=100)
new_payment = router.route('v2/payments', amount=100, currency='USD')

Multi-Protocol Integration

# HTTP REST API
router.add_endpoint('rest_users', {
    'transport': 'http',
    'parser': 'json'
})

# GraphQL API  
router.add_endpoint('graphql_users', {
    'transport': 'http',
    'parser': 'graphql'
})

# gRPC Service
router.add_endpoint('grpc_users', {
    'transport': 'grpc', 
    'parser': 'protobuf'
})

# Same application logic, different protocols
for endpoint in ['rest_users', 'graphql_users', 'grpc_users']:
    users = router.route(endpoint, query="active_users")
    process_users(users.content)  # Same processing logic

Database Abstraction

# Route to different databases with same interface
router.add_endpoint('postgres_users', {
    'transport': 'sql',
    'parser': 'json',
    'request_template': {'query': 'SELECT * FROM users WHERE id = {user_id}'}
})

router.add_endpoint('mongodb_users', {
    'transport': 'mongo',
    'parser': 'bson', 
    'request_template': {'collection': 'users', 'filter': {'_id': '{user_id}'}}
})

# Same interface for different databases
pg_user = router.route('postgres_users', user_id=123)
mongo_user = router.route('mongodb_users', user_id=123)

Streaming Data Processing

# Stream from any endpoint that supports it
for chunk in router.route_stream('live_metrics', metric_type='cpu'):
    print(f"CPU: {chunk.content['value']}%")
    if chunk.is_final:
        break

# Handle different streaming data types
for chunk in router.route_stream('video_feed', camera_id=1):
    if chunk.content_type == 'video/mp4':
        process_video_frame(chunk.content)

๐Ÿ“š API Reference

Core Classes

Router

Main interface for configuring endpoints and routing requests.

Methods:

  • add_endpoint(name, config) - Configure a new endpoint
  • route(endpoint, **data) - Route request synchronously
  • aroute(endpoint, **data) - Route request asynchronously
  • route_stream(endpoint, **data) - Stream from endpoint synchronously
  • aroute_stream(endpoint, **data) - Stream from endpoint asynchronously

BaseTransport

Abstract base class for transport implementations.

Must Implement:

  • send(data, **kwargs) - Send data synchronously
  • asend(data, **kwargs) - Send data asynchronously

Optional Override:

  • send_stream(data, **kwargs) - Send with streaming response
  • asend_stream(data, **kwargs) - Async send with streaming response

BaseParser

Abstract base class for parser implementations.

Must Implement:

  • parse_request(data, template) - Build request from data and template
  • parse_response(raw_response) - Parse transport response

Optional Override:

  • get_content_type(raw_response) - Extract content type
  • parse_stream_chunk(raw_chunk) - Parse streaming chunk

Data Structures

Response

@dataclass
class Response:
    content: Any  # Can be any data type
    endpoint_name: str
    content_type: str
    timestamp: str
    metadata: Optional[Dict[str, Any]] = None

StreamChunk

@dataclass  
class StreamChunk:
    content: Any  # Can be any data type
    endpoint_name: str
    content_type: str
    timestamp: str
    metadata: Optional[Dict[str, Any]] = None
    is_final: bool = False

RawResponse

class RawResponse:
    def __init__(self, data: Any, headers: Optional[Dict] = None, status: Optional[Any] = None):
        self.data = data
        self.headers = headers or {}
        self.status = status

๐Ÿ—๏ธ Architecture

agnomux2 follows a clean, modular architecture with clear separation of concerns:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚     Router      โ”‚โ”€โ”€โ”€โ”€โ”‚    Registry     โ”‚โ”€โ”€โ”€โ”€โ”‚  BaseTransport   โ”‚
โ”‚  (Orchestrator) โ”‚    โ”‚   (Discovery)   โ”‚    โ”‚ (Communication)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚                       โ”‚                      โ”‚
         โ”‚              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”             โ”‚
         โ”‚              โ”‚                 โ”‚             โ”‚
         โ–ผ              โ–ผ                 โ–ผ             โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Your App Code  โ”‚ โ”‚   HTTP   โ”‚ โ”‚    gRPC      โ”‚ โ”‚  WebSocket   โ”‚
โ”‚                 โ”‚ โ”‚Transport โ”‚ โ”‚  Transport   โ”‚ โ”‚  Transport   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                           โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚                      โ”‚                      โ”‚
                    โ–ผ                      โ–ผ                      โ–ผ
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
            โ”‚     JSON     โ”‚    โ”‚   Protobuf   โ”‚    โ”‚     XML      โ”‚
            โ”‚   Parser     โ”‚    โ”‚   Parser     โ”‚    โ”‚   Parser     โ”‚
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Principles:

  • Configuration over Code: Define endpoints through config, not hardcoded clients
  • Transport Abstraction: Same interface works with any protocol
  • Data Type Flexibility: Handle any data format through pluggable parsers
  • Error Transparency: Transport/parser exceptions bubble up naturally (by design)
  • Registry Pattern: Components register themselves for discovery

โšก Advanced Features

Template Engines

from agnomux2 import BaseTemplateEngine, TemplateRegistry

class JinjaTemplateEngine(BaseTemplateEngine):
    def render_template(self, template, variables):
        from jinja2 import Template
        return Template(template).render(**variables)

TemplateRegistry.register('jinja', JinjaTemplateEngine)

# Use in endpoint config
router.add_endpoint('dynamic_api', {
    'transport': 'http',
    'parser': 'json',
    'template_engine': 'jinja',
    'request_template': {
        'url': 'https://api.service.com/{{ endpoint }}/{{ resource_id }}',
        'headers': {'Authorization': 'Bearer {{ token }}'}
    }
})

Custom Transport Protocols

import websocket
from agnomux2 import BaseTransport

class WebSocketTransport(BaseTransport):
    def send(self, data, **kwargs):
        ws = websocket.create_connection(data['url'])
        ws.send(json.dumps(data['payload']))
        result = ws.recv()
        ws.close()
        return RawResponse(result)

class MessageQueueTransport(BaseTransport):
    def send(self, data, **kwargs):
        # Your message queue implementation
        pass

Binary Data Handling

class ProtobufParser(BaseParser):
    def parse_request(self, data, template):
        # Serialize to protobuf
        message = MyProtoMessage(**data)
        return {'payload': message.SerializeToString()}
    
    def parse_response(self, raw_response):
        # Deserialize protobuf response
        message = MyProtoMessage()
        message.ParseFromString(raw_response.data)
        return message

    def get_content_type(self, raw_response):
        return "application/x-protobuf"

๐Ÿค Contributing

Coming Soon: A dedicated repository for community contributions and ideas is being set up. Stay tuned!

For now, if you have suggestions or find issues, please reach out via email.

๐Ÿ“„ License

Licensed under the PolyForm Noncommercial License 1.0.0.

โš ๏ธ Important: This license prohibits commercial use. If you need to use agnomux2 in a commercial project, please contact agnosweaver@gmail.com for a commercial license.

๐Ÿ“ž Contact


One interface. Many APIs.

agnomux2 keeps it clean and flexible.

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

agnomux2-0.0.1a0.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

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

agnomux2-0.0.1a0-py3-none-any.whl (15.8 kB view details)

Uploaded Python 3

File details

Details for the file agnomux2-0.0.1a0.tar.gz.

File metadata

  • Download URL: agnomux2-0.0.1a0.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for agnomux2-0.0.1a0.tar.gz
Algorithm Hash digest
SHA256 91a4c883d90270fd7053045245e2ee56d33d293964809293ca3acffdf4e9279c
MD5 ab83b8c2464c22715e8c27cb09c131d0
BLAKE2b-256 3b11aee6ef96ff580670559d9c2e6c81bf346c9016f263181ce38412eeec4b82

See more details on using hashes here.

File details

Details for the file agnomux2-0.0.1a0-py3-none-any.whl.

File metadata

  • Download URL: agnomux2-0.0.1a0-py3-none-any.whl
  • Upload date:
  • Size: 15.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for agnomux2-0.0.1a0-py3-none-any.whl
Algorithm Hash digest
SHA256 997bb61606b1d53da03bbad9547fcb6adc935e54596fe678f4456e9db7dca58e
MD5 d256e51e6fe5e7ba1832654efa3f960d
BLAKE2b-256 e3267391ea71d29127ba67cbf99f1f5251aaa7cadc1dd9e4c6999e53c3ea9b9e

See more details on using hashes here.

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