Skip to main content

Reliqua

A simple, efficient, and intuitive Python REST API framework built on Falcon and Gunicorn. Define resources with Sphinx-style docstrings and Reliqua handles routing, parameter validation, type coercion, and OpenAPI 3.1 documentation automatically.

Features

  • Automatic route discovery — Resource modules are auto-loaded from a directory
  • OpenAPI 3.1 generation — API docs generated from docstrings at startup
  • Built-in Swagger UI — Interactive API explorer served at /docs
  • Parameter validation & type coercion — Declared parameter types are automatically enforced
  • Pluggable authentication & authorization — Basic, Cookie, Header, Query, and Bearer auth with role-based access control
  • CORS support — Enabled by default
  • Database helpers — MySQL, PostgreSQL, and SQLite via Peewee ORM
  • Custom media handlers — JSON (with datetime support), YAML, and plain text

Installation

pip install reliqua

Dependencies

  • falcon>=3.0.0
  • falcon-cors
  • gunicorn>=19.6.0
  • pyyaml

Optional for database support: peewee

Quick Start

1. Create a Resource

Resources are classes that subclass Resource. Routes, parameters, and responses are all defined via class attributes and docstrings.

from reliqua.resources.base import Resource
from reliqua.exceptions import HTTPNotFound


users = [
    {"username": "ted", "email": "ted@nowhere.com"},
    {"username": "bob", "email": "bob@nowhere.com"},
]

USER = {
    "type": "object",
    "properties": {
        "username": {"type": "string", "examples": ["billy"]}
    },
    "required": ["username"],
}

USERS = {"type": "array", "items": {"$ref": "#/components/schemas/user"}}


class User(Resource):
    """User resource."""

    __routes__ = {
        "/users/{id}": {"suffix": "by_id"},
    }

    __tags__ = ["users"]
    __auth__ = {"GET": ["admin"]}

    user = USER

    def on_get_by_id(self, req, resp, id=None):
        """
        Return a user.

        :param str id:         [in=path required] User ID
        :response 200 user:    User was retrieved
        :response 404:         User not found
        :return json:
        """
        try:
            resp.media = users[int(id)]
        except IndexError as exc:
            raise HTTPNotFound("User not found") from exc


class Users(Resource):
    """Users resource."""

    __routes__ = {"/users": {}}
    __tags__ = ["users"]

    users = USERS

    def on_get(self, req, resp):
        """
        Return users.

        :param str username:    [in=query] Username
        :param str email:       [in=query default=ted@nowhere.com] Email

        :response 200 users:    Users were retrieved
        :response 401:          Invalid Authorization
        :return json:
        """
        resp.media = {"results": users}

    def on_post(self, req, resp):
        """
        Create a new user.

        :param str username:    [in=body required] Username
        :param str email:       [in=body required] Email

        :accepts json:
        :return json:
        """
        users.append(req.params)
        resp.media = len(users) - 1

2. Create the Application

from reliqua import Application

app = Application(
    resource_path="/path/to/resources",
    info={
        "title": "My API",
        "version": "1.0.0",
        "description": "My awesome API",
    },
    gunicorn={
        "bind": "127.0.0.1:8000",
        "workers": 2,
    },
)
app.run()

The OpenAPI UI will be available at:

http://127.0.0.1:8000/docs

The OpenAPI JSON spec will be available at:

http://127.0.0.1:8000/openapi/openapi.json

Configuration

The Application constructor accepts the following option groups:

info — API Metadata

Key Default Description
title "Application Title" API title shown in docs
version "1.0.0" API version
description "" API description
license "" License name
license_url "" License URL
contact_name "" Contact name

openapi — Documentation Options

Key Default Description
ui_url None Public URL for the API (e.g., behind a proxy). Defaults to http://{bind}
highlight True Enable syntax highlighting in Swagger UI
sort "alpha" Sort order for operations
path "/openapi" Path for OpenAPI spec endpoint
docs "/docs" Path for Swagger UI
servers [] Additional server entries for OpenAPI spec

gunicorn — Server Options

Key Default Description
bind "127.0.0.1:8000" Address and port to listen on
workers 1 Number of worker processes
worker_class "gthread" Gunicorn worker class
timeout 30 Worker timeout in seconds
keepalive 2 Keep-alive timeout
loglevel "critical" Log level
accesslog "-" Access log target
errorlog "-" Error log target

Other Parameters

Parameter Default Description
resource_path "" Directory containing resource modules
middleware [] List of middleware instances
config None App config dict accessible on resources as self.app_config
resource_attributes {} Key/value pairs set as attributes on every resource

Resources

Routes

Routes are defined as a dictionary on the __routes__ class attribute. Keys are URL patterns and values are option dicts.

class MyResource(Resource):
    __routes__ = {
        "/items": {},
        "/items/{id}": {"suffix": "by_id"},
    }

    def on_get(self, req, resp):
        """Handle GET /items."""

    def on_get_by_id(self, req, resp, id=None):
        """Handle GET /items/{id}."""

When a resource has multiple routes, the suffix option distinguishes handler methods (e.g., on_get_by_id, on_delete_by_id).

Docstring Parameters

Parameters are declared using Sphinx-style docstrings:

:param <type> <name>:   [<options>] <description>

Supported types: str, int, float, bool, list, list[int], list[str], list[dict], object, dict, json

Options (inside []):

Option Description
in=query|path|body|form Parameter location (default: query)
required Mark as required (path params are always required)
default=<value> Default value
enum=<attr> Resource attribute containing allowed values
min=<n> Minimum value
max=<n> Maximum value

Responses

:response <code> [<schema>]:   <description>

The optional <schema> name references a class attribute containing an OpenAPI schema dict. The schema is automatically added to the OpenAPI components/schemas section.

