Skip to main content

Pyvider RPC Plugin

Project description

๐Ÿ๐Ÿ”Œ pyvider.rpcplugin

High-performance, type-safe RPC plugin framework for Python.

Modern gRPC-based plugin architecture with async support, mTLS security, and comprehensive transport options.

Awesome: uv PyPI Version Python Versions Downloads

CI Coverage Type Checked Code style: ruff

Powered by gRPC Built with attrs Performance

License: Apache 2.0


Build lightning-fast, secure RPC plugins! pyvider.rpcplugin provides a complete framework for creating high-performance RPC-based plugins with built-in security, async support, and production-ready patterns. Perfect for microservices, plugin architectures, and inter-process communication.

๐Ÿค” Why pyvider.rpcplugin?

โšก Performance-First

  • Async-native with full asyncio integration for maximum concurrency
  • Efficient transports - Unix domain sockets for local IPC, TCP for network communication
  • Zero-copy protocols - Protocol Buffers with optimized serialization
  • High throughput - Handle 10,000+ requests/second with low latency

๐Ÿ”’ Security-Focused

  • Built-in mTLS - Mutual TLS authentication with certificate management
  • Process Isolation - Plugins run in separate processes, protecting the host application from plugin crashes or resource exhaustion.
  • Transport encryption - Secure communication over any network
  • Magic cookie validation - Handshake verification for trusted connections
  • Certificate utilities - Easy cert generation and rotation

๐Ÿ› ๏ธ Developer Experience

  • Modern Python 3.13+ with complete type annotations and attrs integration
  • Factory functions - Simple APIs for common plugin patterns
  • Comprehensive logging - Integrated with pyvider.telemetry for observability
  • Rich error handling - Detailed exceptions with context and recovery guidance

๐Ÿ—๏ธ Production Ready

  • Robust configuration - Environment variables, file-based, and programmatic setup
  • Graceful shutdown - Clean resource cleanup and connection termination
  • Health monitoring - Built-in health checks and status reporting
  • Battle-tested - Used in production environments with high reliability

๐Ÿš€ Quick Start

Installation

# With uv (recommended)
uv add pyvider-rpcplugin

# With pip
pip install pyvider-rpcplugin

Hello, RPC World!

Create your first RPC plugin in minutes. This example demonstrates a client application launching a simple plugin server (as a separate Python script/executable) and connecting to it.

Note on mTLS: For simplicity, these examples run with mTLS disabled (PLUGIN_AUTO_MTLS=False is set by example_utils.configure_for_example()). In production, mTLS is highly recommended. See examples/05_security_mtls.py for mTLS setup.

1. The Plugin Server Executable (examples/00_dummy_server.py)

This script acts as a minimal executable plugin. When run, it starts an RPC server using a basic protocol and a simple handler. It's designed to be launched by a client process.

# Condensed from examples/00_dummy_server.py
import asyncio
# Imports for pyvider.rpcplugin, logger, and example_utils are present in the actual file
# from example_utils import configure_for_example
# configure_for_example() # Sets up paths and default config (like disabling mTLS)

from pyvider.rpcplugin import plugin_protocol, plugin_server
from pyvider.telemetry import logger

class DummyHandler: # Basic handler
    async def NoOp(self, request, context):
        logger.info("DummyHandler: NoOp called (should not occur in this example)")
        return {}

async def main():
    # configure_for_example() is called at module level.
    # It sets default magic cookie (key & value) and disables mTLS.
    # The launching client is expected to set the correct magic cookie environment variable.
    protocol = plugin_protocol() # Basic protocol
    server = plugin_server(protocol=protocol, handler=DummyHandler())
    logger.info("Dummy server starting (will print handshake string to stdout)...")
    await server.serve() # Prints handshake, then serves until stopped by client
    logger.info("Dummy server stopped.")

# if __name__ == "__main__":
#     asyncio.run(main())

You typically don't run 00_dummy_server.py directly in this scenario; it's launched by the client. If you were to run it independently (e.g., for direct testing with 01b_direct_client_connection.py), it would start and wait for connections.

2. The Client Application (examples/01_quick_start.py)

This script acts as the client (or host application). It uses plugin_client to launch the 00_dummy_server.py script as a subprocess and then connects to it.

# Condensed from examples/01_quick_start.py
import asyncio
import sys
from pathlib import Path
# Imports for pyvider.rpcplugin, logger, and example_utils are present
# from example_utils import configure_for_example
# configure_for_example() # Sets up paths and default config

from pyvider.rpcplugin import plugin_client
from pyvider.telemetry import logger

