Skip to main content

A REST Framework for FastAPI

Project description

FastAPI-Restly

CI Python License Coverage

FastAPI-Restly logo

Build maintainable REST APIs on FastAPI, SQLAlchemy 2.0, and Pydantic v2 — with real class-based views.

Status: 0.6.1 — public beta release.

Restly is public after four years of internal use. The API is settling on the way to 1.0.0; expect small breaking changes in deeper extension points. Feedback is welcome.

pip install "fastapi-restly[standard]"

Docs: https://rjprins.github.io/fastapi-restly/ · Changelog · Contributing · Security · Examples

Why FastAPI-Restly?

Restly turns SQLAlchemy models into FastAPI resources without hiding FastAPI. Its class-based views are real Python classes: use inheritance, mixins, and method overrides to share behavior across resources.

  • Class-based views: group endpoints on Python classes with inheritance and method overrides.
  • REST endpoints in minutes: use View for custom resources, or AsyncRestView / RestView for generated CRUD.
  • Incremental adoption: use Restly per resource; drop to ordinary FastAPI when needed.
  • Class-level dependencies: declare shared dependencies once and read their values from self.
  • Explicit override points: change the route shell, request handler, or business verb.
  • Filtering, pagination, sorting: get schema-derived list parameters.
  • Field control: ReadOnly / WriteOnly markers, plus foreign-key validation through IDRef[...].
  • React Admin ready: AsyncReactAdminView speaks ra-data-simple-rest.
  • App utilities: SQLAlchemy engine/session setup, exception handlers, and test fixtures.

Quickstart

FastAPI-Restly turns a SQLAlchemy model into a class-based CRUD resource:

import fastapi_restly as fr
from fastapi import FastAPI
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

app = FastAPI()

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    email: Mapped[str]

@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User

That view exposes these HTTP routes:

GET    /users/       # list users, with filtering, sorting, and pagination
POST   /users/       # create a user
GET    /users/{id}   # read one user
PATCH  /users/{id}   # partially update one user
DELETE /users/{id}   # delete one user

Restly generates the Pydantic schemas automatically. For the full copy-paste app see Getting Started.

Installation

pip install "fastapi-restly[standard]" aiosqlite

The standard extra mirrors fastapi[standard] (the fastapi dev server toolchain). Restly is database-driver-agnostic, so install the async driver for your database alongside it — aiosqlite for SQLite (used in the examples), asyncpg/psycopg for PostgreSQL. Test tooling lives in a separate extra: pip install "fastapi-restly[testing]".

Main features

Manual schema definition

For custom validation, aliases, or stable public contracts, define an explicit read schema:

from datetime import datetime

class UserRead(fr.IDSchema):
    name: str
    email: str
    password: fr.WriteOnly[str]
    created_at: fr.ReadOnly[datetime]

@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    schema = UserRead
    # schema_create = UserCreate  # auto-generated from UserRead
    # schema_update = UserUpdate  # auto-generated from UserRead

Restly derives create and update schemas from UserRead by default. The UserCreate schema is created by omitting ReadOnly fields. The UserUpdate schema allows for partial updates by making all fields optional.

When you need full control over write payloads, declare them explicitly:

class UserCreate(fr.BaseSchema):
    name: str
    email: str

class UserUpdate(fr.BaseSchema):
    name: str | None = None
    email: str | None = None

@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    schema = UserRead
    schema_create = UserCreate
    schema_update = UserUpdate

Use auto-schema for prototypes and internal tools. Use an explicit schema for public contracts, aliases, and strict validation.

List endpoint query parameters

List endpoints expose a stable URL parameter dialect generated from the response schema:

GET /users/?name=John&age__gte=21
GET /users/?status=active,pending           # comma-separated → OR (IN)
GET /users/?status__ne=archived,deleted     # comma-separated → NOT IN
GET /users/?email__icontains=example
GET /users/?deleted_at__isnull=true
GET /users/?sort=-created_at,name
GET /users/?page=2&page_size=10

Parameter keys use the response schema's public names, including dotted relation paths. Aliases apply to each path segment: writer.authorName, not author.name.

Pagination is opt-in: omitting page_size returns every matching row. For public endpoints, set default_page_size and max_page_size on the view:

class UserView(fr.AsyncRestView):
    default_page_size = 25
    max_page_size = 200

See How-To: Filter, Sort, and Paginate Lists for the full operator surface, alias rules, and pagination guidance.

Read-only and write-only fields

IDSchema already provides a read-only id, so don't redeclare it unless you need to narrow the type.

class UserRead(fr.IDSchema):
    name: str
    email: str
    password: fr.WriteOnly[str]        # stripped by to_response_schema()
    created_at: fr.ReadOnly[datetime]  # excluded from schema_create / schema_update

Relationship handling

Validate relationships on create and update with fr.IDRef[...]. Restly passes either the foreign key (customer_id) or the related object (Customer) to SQLAlchemy, depending on the model constructor.