Content Types

:return [json, yaml, xml]:      Response content types
:accepts [json, xml]:           Accepted request content types

Supported aliases: json, yaml, xml, html, text, binary, gzip, form, jpeg, png, gif

Resource Attributes

Attribute Description
__routes__ Dict mapping URL patterns to route options
__tags__ List of OpenAPI tags (defaults to class name)
__auth__ Dict mapping HTTP methods to allowed roles
__components__ Dict of OpenAPI component schemas
no_auth Set to True to bypass authentication

Helper Methods

The Resource base class provides get_params(req, keys=None, exclude=None) to extract request parameters.

Authentication & Authorization

Reliqua separates authentication (identity verification) from authorization (access control).

Authentication

from reliqua.auth import (
    AuthenticationContext,
    AuthMiddleware,
    BasicAuthentication,
    CookieAuthentication,
    HeaderAuthentication,
    QueryAuthentication,
    MultiAuthentication,
)


def validate_user(username, password):
    if username == "admin" and password == "secret":
        return AuthenticationContext(user="admin", role="admin")
    return None


def validate_api_key(api_key):
    if api_key == "abc123":
        return AuthenticationContext(name="api_user", role="admin")
    return None


# HTTP Basic auth
basic_auth = BasicAuthentication(validation=validate_user)

# API key from cookie
cookie_auth = CookieAuthentication("api_key", validation=validate_api_key)

# API key from header
header_auth = HeaderAuthentication("X-API-Key", validation=validate_api_key)

# API key from query parameter
query_auth = QueryAuthentication("api_key", validation=validate_api_key)

# Try multiple authenticators in order
multi_auth = MultiAuthentication([basic_auth, cookie_auth])

Validation callbacks receive credentials and must return an AuthenticationContext on success or a falsy value on failure.

Authorization

Four access control strategies are available:

AccessResource — Per-resource authorization via __auth__ class attributes:

from reliqua.auth import AccessResource, AuthMiddleware

control = AccessResource(default_mode="deny")
auth = AuthMiddleware([basic_auth], control=control)

class SecureResource(Resource):
    __auth__ = {
        "GET": ["admin", "user"],
        "POST": ["admin"],
    }

AccessMap — A centralized route-to-role mapping:

from reliqua.auth import AccessMap

access_map = {
    "/users": {"GET": ["admin", "user"], "POST": ["admin"]},
    "/public": {"*": ["*"]},  # wildcard: any role, any method
}
control = AccessMap(access_map)

AccessList — Simple allow/deny lists of routes and methods:

from reliqua.auth import AccessList

control = AccessList(
    routes=["/secret"],
    methods=["DELETE"],
    default_mode="allow",  # everything open except listed routes/methods
)

AccessCallback — Delegate to custom functions:

from reliqua.auth import AccessCallback

control = AccessCallback(
    authenticate_callback=my_auth_check,
    authorized_callback=my_role_check,
)

Applying Auth Middleware

auth_middleware = AuthMiddleware([basic_auth, cookie_auth], control=control)

app = Application(
    resource_path="/path/to/resources",
    middleware=[auth_middleware],
)

Resources with no_auth = True bypass authentication entirely. The authenticated context is available in handlers via req.context["authentication"].

Database Support

Database helpers use the Peewee ORM with a proxy pattern.

from reliqua.database import BaseModel, mysql_connect, DatabaseConnection
import peewee

# Connect to the database
db = mysql_connect("localhost", "mydb", "user", "password", 3306)

# Define a model
class UserModel(BaseModel):
    username = peewee.CharField()
    email = peewee.CharField()

# Add DatabaseConnection middleware to manage connections per request
app = Application(
    resource_path="/path/to/resources",
    middleware=[DatabaseConnection()],
)

Available connection helpers: mysql_connect(), psql_connect(), sqlite_connect()

Media Handlers

Built-in handlers are registered automatically:

Content Type Handler Notes
application/json JSONHandler Serializes datetime, date, and time objects
application/yaml YAMLHandler YAML via PyYAML
text/html TextHandler Plain text
text/plain TextHandler Plain text

Example Application

A full example application is included in the example/ directory demonstrating resources, authentication, form handling, and binary media streaming.

# Run the example
python -m example

# Custom bind address and port
python -m example --address 0.0.0.0 --port 9000

The Swagger UI will be available at http://127.0.0.1:8000/docs.

License

MIT

Metadata

Release files for reliqua 0.0.14

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reliqua 0.0.14
File Size Uploaded
reliqua-0.0.14.tar.gz 1.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for reliqua 0.0.14
File Interpreter ABI Platform
reliqua-0.0.14-py3-none-any.whl Python 3 none any Details

Total release size: 3.0 MB

Release files / reliqua-0.0.14.tar.gz

Download URL reliqua-0.0.14.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
a0898a303eed7757e959c7202d82f153b35d037e0f7db213862a202f5bd76f3c
BLAKE2b-256 checksum
How to use checksums
768e8ed50af922ace4e5524798a1ccd1b7d8e5b7616b4587ac1b026a20bde27b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.

Transparency log

Release files / reliqua-0.0.14-py3-none-any.whl

Download URL reliqua-0.0.14-py3-none-any.whl
Size 1.5 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0080cbf462ee057d59779784e2c2726c3b966d37d20a7aa452659c15d0fa9d16
BLAKE2b-256 checksum
How to use checksums
89e041f80fdd245e1880ab08d7e915f52929342f45ca60ebe299d2aaecd74c0a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.

Transparency log

Release history Release notifications | RSS feed

0.0.15

2 release files

This release

0.0.14 This release

2 release files

0.0.13

2 release files

0.0.11

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

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