Skip to main content

Restfy

A lightweight async REST framework for Python 3.10+.

Stable Version

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 / list
  • application/x-www-form-urlencoded → dict
  • multipart/form-data → dict (fields) + request.files (uploaded files)

Returning data

A handler can return:

  • A Response instance for full control over status code, headers and body.
  • A dict, list, str, int, float or bool — Restfy wraps it in a Response automatically.
  • 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)

Source distribution for restfy 0.5.0
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)

Table of built distributions (wheels) for restfy 0.5.0
File Interpreter ABI Platform Actions
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page