class Order(fr.IDBase):
    customer_id: Mapped[int] = mapped_column(ForeignKey("customer.id"))
    customer: Mapped[Customer] = relationship()

class OrderRead(fr.IDSchema):
    customer_id: fr.IDRef[Customer]
    customer: fr.ReadOnly[CustomerRead]

Custom endpoints

Add custom routes with FastAPI-style decorators.

  • @fr.get
  • @fr.post
  • @fr.put
  • @fr.patch
  • @fr.delete
  • @fr.route

They forward keyword arguments to FastAPI's route registration.

class UploadView(fr.AsyncRestView):
    prefix = "/uploads"
    model = Upload

    @fr.get(
        "/{id}/download",
        response_class=FileResponse,
        responses={200: {"content": {EXCEL_MIME_TYPE: {}}}},
    )
    async def download_excel(self, id: int):
        upload = await self.handle_get_one(id)
        return to_excel_response(upload)

React Admin integration

Use AsyncReactAdminView for a react-admin backend compatible with ra-data-simple-rest:

@fr.include_view(app)
class ProductView(fr.AsyncReactAdminView):
    prefix = "/products"
    model = Product
    schema = ProductRead

The view speaks the ra-data-simple-rest wire contract.

See React Admin Integration in the docs for CORS setup and customization.

Excluding built-in routes

@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    exclude_routes = (fr.ViewRoute.DELETE,)

Pagination metadata

@fr.include_view(app)
class UserView(fr.AsyncRestView):
    prefix = "/users"
    model = User
    include_pagination_metadata = True
    # Response: {"items": [...], "total": N, "page": 1, "page_size": 100, "total_pages": N, ...}

Testing

fastapi_restly.pytest_fixtures provides client and session fixtures with savepoint-based isolation. The testing extra auto-loads them as a pytest plugin.

Install the testing extra when consuming FastAPI-Restly as a package:

pip install "fastapi-restly[testing]"

Configure Restly for your test database in conftest.py.

RestlyTestClient asserts the expected status (200 for GET, 201 for POST, 204 for DELETE, ...) and includes the response body on failure:

# test_users.py
def test_create_and_fetch_user(restly_client):
    # Raises AssertionError if status != 201
    response = restly_client.post("/users/", json={"name": "John", "email": "john@example.com"})
    user_id = response.json()["id"]

    # Raises AssertionError if status != 200
    data = restly_client.get(f"/users/{user_id}").json()
    assert data["name"] == "John"

Pass assert_status_code=None to skip the assertion and inspect the response yourself.

Configuration

# Async SQLite
fr.configure(async_database_url="sqlite+aiosqlite:///app.db")

# Async PostgreSQL
fr.configure(async_database_url="postgresql+asyncpg://user:pass@localhost/db")

# Sync SQLite
fr.configure(database_url="sqlite:///app.db")

Restly has one public process-wide configuration. For per-view databases, read replicas, or custom sessions, use a normal FastAPI dependency on that view.

Documentation

Examples

Complete applications under example-projects/:

  • Shop — e-commerce API with products, orders, customers
  • Blog — minimal blog with a single Blog model
  • SaaS — multi-tenant project management API

Contributing

Pull requests and issue discussions welcome. See CONTRIBUTING.md for setup and tests. For security issues, see SECURITY.md.

License

MIT — see LICENSE.

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

fastapi_restly-0.6.1.tar.gz (73.1 kB view details)

Uploaded Source

Built Distribution

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

fastapi_restly-0.6.1-py3-none-any.whl (78.6 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_restly-0.6.1.tar.gz.

File metadata

  • Download URL: fastapi_restly-0.6.1.tar.gz
  • Upload date:
  • Size: 73.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastapi_restly-0.6.1.tar.gz
Algorithm Hash digest
SHA256 aefae0533180d3269f92396395136e17d169e286238bf5df20564d21d367baf3
MD5 d704877064fb2b968b892ffc250c84f3
BLAKE2b-256 63ee57bfc20eff1d3c5de6eaf9a86524b585ce48040a3b5dd3e2da4acdf542ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_restly-0.6.1.tar.gz:

Publisher: publish.yml on rjprins/fastapi-restly

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastapi_restly-0.6.1-py3-none-any.whl.

File metadata

  • Download URL: fastapi_restly-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 78.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastapi_restly-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ba8a74aa3b787d5ddc9c0b9f9a928204456cdd391f785e8cdbf6b1501fcacd97
MD5 83894f7d3b21a2d783bfb0c7f6714cfd
BLAKE2b-256 5b860a0c517b3a9c4eaf98383bc67ea5c8be55d4d82d838b7fbfdd1b5782a167

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_restly-0.6.1-py3-none-any.whl:

Publisher: publish.yml on rjprins/fastapi-restly

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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