Restfy
A lightweight async REST framework for Python 3.10+.
Installation
pip install restfy
Usage
Minimal application
A basic Restfy application is shown below.
from restfy import Application, Server, Request, Response
app = Application()
@app.post('/')
async def handler(request: Request) -> Response:
ret = request.data
return Response(ret)
server = Server(app)
server.run()
The @app.post('/') decorator registers a route for POST /. When a request
arrives, Restfy calls the handler, which receives a Request object and returns
a Response. Server.run() starts the asyncio event loop and begins accepting
connections.
Routes can also be registered programmatically via app.add_route(path, handler, method):
from restfy import Application, Server, Response, Request
async def handler(request: Request) -> Response:
data = f'{request.method}: {request.url}'
return Response(data)
app = Application()
app.add_route('/', handler, method='GET')
Routers
For applications with a large number of routes, better module organization is necessary. Restfy provides the Router class to group related routes together.
# servers.py
from restfy import Router
router = Router()
@router.get('')
async def get_servers_list():
return []
@router.post('')
async def create_new_server():
return {}
# application.py
from restfy import Application
from .servers import router as servers_router
app = Application()
app.register_router('/servers', servers_router)
Routes are defined in a separate module using a Router instance. The
application mounts it under a base path with register_router(path, router).
Multiple routers can be registered under different paths.
Available HTTP method decorators
get, post, put, delete, patch, options, head, websocket are
available on both Application and Router.
Receiving data from the request
Data can be passed via path variables, query string parameters, request body
and headers. The Request object exposes all of these:
@router.put('/servers/{key}')
async def update_server(
request: Request,
key: int
) -> Response:
headers: dict = request.headers # request headers
# Body
body: bytes = request.body # raw binary body
data: dict | list | None = request.data # deserialized body
# Path variables
path_args: dict = request.path_args # all path variables
vars: dict = request.vars # path variables not bound to a parameter
# Query string
query_args: dict = request.query_args # all query string parameters
params: dict = request.params # query parameters not bound to a parameter
return Response([])
When a parameter is declared as a function argument (e.g. key: int), Restfy
extracts it automatically from path_args or query_args and performs type
conversion. vars and params hold only the values that were not consumed
as function parameters.
The request body is automatically deserialized into request.data based on the
Content-Type header:
application/json→ dict / listapplication/x-www-form-urlencoded→ dictmultipart/form-data→ dict (fields) +request.files(uploaded files)
Returning data
A handler can return:
- A
Responseinstance for full control over status code, headers and body. - A
dict,list,str,int,floatorbool— Restfy wraps it in aResponseautomatically. - A
(data, status_code)tuple.
from restfy import Response, Request
# Full Response with custom headers
async def handler(request: Request, pk: int) -> Response:
data = f'<b>restfy: pk {pk}</b>'
return Response(data, status=200, headers={'Content-Type': 'text/html'})
# Using the content_type shortcut
async def handler_typed(request: Request, pk: int) -> Response:
data = f'<b>restfy: pk {pk}</b>'
return Response(data, status=200, content_type='text/html')
# Shorthand — Restfy infers the Response
async def handler_short(request: Request):
return {'id': 1, 'name': 'restfy'}
# Tuple shorthand — (data, status_code)
async def handler_tuple(request: Request):
return {'error': 'not found'}, 404
When returning binary data (e.g. a PDF), always use a Response instance with
the appropriate content_type.
CORS
Cross-Origin Resource Sharing is configured via Application.configure_cors() or by passing a CORSConfig instance to the constructor.
from restfy import Application, CORSConfig
app = Application()
# Keyword-argument style
app.configure_cors(
allow_origins=['https://example.com', 'https://app.example.com'],
allow_methods=['GET', 'POST', 'PUT', 'DELETE'],
allow_headers=['Content-Type', 'Authorization'],
allow_credentials=False,
max_age=3600,
expose_headers=['X-Custom-Header'],
)
# Constructor style
app2 = Application(cors=CORSConfig(allow_origins='*'))
allow_origins can be a single string ('*' for wildcard) or a list of allowed origins.
When a specific origin list is configured, only matching origins receive CORS headers and
Vary: Origin is added automatically. When allow_credentials=True, the wildcard '*'
is never sent — the echoed request origin is used instead, as required by the CORS spec.
Preflight (OPTIONS) requests are handled automatically and return 204 No Content with
the appropriate CORS headers.
Middlewares
Middlewares intercept requests and responses. Create a class that extends
Middleware and implement the exec method. Call await self.forward(request)
to pass the request to the next middleware or the route handler.
from restfy import Application, Middleware
class DefaultMiddleware(Middleware):
async def exec(self, request):
# Modify the request before it reaches the handler
request.headers['X-Request-Id'] = 'abc123'
response = await self.forward(request)
# Modify the response before it is returned to the client
response.headers['X-Processed'] = 'true'
return response
app = Application()
app.register_middleware(DefaultMiddleware)
Middlewares are executed in the order they are registered.
HTTPS / TLS
Pass the certificate and key paths to Server to enable TLS. Restfy will
advertise HTTP/2 via ALPN when TLS is active.
from restfy import Application, Server
app = Application()
server = Server(app, ssl_crt='cert.pem', ssl_key='key.pem')
server.run()
Testing
Restfy ships a Client helper that bypasses the TCP layer, making it easy to
write fast unit tests without starting a real server.
import pytest
from restfy import Application, Client
app = Application()
@app.get('/ping')
async def ping():
return {'status': 'ok'}
client = Client(app)
@pytest.mark.asyncio
async def test_ping():
res = await client.get('/ping')
assert res.status == 200
assert res.parser() == {'status': 'ok'}
HTTP client
The http module provides an async HTTP client for calling external services.
from restfy import http
# Simple GET request
res = await http.get('https://api.example.com/items')
print(res.status) # 200
# POST with JSON body and custom headers
data = {'name': 'Nick', 'surname': 'Lauda'}
headers = {'Authorization': 'Bearer token123'}
res = await http.post('https://api.example.com/items', data=data, headers=headers)
print(res.status) # 201
Available functions: get, post, put, delete, patch. For any other
method use http.request(method, url, ...).
Metadata
Release files for restfy 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| restfy-0.5.0.tar.gz | 24.4 kB | Without provenance | Details for restfy-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| restfy-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for restfy-0.5.0-py3-none-any.whl |
Total release size: 51.6 kB
Release files / restfy-0.5.0.tar.gz
| Download URL | restfy-0.5.0.tar.gz |
|---|---|
| Size | 24.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8fcea5459c9136f400eebe28122b40d0d2841c9cd56e36a7978db0a205e79d07
|
|
BLAKE2b-256 checksum How to use checksums |
c60a00dd6ada1918647a8bb833791f8e0ce25dc05492d75145fe4a97d9232b81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.2.1 CPython/3.13.2 Darwin/21.6.0
|
Release files / restfy-0.5.0-py3-none-any.whl
| Download URL | restfy-0.5.0-py3-none-any.whl |
|---|---|
| Size | 27.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a2ecccfbb43875f3974dd3bb6a6bc54b81af3c9fbf03a36b0e41920c9904d8a4
|
|
BLAKE2b-256 checksum How to use checksums |
401de2a4def694e888b8c530e9a01c92c448e113d6811f06d21143f120cfe523
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.2.1 CPython/3.13.2 Darwin/21.6.0
|
Security for restfy 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.