Skip to main content

A lightweight async REST framework for Python 3.10+

Project description

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, ...).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

restfy-0.5.0.tar.gz (24.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

restfy-0.5.0-py3-none-any.whl (27.2 kB view details)

Uploaded Python 3

File details

Details for the file restfy-0.5.0.tar.gz.

File metadata

  • Download URL: restfy-0.5.0.tar.gz
  • Upload date:
  • Size: 24.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.13.2 Darwin/21.6.0

File hashes

Hashes for restfy-0.5.0.tar.gz
Algorithm Hash digest
SHA256 8fcea5459c9136f400eebe28122b40d0d2841c9cd56e36a7978db0a205e79d07
MD5 8bc3e4eafd9f7882e140196b9b137a7e
BLAKE2b-256 c60a00dd6ada1918647a8bb833791f8e0ce25dc05492d75145fe4a97d9232b81

See more details on using hashes here.

File details

Details for the file restfy-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: restfy-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 27.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.13.2 Darwin/21.6.0

File hashes

Hashes for restfy-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a2ecccfbb43875f3974dd3bb6a6bc54b81af3c9fbf03a36b0e41920c9904d8a4
MD5 1053e608d91c612cc2f428f0330ec029
BLAKE2b-256 401de2a4def694e888b8c530e9a01c92c448e113d6811f06d21143f120cfe523

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page