HTTP RPC Client
The Jararaca HTTP RPC client provides a complete REST client implementation with a decorator-based approach for defining HTTP endpoints. It includes advanced features like authentication, caching, retry logic, form data handling, and file uploads.
Quick Start
from jararaca.rpc.http import (
BearerTokenAuth,
Body,
CacheMiddleware,
Delete,
File,
FormData,
Get,
HttpRpcClientBuilder,
HTTPXHttpRPCAsyncBackend,
Post,
Put,
Query,
RestClient,
Retry,
RetryConfig,
)
@RestClient("https://api.example.com")
class ApiClient:
@Get("/users")
@Query("limit")
async def get_users(self, limit: int) -> dict:
pass
@Post("/users")
@Body("user_data")
async def create_user(self, user_data: dict) -> dict:
pass
# Create client
backend = HTTPXHttpRPCAsyncBackend()
auth = BearerTokenAuth("your-token")
cache = CacheMiddleware(ttl_seconds=300)
builder = HttpRpcClientBuilder(
backend=backend,
middlewares=[auth, cache]
)
client = builder.build(ApiClient)
# Use client
users = await client.get_users(10)
new_user = await client.create_user({"name": "John", "email": "john@example.com"})
HTTP Method Decorators
Basic HTTP Methods
from jararaca.rpc.http import Delete, Get, Patch, Post, Put
@RestClient("https://api.example.com")
class ApiClient:
@Get("/users")
async def get_users(self) -> list[dict]:
pass
@Post("/users")
async def create_user(self) -> dict:
pass
@Put("/users/{user_id}")
async def update_user(self) -> dict:
pass
@Patch("/users/{user_id}")
async def patch_user(self) -> dict:
pass
@Delete("/users/{user_id}")
async def delete_user(self) -> bool:
pass
Request Parameter Decorators
Query Parameters
from jararaca.rpc.http import Query
@Get("/users")
@Query("limit")
@Query("offset")
async def get_users(self, limit: int, offset: int = 0) -> list[dict]:
pass
# Usage: client.get_users(10, 20) -> GET /users?limit=10&offset=20
Path Parameters
from jararaca.rpc.http import PathParam
@Get("/users/{user_id}")
@PathParam("user_id")
async def get_user(self, user_id: int) -> dict:
pass
# Usage: client.get_user(123) -> GET /users/123
Headers
from jararaca.rpc.http import Header
@Get("/users")
@Header("X-Client-Version")
async def get_users(self, x_client_version: str = "1.0") -> list[dict]:
pass
# Usage: client.get_users("2.0") -> adds X-Client-Version: 2.0 header
Request Body
from jararaca.rpc.http import Body
@Post("/users")
@Body("user_data")
async def create_user(self, user_data: dict) -> dict:
pass
# Usage: client.create_user({"name": "John"}) -> sends JSON body
Form Data
from jararaca.rpc.http import FormData
@Post("/login")
@FormData("username")
@FormData("password")
async def login(self, username: str, password: str) -> dict:
pass
# Usage: client.login("user", "pass") -> sends form-encoded data
File Uploads
from jararaca.rpc.http import File, FormData
@Post("/upload")
@FormData("name")
@File("avatar")
async def upload_avatar(self, name: str, avatar: bytes) -> dict:
pass
# Usage:
# with open("avatar.jpg", "rb") as f:
# result = await client.upload_avatar("John", f.read())
Configuration Decorators
Timeout
from jararaca.rpc.http import Timeout
@Get("/slow-endpoint")
@Timeout(30.0) # 30 seconds timeout
async def slow_request(self) -> dict:
pass
Retry Configuration
from jararaca.rpc.http import Retry, RetryConfig
@Get("/unreliable-endpoint")
@Retry(RetryConfig(
max_attempts=3,
backoff_factor=2.0,
retry_on_status_codes=[500, 502, 503, 504]
))
async def unreliable_request(self) -> dict:
pass
Content Type
from jararaca.rpc.http import ContentType
@Post("/xml-endpoint")
@ContentType("application/xml")
@Body("xml_data")
async def send_xml(self, xml_data: str) -> dict:
pass
Authentication
Bearer Token Authentication
from jararaca.rpc.http import BearerTokenAuth
auth = BearerTokenAuth("your-access-token")
builder = HttpRpcClientBuilder(backend=backend, middlewares=[auth])
Basic Authentication
from jararaca.rpc.http import BasicAuth
auth = BasicAuth("username", "password")
builder = HttpRpcClientBuilder(backend=backend, middlewares=[auth])
API Key Authentication
from jararaca.rpc.http import ApiKeyAuth
auth = ApiKeyAuth("your-api-key", header_name="X-API-Key")
builder = HttpRpcClientBuilder(backend=backend, middlewares=[auth])
Middleware
Cache Middleware
The cache middleware provides in-memory caching for GET requests:
from jararaca.rpc.http import CacheMiddleware
cache = CacheMiddleware(ttl_seconds=300) # Cache for 5 minutes
builder = HttpRpcClientBuilder(backend=backend, middlewares=[cache])
Custom Request Middleware
from jararaca.rpc.http import HttpRPCRequest, RequestMiddleware
class LoggingMiddleware(RequestMiddleware):
def on_request(self, request: HttpRPCRequest) -> HttpRPCRequest:
print(f"Making request to {request.url}")
return request
logging_middleware = LoggingMiddleware()
builder = HttpRpcClientBuilder(backend=backend, middlewares=[logging_middleware])
Response Middleware
from jararaca.rpc.http import HttpRPCRequest, HttpRPCResponse, ResponseMiddleware
class ResponseLoggingMiddleware(ResponseMiddleware):
def on_response(self, request: HttpRPCRequest, response: HttpRPCResponse) -> HttpRPCResponse:
print(f"Response from {request.url}: {response.status_code}")
return response
response_middleware = ResponseLoggingMiddleware()
builder = HttpRpcClientBuilder(
backend=backend,
response_middlewares=[response_middleware]
)
Hooks
Request Hooks
from jararaca.rpc.http import HttpRPCRequest, RequestHook
class RequestTimingHook(RequestHook):
def before_request(self, request: HttpRPCRequest) -> HttpRPCRequest:
request.start_time = time.time()
return request
timing_hook = RequestTimingHook()
builder = HttpRpcClientBuilder(
backend=backend,
request_hooks=[timing_hook]
)
Response Hooks
from jararaca.rpc.http import HttpRPCRequest, HttpRPCResponse, ResponseHook
class ResponseTimingHook(ResponseHook):
def after_response(self, request: HttpRPCRequest, response: HttpRPCResponse) -> HttpRPCResponse:
if hasattr(request, 'start_time'):
elapsed = time.time() - request.start_time
print(f"Request took {elapsed:.2f} seconds")
return response
timing_hook = ResponseTimingHook()
builder = HttpRpcClientBuilder(
backend=backend,
response_hooks=[timing_hook]
)
Error Handling
Global Error Handlers
from jararaca.rpc.http import GlobalHttpErrorHandler
@GlobalHttpErrorHandler(404)
def handle_not_found(request, response):
return {"error": "Resource not found"}
@GlobalHttpErrorHandler(500)
def handle_server_error(request, response):
return {"error": "Server error occurred"}
Route-Specific Error Handlers
from jararaca.rpc.http import RouteHttpErrorHandler
@Get("/users/{user_id}")
@PathParam("user_id")
@RouteHttpErrorHandler(404)
def handle_user_not_found(request, response):
return {"error": f"User not found"}
async def get_user(self, user_id: int) -> dict:
pass
Advanced Features
Complete Example with All Features
import asyncio
from jararaca.rpc.http import (
ApiKeyAuth,
BasicAuth,
BearerTokenAuth,
Body,
CacheMiddleware,
ContentType,
Delete,
File,
FormData,
Get,
GlobalHttpErrorHandler,
Header,
HttpRpcClientBuilder,
HTTPXHttpRPCAsyncBackend,
PathParam,
Post,
Put,
Query,
RequestHook,
ResponseHook,
ResponseMiddleware,
RestClient,
Retry,
RetryConfig,
RouteHttpErrorHandler,
Timeout,
)
# Custom middleware
class RequestIdMiddleware(RequestMiddleware):
def on_request(self, request: HttpRPCRequest) -> HttpRPCRequest:
import uuid
request.headers.append(("X-Request-ID", str(uuid.uuid4())))
return request
# Error handlers
@GlobalHttpErrorHandler(500)
def handle_server_error(request, response):
return {"error": "Server error", "status": 500}
@RestClient("https://api.example.com/v1")
class AdvancedApiClient:
@Get("/users")
@Query("limit")
@Query("search")
@Header("X-Client-Version")
@Timeout(10.0)
@CacheMiddleware(ttl_seconds=60)
async def search_users(
self,
limit: int = 10,
search: str = "",
x_client_version: str = "1.0"
) -> list[dict]:
pass
@Post("/users")
@Body("user_data")
@ContentType("application/json")
@Retry(RetryConfig(max_attempts=3, backoff_factor=1.5))
@RouteHttpErrorHandler(400)
def handle_validation_error(request, response):
return {"error": "Validation failed", "details": response.data}
async def create_user(self, user_data: dict) -> dict:
pass
@Put("/users/{user_id}/avatar")
@PathParam("user_id")
@File("avatar")
@FormData("description")
@Timeout(30.0)
async def upload_user_avatar(
self,
user_id: int,
avatar: bytes,
description: str = ""
) -> dict:
pass
@Delete("/users/{user_id}")
@PathParam("user_id")
@Retry(RetryConfig(max_attempts=2))
async def delete_user(self, user_id: int) -> bool:
pass
async def main():
# Setup backend and middleware
backend = HTTPXHttpRPCAsyncBackend(default_timeout=15.0)
auth = BearerTokenAuth("your-access-token")
cache = CacheMiddleware(ttl_seconds=300)
request_id = RequestIdMiddleware()
# Build client with all features
builder = HttpRpcClientBuilder(
backend=backend,
middlewares=[auth, cache, request_id],
response_middlewares=[],
request_hooks=[],
response_hooks=[]
)
client = builder.build(AdvancedApiClient)
try:
# Use the client
users = await client.search_users(limit=20, search="john")
new_user = await client.create_user({
"name": "Jane Doe",
"email": "jane@example.com"
})
# Upload avatar
with open("avatar.jpg", "rb") as f:
avatar_result = await client.upload_user_avatar(
user_id=new_user["id"],
avatar=f.read(),
description="Profile picture"
)
print("✅ All operations completed successfully")
except Exception as e:
print(f"❌ Error: {e}")
if __name__ == "__main__":
asyncio.run(main())
Backend Configuration
HTTPX Backend Options
from jararaca.rpc.http import HTTPXHttpRPCAsyncBackend
backend = HTTPXHttpRPCAsyncBackend(
prefix_url="https://api.example.com", # Base URL for all requests
default_timeout=30.0 # Default timeout in seconds
)
Exception Handling
The HTTP RPC client provides several exception types:
TimeoutException: Raised when a request times outRPCRequestNetworkError: Raised for network-related errorsRPCUnhandleError: Raised when no error handler matches the response status
from jararaca.rpc.http import RPCRequestNetworkError, RPCUnhandleError, TimeoutException
try:
result = await client.get_users()
except TimeoutException:
print("Request timed out")
except RPCRequestNetworkError:
print("Network error occurred")
except RPCUnhandleError as e:
print(f"Unhandled error: {e.response.status_code}")
Best Practices
- Use Type Hints: Always provide type hints for better IDE support and documentation
- Error Handling: Implement appropriate error handlers for expected error conditions
- Timeouts: Set reasonable timeouts for all requests
- Retry Logic: Use retry configuration for operations that may fail temporarily
- Caching: Use cache middleware for read-heavy operations
- Authentication: Store tokens securely and refresh them as needed
- Middleware Order: Consider the order of middleware execution
- Resource Management: Use async context managers when appropriate
Migration from Previous Versions
If you're upgrading from a previous version of the HTTP RPC client, here are the key changes:
- New Decorators:
@FormData,@File,@Timeout,@Retry,@ContentType - Authentication: New authentication middleware classes
- Caching: Built-in cache middleware
- Enhanced Error Handling: More granular exception types
- Middleware System: Expanded middleware and hooks system
- Form Data Support: Native support for form submissions and file uploads
All existing functionality remains backward compatible.