async def main():
    # configure_for_example() called at module level.
    example_dir = Path(__file__).resolve().parent
    dummy_server_executable = example_dir / "00_dummy_server.py"
    dummy_server_command = [sys.executable, str(dummy_server_executable)]

    client = None
    try:
        logger.info(f"Client launching plugin: {' '.join(dummy_server_command)}")
        # plugin_client reads its config (e.g., magic cookie key/value from
        # configure_for_example) and sets the required environment variable
        # (e.g., PYVIDER_PLUGIN_MAGIC_COOKIE) for the dummy_server_command process.
        client = plugin_client(command=dummy_server_command)
        await client.start() # Starts the server executable and connects via handshake

        logger.info("Client connected to dummy_server plugin successfully!")
        # The dummy_server uses a basic protocol with no custom RPC methods.
        # If it defined services (e.g., via .proto files), you would create a
        # gRPC stub here using client.grpc_channel and make calls:
        # stub = YourServiceStub(client.grpc_channel)
        # response = await stub.YourMethod(YourRequest())
        await asyncio.sleep(2) # Keep connection alive for a moment

    except Exception as e:
        logger.error(f"Client error: {e}", exc_info=True)
    finally:
        if client and client.is_started:
            await client.close() # Stops the server executable too
            logger.info("Client closed, dummy_server plugin stopped.")
        elif client: # If client was created but not started
            await client.close()


# if __name__ == "__main__":
#     asyncio.run(main())

To run this Quick Start example: python examples/01_quick_start.py

This setup demonstrates the typical plugin model where the client (host application) manages the lifecycle of the plugin executable. The plugin_client handles launching the process, the handshake (including magic cookie authentication and transport negotiation), and establishing the gRPC connection.

๐Ÿงฉ Core Concepts & Use Cases

What is a Plugin Architecture?

A plugin architecture allows you to extend a core application with modular, independently developed components called plugins. These plugins run as separate processes, communicating with the main application via well-defined interfaces (often RPC). Key benefits include:

  • Modularity: Features can be developed, deployed, and scaled independently.
  • Isolation: A crash in one plugin doesn't necessarily bring down the core application.
  • Flexibility: Different plugins can even be written in different languages (though pyvider.rpcplugin focuses on Python plugins).
  • Security: Plugins can be sandboxed, limiting their access to system resources.

pyvider.rpcplugin provides the tools to build and manage the RPC communication layer for such an architecture in Python.

When to Use pyvider.rpcplugin?

Consider pyvider.rpcplugin when:

  • You need to extend your Python application with out-of-process plugins for enhanced modularity or security isolation.
  • You're building a system where components (plugins) communicate via efficient, type-safe RPC, especially if they are on the same machine (leveraging Unix domain sockets) or across a network (using TCP with mTLS).
  • High-performance Inter-Process Communication (IPC) is critical.
  • You value clear service contracts defined via Protocol Buffers (protobufs).
  • End-to-end security with mutual TLS (mTLS) is a requirement for plugin communication.
  • You want to standardize plugin communication with features like magic cookie authentication and common configuration patterns.
  • You prefer an async-native framework that integrates well with Python's asyncio.

It simplifies the complexities of setting up and managing gRPC connections, security, and configuration for these scenarios.

Comparison to Alternatives

