HTTP API Gateway for MoleculerPy — maps HTTP requests to Moleculer service actions
Project description
MoleculerPy Web
HTTP API Gateway for MoleculerPy — maps HTTP requests to Moleculer service actions via Starlette (ASGI).
Python port of moleculer-web.
Features
- HTTP Gateway — Starlette (ASGI) + uvicorn, deploy via any ASGI server
- Route Aliases —
"GET /users/{id}" → "users.get"with:idand{id}syntax - REST Shorthand —
"REST /users" → 6 CRUD routeswithonly/exceptfilters - Middleware Pipeline — Onion model matching Node.js moleculer-web execution order
- Hooks —
onBeforeCall,onAfterCall,onError(route-level) - Authentication — Sets
ctx.meta.userfrom custom auth function - Authorization — Raises
ForbiddenErroron deny - CORS — Route-level with origin validation (string/list/wildcard/callable)
- Rate Limiting — MemoryStore with async reset,
X-Rate-Limit-*headers - Whitelist/Blacklist — Action access control with fnmatch wildcards + regex
- Error Mapping — MoleculerError hierarchy → HTTP status codes (400-504)
- ctx.meta Passthrough —
$statusCode,$responseHeaders,$responseType,$location - Service Inheritance —
ApiGatewayService(Service)with full broker integration - Internal Actions —
api.listAliases,api.addRoute,api.removeRoute - Auto-aliases —
$services.changedevent + actionrestannotations - Streaming — Async/sync generators →
StreamingResponse - File Upload — Multipart/form-data with filename sanitization
- Static Files — Starlette
StaticFilesmount viasettings.assets - ETag + 304 — Conditional GET with
If-None-Matchsupport - Security — Path traversal, SSRF, open redirect, CRLF injection, body size limits
- Type-Safe — Full type hints with mypy strict mode
Performance
Benchmarked with Apache Bench (ab), Python 3.12:
| Scenario | Throughput | Latency |
|---|---|---|
| Simple GET (mock broker) | 11,425 req/sec | 4.4ms |
| Real NATS + 3 services | 10,900 req/sec | 4.5ms |
| 404 error path | 14,302 req/sec | 3.5ms |
Quick Start
Installation
pip install moleculerpy-web
Basic Gateway
import asyncio
from moleculerpy import Broker
from moleculerpy.settings import Settings
from moleculerpy_web import ApiGatewayService
class Gateway(ApiGatewayService):
name = "api"
settings = {
"port": 3000,
"path": "/api",
"routes": [{
"path": "/v1",
"aliases": {
"REST /users": "users", # 6 CRUD routes
"GET /health": "health.check",
},
"cors": {"origin": "*"},
"rateLimit": {"window": 60, "limit": 100, "headers": True},
}]
}
async def main():
broker = Broker("gateway-1", settings=Settings(transporter="nats://localhost:4222"))
broker.create_service(Gateway)
await broker.start() # starts gateway + uvicorn automatically
# Gateway is now listening on http://0.0.0.0:3000/api/v1/...
asyncio.run(main())
With Authentication
from moleculerpy_web import ApiGatewayService
from moleculerpy_web.errors import UnauthorizedError, ForbiddenError
async def authenticate(ctx, route, request):
token = request.headers.get("authorization", "")[7:] # Bearer <token>
if not token:
return None # Anonymous
user = await verify_token(token)
if not user:
raise UnauthorizedError("Invalid token")
return user # Sets ctx.meta.user
async def authorize_admin(ctx, route, request):
if not ctx.user or ctx.user.get("role") != "admin":
raise ForbiddenError("Admin required")
gateway = ApiGatewayService(broker=broker, settings={
"port": 3000,
"path": "/api",
"routes": [{
"path": "/admin",
"aliases": {"GET /stats": "admin.stats"},
"authentication": authenticate,
"authorization": authorize_admin,
}]
})
Route Configuration
{
"path": "/v1", # Route prefix
"mappingPolicy": "restrict", # "restrict" (default) or "all"
"aliases": {
"GET /users": "users.list",
"REST /products": "products", # REST shorthand → 6 CRUD routes
"REST /orders": {"action": "orders", "only": ["list", "get"]},
},
# Middleware pipeline (Node.js execution order)
"onBeforeCall": async_function, # Before broker.call()
"onAfterCall": async_function, # After broker.call(), can modify data
"onError": async_function, # Custom error handler
# Access control
"whitelist": ["users.*", "products.*"], # fnmatch patterns
"blacklist": ["admin.danger"],
# Authentication
"authentication": async_function, # Returns user object or None
"authorization": async_function, # Raises on deny
# CORS
"cors": {
"origin": "*", # String, list, callable, or wildcard
"methods": ["GET", "POST", "PUT", "DELETE"],
"credentials": False,
"maxAge": 3600,
},
# Rate limiting
"rateLimit": {
"window": 60, # Seconds
"limit": 100, # Max requests per window
"headers": True, # X-Rate-Limit-* headers
},
}
Architecture
HTTP Request (uvicorn)
↓
Starlette (ASGI)
↓
CORS preflight check
↓
AliasResolver → match route
↓
Middleware Pipeline (onion model):
onBeforeCall → Whitelist → Blacklist
→ Auth → Authz → Rate Limit
→ broker.call(action, params, meta)
→ onAfterCall
↓
Response: ctx.meta.$statusCode, $responseHeaders, $responseType
↓
HTTP Response (JSON / bytes / redirect / 204)
Modules
| Module | Purpose | LOC |
|---|---|---|
service.py |
ApiGatewayService lifecycle | 89 |
handler.py |
Request pipeline + response | 81 |
middleware.py |
RequestContext + compose | 97 |
alias.py |
Path matching + REST shorthand | 70 |
errors.py |
HTTP error classes + mapping | 67 |
cors.py |
CORS headers + origin check | 142 |
ratelimit.py |
MemoryStore + rate limit | 138 |
access.py |
Whitelist/blacklist | 132 |
route.py |
RouteConfig dataclass | 77 |
parsers.py |
JSON + form body parsing | 22 |
utils.py |
Path normalization | 22 |
Testing
pip install -e ".[dev]"
pytest # 348 tests, 93% coverage
mypy moleculerpy_web/ # 0 errors (strict mode)
ruff check moleculerpy_web/ # 0 errors
Real NATS demo
# Start NATS
docker run -p 4222:4222 nats:2-alpine
# Run demo (3 microservices + gateway)
python examples/demo_real_v2.py
# Run smoke tests (31 tests on real NATS)
python examples/smoke_test_v2.py
Node.js Compatibility
| Feature | moleculer-web | moleculerpy-web |
|---|---|---|
| Route aliases | ✅ | ✅ |
| REST shorthand | ✅ | ✅ |
Path params (:id / {id}) |
✅ | ✅ (both) |
| Hooks (before/after/error) | ✅ | ✅ |
| Authentication | ✅ | ✅ |
| Authorization | ✅ | ✅ |
| CORS | ✅ | ✅ |
| Rate limiting | ✅ | ✅ |
| Whitelist/blacklist | ✅ | ✅ |
| ctx.meta passthrough | ✅ | ✅ |
| Service inheritance | ✅ | ✅ |
| Internal actions (listAliases) | ✅ | ✅ |
| Auto-aliases ($services.changed) | ✅ | ✅ |
| Streaming responses | ✅ | ✅ |
| File upload (multipart) | ✅ | ✅ |
| Static file serving | ✅ | ✅ |
| ETag / conditional GET | ✅ | ✅ |
| Param merge order | body < query < path | ✅ Same |
| Error format | ✅ | ✅ Same |
| Mapping policy | default: all |
default: restrict (secure) |
Contributing
See CONTRIBUTING.md.
Security
See SECURITY.md.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moleculerpy_web-0.1.1.tar.gz.
File metadata
- Download URL: moleculerpy_web-0.1.1.tar.gz
- Upload date:
- Size: 30.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8319bf4edc00e1bd36013d7fa41b27d7a424c5c49e57bcf834faa643a5d0e0da
|
|
| MD5 |
d94ad1559854a19df860aa95ac27f07e
|
|
| BLAKE2b-256 |
bbd7b772d379dd5f3144818ca18070cb74c3f62f12badd65263bca0db9feb6bf
|
Provenance
The following attestation bundles were made for moleculerpy_web-0.1.1.tar.gz:
Publisher:
publish.yml on MoleculerPy/moleculer-web
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moleculerpy_web-0.1.1.tar.gz -
Subject digest:
8319bf4edc00e1bd36013d7fa41b27d7a424c5c49e57bcf834faa643a5d0e0da - Sigstore transparency entry: 1262334911
- Sigstore integration time:
-
Permalink:
MoleculerPy/moleculer-web@bcb32305e525da129da3fc2ca91e741572a0c23e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MoleculerPy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bcb32305e525da129da3fc2ca91e741572a0c23e -
Trigger Event:
release
-
Statement type:
File details
Details for the file moleculerpy_web-0.1.1-py3-none-any.whl.
File metadata
- Download URL: moleculerpy_web-0.1.1-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b896a493160582df249ce857a7dde2b92257c77d9ee219df79026e9d8b651cd
|
|
| MD5 |
322c0459e8f786ee268ac58023ce8f94
|
|
| BLAKE2b-256 |
dff3944857eccf4dc0e31fba2ef62c7d43d211c4163074d3a90a0b146f41fcaf
|
Provenance
The following attestation bundles were made for moleculerpy_web-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on MoleculerPy/moleculer-web
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moleculerpy_web-0.1.1-py3-none-any.whl -
Subject digest:
4b896a493160582df249ce857a7dde2b92257c77d9ee219df79026e9d8b651cd - Sigstore transparency entry: 1262334931
- Sigstore integration time:
-
Permalink:
MoleculerPy/moleculer-web@bcb32305e525da129da3fc2ca91e741572a0c23e -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MoleculerPy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bcb32305e525da129da3fc2ca91e741572a0c23e -
Trigger Event:
release
-
Statement type: