7. OpenAPI Generation
Unique Feature
Built-in OpenAPI generation is rare in JSON-RPC libraries! Most require manual schema writing or have no documentation features at all.
What You'll Learn
- Generate OpenAPI 3.0 schema from your methods
- Integrate with RapiDoc/Swagger UI
- Auto-document complex types and nested structures
Basic OpenAPI Generation
basic_openapi.py
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
from jsonrpc.openapi import OpenAPIGenerator
# Define methods
@dataclass
class CalculateParams:
x: float
y: float
operation: str
class Calculate(Method):
def execute(self, params: CalculateParams) -> float:
"""Perform arithmetic operation on two numbers.
Supported operations: add, subtract, multiply, divide
"""
ops = {
'add': params.x + params.y,
'subtract': params.x - params.y,
'multiply': params.x * params.y,
'divide': params.x / params.y if params.y != 0 else 0.0
}
return ops.get(params.operation, 0.0)
@dataclass
class GreetParams:
name: str
greeting: str = "Hello"
class Greet(Method):
def execute(self, params: GreetParams) -> str:
"""Greet someone with a customizable greeting message."""
return f"{params.greeting}, {params.name}!"
# Setup
rpc = JSONRPC(version='2.0')
rpc.register('calculate', Calculate())
rpc.register('greet', Greet())
# Generate OpenAPI schema
generator = OpenAPIGenerator(
rpc,
title="Calculator API",
version="1.0.0",
description="Simple calculator with JSON-RPC 2.0"
)
spec = generator.generate()
# spec is a dict containing full OpenAPI 3.0 schema
import json
print(json.dumps(spec, indent=2))
Generated OpenAPI Schema:
openapi_output.json
{
"openapi": "3.0.0",
"info": {
"title": "Calculator API",
"version": "1.0.0",
"description": "Simple calculator with JSON-RPC 2.0"
},
"paths": {
"/": {
"post": {
"summary": "JSON-RPC endpoint",
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{"$ref": "#/components/schemas/CalculateRequest"},
{"$ref": "#/components/schemas/GreetRequest"}
]
}
}
}
}
}
}
},
"components": {
"schemas": {
"CalculateRequest": {
"type": "object",
"properties": {
"jsonrpc": {"type": "string", "enum": ["2.0"]},
"method": {"type": "string", "enum": ["calculate"]},
"params": {
"type": "object",
"properties": {
"x": {"type": "number"},
"y": {"type": "number"},
"operation": {"type": "string"}
},
"required": ["x", "y", "operation"]
},
"id": {"type": "integer"}
},
"required": ["jsonrpc", "method", "params", "id"]
}
}
}
}
Complex Types and Nested Structures
complex_openapi.py
from dataclasses import dataclass
from typing import Literal
from jsonrpc import JSONRPC, Method
from jsonrpc.openapi import OpenAPIGenerator
@dataclass
class Address:
street: str
city: str
country: str
@dataclass
class Contact:
email: str
phone: str | None = None
@dataclass
class CreateUserParams:
username: str
email: str
age: int
address: Address
contacts: list[Contact]
role: Literal["user", "admin", "moderator"] = "user"
tags: list[str] | None = None
@dataclass
class CreateUserResult:
user_id: int
username: str
role: str
class CreateUser(Method):
def execute(self, params: CreateUserParams) -> CreateUserResult:
"""Create a new user with full profile information.
Returns the created user ID and profile summary.
"""
return CreateUserResult(
user_id=123,
username=params.username,
role=params.role,
)
rpc = JSONRPC(version='2.0')
rpc.register('create_user', CreateUser())
generator = OpenAPIGenerator(rpc, title="User Management API", version="1.0.0")
spec = generator.generate()
# OpenAPI schema includes:
# - Nested Address schema
# - Nested Contact schema
# - Literal enum for role
# - Optional types (phone, tags)
# - Array types (contacts, tags)
Generated Schema (excerpt):
nested_schema.json
{
"components": {
"schemas": {
"Address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"}
},
"required": ["street", "city", "country"]
},
"Contact": {
"type": "object",
"properties": {
"email": {"type": "string"},
"phone": {"type": "string", "nullable": true}
},
"required": ["email"]
},
"CreateUserParams": {
"type": "object",
"properties": {
"username": {"type": "string"},
"email": {"type": "string"},
"age": {"type": "integer"},
"address": {"$ref": "#/components/schemas/Address"},
"contacts": {
"type": "array",
"items": {"$ref": "#/components/schemas/Contact"}
},
"role": {
"type": "string",
"enum": ["user", "admin", "moderator"],
"default": "user"
},
"tags": {
"type": "array",
"items": {"type": "string"},
"nullable": true
}
},
"required": ["username", "email", "age", "address", "contacts"]
}
}
}
}
RapiDoc Integration (Flask)
flask_rapidoc.py
from flask import Flask
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
from jsonrpc.openapi import OpenAPIGenerator
import json
app = Flask(__name__)
# Define API
@dataclass
class SearchParams:
query: str
limit: int = 10
class Search(Method):
def execute(self, params: SearchParams) -> list[dict]:
"""Search for items by query string."""
return [{"id": 1, "title": "Result 1"}]
rpc = JSONRPC(version='2.0')
rpc.register('search', Search())
# Generate OpenAPI spec
generator = OpenAPIGenerator(
rpc,
base_url="/rpc",
title="Search API",
version="1.0.0",
)
openapi_spec = generator.generate()
# RPC endpoint
@app.route('/rpc', methods=['POST'])
def handle_rpc():
from flask import request
response = rpc.handle(request.data)
return response, 200, {'Content-Type': 'application/json'}
# OpenAPI spec endpoint
@app.route('/openapi.json')
def openapi():
return openapi_spec
# Interactive documentation with RapiDoc
@app.route('/docs')
def docs():
return f'''
<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc
spec-url="/openapi.json"
render-style="read"
theme="dark"
show-header="false"
allow-try="true"
> </rapi-doc>
</body>
</html>
'''
if __name__ == '__main__':
app.run(port=5000)
# Visit http://localhost:5000/docs for interactive API docs!
FastAPI Integration with RapiDoc
fastapi_rapidoc.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
from jsonrpc.openapi import OpenAPIGenerator
app = FastAPI()
# Define methods
@dataclass
class Item:
name: str
price: float
quantity: int
@dataclass
class CreateOrderParams:
items: list[Item]
customer_id: int
@dataclass
class CreateOrderResult:
order_id: str
customer_id: int
total: float
items_count: int
class CreateOrder(Method):
def execute(self, params: CreateOrderParams) -> CreateOrderResult:
"""Create a new order with items.
Returns order ID and total price.
"""
total = sum(item.price * item.quantity for item in params.items)
return CreateOrderResult(
order_id='ORD-123',
customer_id=params.customer_id,
total=total,
items_count=len(params.items),
)
rpc = JSONRPC(version='2.0')
rpc.register('create_order', CreateOrder())
# Generate OpenAPI
generator = OpenAPIGenerator(
rpc,
base_url="/rpc",
title="Order Management API",
version="2.0.0",
)
openapi_spec = generator.generate()
@app.post('/rpc')
async def handle_rpc(request: Request):
body = await request.body()
response = await rpc.handle_async(body)
return response
@app.get('/openapi.json')
async def get_openapi():
return openapi_spec
@app.get('/docs', response_class=HTMLResponse)
async def docs():
return '''
<!DOCTYPE html>
<html>
<head>
<title>Order API Documentation</title>
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc
spec-url="/openapi.json"
render-style="focused"
theme="light"
primary-color="#7C4DFF"
allow-try="true"
allow-server-selection="false"
> </rapi-doc>
</body>
</html>
'''
Visit http://localhost:8000/docs for interactive API documentation where you can:
- Browse all available methods
- See parameter schemas
- Try requests directly in browser
- View response examples
Security Schemes
security_schemes.py
from jsonrpc.openapi import OpenAPIGenerator
# Bearer token authentication
generator = OpenAPIGenerator(rpc, title="Secure API", version="1.0.0")
generator.add_security_scheme(
"BearerAuth",
scheme_type="http",
scheme="bearer",
bearerFormat="JWT",
)
generator.add_security_requirement("BearerAuth")
# OAuth2
generator = OpenAPIGenerator(rpc, title="OAuth API", version="1.0.0")
generator.add_security_scheme(
"OAuth2",
scheme_type="oauth2",
flows={
"authorizationCode": {
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"read": "Read access",
"write": "Write access",
},
}
},
)
generator.add_security_requirement("OAuth2", scopes=["read", "write"])
Custom Headers
custom_headers.py
generator = OpenAPIGenerator(rpc, title="API with Headers", version="1.0.0")
generator.add_header(
name="X-User-ID",
description="User ID from session",
required=True,
schema={"type": "integer"},
)
generator.add_header(
name="X-Request-ID",
description="Unique request identifier",
required=False,
schema={"type": "string", "format": "uuid"},
)
Why This is Powerful
Traditional approach (manual schema):
# Define method
def calculate(x, y, op):
return x + y
# Separately maintain OpenAPI schema
openapi = {
"paths": {
"/": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
# Manually write schema...
# Must keep in sync with code!
}
}
}
}
}
}
}
}
python-jsonrpc-lib approach:
# Define method with types
@dataclass
class CalculateParams:
x: float
y: float
operation: str
class Calculate(Method):
def execute(self, params: CalculateParams) -> float:
"""Perform arithmetic operation."""
return params.x + params.y
# Schema auto-generated from types!
spec = generator.generate()
Benefits:
| Manual Schema | python-jsonrpc-lib |
|---|---|
| Write schema separately | Auto-generated |
| Keep docs in sync manually | Always in sync |
| No validation | Automatic validation |
| Prone to errors | Type-safe |
| Verbose | Concise |
Key Points
- Automatic: Schema generated from dataclass type hints
- Nested types: Full support for complex structures
- Docstrings: Method descriptions from docstrings
- Interactive docs: RapiDoc/Swagger UI integration
- Always in sync: Schema updates when code changes
- Zero config: Just define types, get documentation
Rare Feature
Most JSON-RPC libraries don't have OpenAPI generation. This is a unique advantage of python-jsonrpc-lib!
What's Next?
→ Integrations - Use with Flask and FastAPI
→ Advanced Topics - Async, batch, protocols