While many RPC and IPC mechanisms exist, pyvider.rpcplugin offers a specific set of advantages for plugin architectures:

  • vs. Direct gRPC: Building directly with grpcio gives you full control but requires significant boilerplate for server/client setup, security handshakes, transport management, and configuration, especially in a plugin context. pyvider.rpcplugin automates these, providing pre-configured patterns for mTLS, magic cookie authentication, and transport negotiation, letting you focus on your service logic.
  • vs. go-plugin: HashiCorp's go-plugin is a mature framework for Go that heavily inspired pyvider.rpcplugin. pyvider.rpcplugin aims to bring a similar level of robustness and ease-of-use to the Python ecosystem, leveraging Python-specific features like asyncio and rich type hinting. If you're familiar with go-plugin's concepts, you'll find pyvider.rpcplugin follows similar patterns for secure, versioned RPC.
  • vs. Other Python RPC Libraries (e.g., RPyC, Pyro5, Nameko):
    • Many traditional Python RPC libraries are excellent for general-purpose RPC.
    • pyvider.rpcplugin differentiates itself by being built on gRPC, offering high performance and leveraging Protocol Buffers for service definitions, which is beneficial for type safety and cross-language interop (if the host application isn't Python).
    • Its primary focus is on the out-of-process, secure plugin model, with built-in mTLS and handshake mechanisms tailored for this.
    • Some alternatives might be simpler for basic Python-to-Python RPC but may not offer the same level of performance, security features for untrusted plugins, or the strict contract definitions of gRPC.

Client Connection Scenarios

There are two main ways a client connects:

1. Connecting to an Independently Running Server

If you have a pyvider.rpcplugin server already running (e.g., started manually or by another process), you can connect to it directly using grpc.aio if you know its transport address (like a Unix socket path or TCP host/port).

See examples/01b_direct_client_connection.py for a runnable example. Here's a condensed version:

# Condensed from examples/01b_direct_client_connection.py
import asyncio
import grpc # For grpc.aio.insecure_channel

async def run_direct_client(socket_path): # socket_path from running server
    target = f"unix:{socket_path}"
    channel = None
    try:
        # Assumes insecure connection; for mTLS use grpc.aio.secure_channel()
        channel = grpc.aio.insecure_channel(target)
        await asyncio.wait_for(channel.channel_ready(), timeout=5.0)
        print(f"Successfully connected directly to server at {target}")
        # Use channel with your gRPC stubs here
    except Exception as e:
        print(f"Direct connection error: {e}")
    finally:
        if channel: await channel.close()

# To run this, you'd need a server running on 'socket_path'
# For example:
# server_socket = "/tmp/my_independent_server.sock" # Get this from your server's output
# asyncio.run(run_direct_client(server_socket))

2. Client Launching and Connecting to a Plugin Executable

This is the typical model for plugin architectures, where the host application (client) manages the lifecycle of the plugin executable. The plugin_client factory is used for this.

The "Hello, RPC World!" example earlier demonstrates this pattern using examples/00_dummy_server.py as the executable and examples/01_quick_start.py as the client that launches it. Here's a conceptual reminder:

# Conceptual, see "Hello, RPC World!" or examples/01_quick_start.py for full version
from pyvider.rpcplugin import plugin_client

async def call_executable_plugin():
    # The 'command' launches your plugin server executable.
    # plugin_client handles handshake and connection.
    client = plugin_client(command=["python", "./path_to_your_plugin_server_script.py"])
    try:
        await client.start() # Launches the executable and connects

        # Now client.grpc_channel is available to use with your gRPC stubs
        # stub = YourServiceStub(client.grpc_channel)
        # response = await stub.YourMethod(YourRequest(data="..."))
        # print(f"Response from plugin: {response.message}")
        print("Client connected to executable plugin. Use client.grpc_channel.")

    except Exception as e:
        print(f"Error with executable plugin: {e}")
    finally:
        if client:
            await client.close() # Shuts down the plugin executable too

๐ŸŽฏ Key Features

๐Ÿš„ Multiple Transport Options

# Unix Domain Sockets (fastest for local IPC)
server = plugin_server(protocol, handler, transport="unix")

# TCP Sockets (network communication)  
server = plugin_server(protocol, handler, transport="tcp", host="0.0.0.0", port=50051)

# The server will listen on the specified transport.
# Client-side configuration can then specify its preferred transports to connect with.
# (Refer to HandshakeConfig and client configuration for details on negotiation)

๐Ÿ” mTLS Security Made Simple

from pyvider.rpcplugin import configure

# Configure mTLS with certificates
configure(
    PLUGIN_AUTO_MTLS=True,
    PLUGIN_SERVER_CERT="path/to/server.crt",
    PLUGIN_SERVER_KEY="path/to/server.key",
    PLUGIN_CLIENT_CERT="path/to/client.crt",
    PLUGIN_CLIENT_KEY="path/to/client.key"
)

# Certificates are automatically validated during TLS handshakes.
# Secure rotation of certificates is a complex operational process that typically
# involves out-of-band updates to certificate files and graceful restarts;
# this library focuses on using the configured certificates for mTLS.

โš™๏ธ Flexible Configuration

from pyvider.rpcplugin import RPCPluginConfig

# Programmatic configuration
config = RPCPluginConfig()
config.set("PLUGIN_LOG_LEVEL", "DEBUG")
config.set("PLUGIN_HANDSHAKE_TIMEOUT", 30.0)

# Environment variable support
# PLUGIN_MAGIC_COOKIE=my-secret-cookie
# PLUGIN_AUTO_MTLS=true
# PLUGIN_SERVER_TRANSPORTS=unix,tcp

See docs/configuration.md for full details on all configuration options and methods.

๐Ÿ“Š Built-in Observability

from pyvider.telemetry import logger

# Rich logging with context
logger.info(
    "RPC call completed",
    domain="rpc",
    action="call",
    status="success",
    method="SayHello",
    duration_ms=23.5,
    client_id="client-123"
)

# Automatic performance metrics
# Transport connection monitoring
# Error tracking and alerting

๐Ÿ“– Complete Examples

Explore our comprehensive example suite:

Run any example:

cd examples/
python 01_quick_start.py

๐Ÿ—๏ธ Architecture

graph TB
    Client[Client Application] --> Transport[Transport Layer]
    Transport --> |Unix Socket| UDS[Unix Domain Socket]
    Transport --> |TCP| TCP[TCP Socket]
    Transport --> |mTLS| TLS[TLS Encryption]
    
    UDS --> Protocol[Protocol Layer]
    TCP --> Protocol
    TLS --> Protocol
    
    Protocol --> |gRPC| Services[Service Handlers]
    Services --> Business[Business Logic]
    
    subgraph "Security Layer"
        Auth[Magic Cookie Auth]
        Cert[Certificate Management]
        Valid[Validation Pipeline]
    end
    
    Protocol --> Auth
    TLS --> Cert
    Services --> Valid

Core Components

  • ๐Ÿš€ Transport Layer - Unix sockets, TCP, with automatic negotiation
  • ๐Ÿ”Œ Protocol Layer - gRPC with Protocol Buffers serialization
  • ๐Ÿ”’ Security Layer - mTLS, certificate management, authentication
  • โš™๏ธ Configuration - Environment, file, and programmatic configuration
  • ๐Ÿ“Š Observability - Comprehensive logging and metrics integration

๐Ÿ“ˆ Performance

pyvider.rpcplugin is designed for high-performance scenarios:

Metric Unix Socket TCP (localhost) TCP (network)
Throughput 50K+ req/s 25K+ req/s 10K+ req/s
Latency (p99) <1ms <2ms <10ms
Memory Usage ~50MB ~75MB ~100MB
CPU Overhead <5% <10% <15%

Benchmarks on modern hardware (8-core, 16GB RAM) with 1KB payloads

Performance Tips

# Optimize for high throughput
server = plugin_server(
    protocol=protocol,
    handler=handler,
    transport="unix",  # Fastest for local IPC
    config={
        "PLUGIN_CONNECTION_TIMEOUT": 60.0,
        "PLUGIN_HANDSHAKE_TIMEOUT": 10.0
        # Note: Detailed gRPC server options (like max workers) are typically
        # configured directly on the grpc.aio.server if not exposed by RPCPluginServer.
    }
)

# For client-side optimization, manage client instances appropriately.
# If connecting to multiple different plugin executables, create separate
# RPCPluginClient instances for each.
# client = plugin_client(server_path="./path_to_your_plugin_executable")
# await client.start()
# # ... use client ...
# await client.close()

๐Ÿ”ง Advanced Usage

Custom Transport Implementation

from pyvider.rpcplugin.transport.base import RPCPluginTransport

class CustomTransport(RPCPluginTransport):
    async def listen(self) -> str:
        # Implement custom server listening logic
        pass
    
    async def connect(self, endpoint: str) -> None:
        # Implement custom client connection logic  
        pass

# To use a custom transport, instantiate RPCPluginServer directly:
# custom_transport_instance = CustomTransport()
# server = RPCPluginServer(protocol=protocol, handler=handler, transport=custom_transport_instance)
# rather than using the plugin_server() factory.

Protocol Factories

from pyvider.rpcplugin import create_basic_protocol

# For testing and development
protocol = create_basic_protocol()

# Production protocol with custom validation
# Note: 'service_pb2' and 'add_ServiceServicer_to_server' are typically
# generated by grpc_tools from a your_service.proto definition.
protocol = plugin_protocol(
    service_name="ProductionService",
    descriptor_module=service_pb2, # e.g., from your_service_pb2.py
    servicer_add_fn=add_ServiceServicer_to_server # e.g., from your_service_pb2_grpc.py
)

Production Configuration

from pyvider.rpcplugin import configure

# Production-ready setup
configure(
    PLUGIN_MAGIC_COOKIE_VALUE="production-secret-key", # Or PLUGIN_MAGIC_COOKIE
    PLUGIN_PROTOCOL_VERSIONS=[1], # Server announces its supported versions
    # For server transport capabilities (server announces what it can do):
    PLUGIN_SERVER_TRANSPORTS=["unix", "tcp"],
    # For client transport preferences (client announces what it prefers):
    # PLUGIN_CLIENT_TRANSPORTS=["unix"],
    PLUGIN_AUTO_MTLS=True,
    PLUGIN_HANDSHAKE_TIMEOUT=30.0,
    PLUGIN_CONNECTION_TIMEOUT=300.0,
    # Certificate paths for mTLS (server-side configuration)
    PLUGIN_SERVER_CERT="/etc/ssl/certs/server.crt",      # Server's own certificate
    PLUGIN_SERVER_KEY="/etc/ssl/private/server.key",     # Server's own private key
    PLUGIN_CLIENT_ROOT_CERTS="/etc/ssl/certs/ca.crt"     # CA cert(s) to verify client certificates
    # Ensure these paths are accessible by the plugin process
)

๐Ÿ› Troubleshooting

Common Issues

Connection refused errors:

# Check if server is running and endpoint is correct
server_info = await server.get_status()
logger.info("Server status", **server_info)

Certificate validation failures:

# Verify certificate chain and expiration
from pyvider.rpcplugin.crypto.certificate import verify_certificate_chain
valid = verify_certificate_chain(cert_path, ca_path)

Performance bottlenecks:

# Enable detailed performance logging
configure(PLUGIN_LOG_LEVEL="DEBUG")
# Monitor metrics for bottleneck identification

See docs/troubleshooting.md for comprehensive debugging guides.

๐Ÿ“š Documentation

๐Ÿค Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone and setup development environment
git clone https://github.com/provide-io/pyvider-rpcplugin.git
cd pyvider-rpcplugin

# Install with development dependencies
uv sync --all-groups

# Run tests
uv run pytest

# Run type checking
uv run mypy src/

# Format code
uv run ruff format src/ tests/

๐Ÿ“œ License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

๐Ÿ™ Acknowledgements

pyvider.rpcplugin builds upon these excellent open-source libraries:

  • grpcio - High-performance RPC framework
  • attrs - Powerful data classes and configuration
  • structlog - Structured logging foundation
  • cryptography - Modern cryptographic recipes

๐Ÿค– Development Transparency

AI-Assisted Development Notice: This project was developed with significant AI assistance for code generation and implementation. While AI tools performed much of the heavy lifting for writing code, documentation, and tests, all architectural decisions, design patterns, functionality requirements, and final verification were made by human developers.

Human Oversight Includes:

  • RPC architecture and protocol design decisions
  • Security model and mTLS implementation strategy
  • API design and developer experience specifications
  • Performance requirements and optimization targets
  • Testing strategy and reliability requirements
  • Production deployment patterns and operational requirements

AI Assistance Includes:

  • gRPC service implementation and protocol buffer integration
  • Transport layer implementation (Unix/TCP sockets)
  • Certificate management and mTLS automation
  • Configuration system and environment variable handling
  • Example scripts and documentation generation
  • Test case generation and error handling implementation

This approach allows us to leverage AI capabilities for productivity while maintaining human control over critical architectural decisions and production reliability standards.

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

pyvider_rpcplugin-0.0.11.tar.gz (93.2 kB view details)

Uploaded Source

Built Distribution

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

pyvider_rpcplugin-0.0.11-py3-none-any.whl (101.9 kB view details)

Uploaded Python 3

File details

Details for the file pyvider_rpcplugin-0.0.11.tar.gz.

File metadata

  • Download URL: pyvider_rpcplugin-0.0.11.tar.gz
  • Upload date:
  • Size: 93.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.3

File hashes

Hashes for pyvider_rpcplugin-0.0.11.tar.gz
Algorithm Hash digest
SHA256 cd27aa3e7c8ff7f45521ad11c3ad2a6c813ad3e31982d20698db2224aa844f58
MD5 c848384cf3461e8ad1188bed3540bed9
BLAKE2b-256 b86bf10638181706f0f3ea411ba79ad49390ddad4ae13456a464dd2afc3ecbd7

See more details on using hashes here.

File details

Details for the file pyvider_rpcplugin-0.0.11-py3-none-any.whl.

File metadata

File hashes

Hashes for pyvider_rpcplugin-0.0.11-py3-none-any.whl
Algorithm Hash digest
SHA256 2225c8b046792a3d2ab35aca9f294c15be3e8cf7faa361c1efd07da053341d76
MD5 337230d8aa4ecf9d2445647b87a162f2
BLAKE2b-256 d0710d2a5e56cde5d310bb63e5c3311edc65a018dd3f8fe7197be34fb457ee52

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