The limitless Python web framework
Expanse gives you the tools to build high-performance, scalable web applications with ease. Start building in minutes, keep using it for years.
Easy route definition with a touch of magic
Whether you need API endpoints or more standard web pages, Expanse got you covered. Use plain functions to handle your requests — or go further with controllers — and leverage the powerful dependency injection mechanism at the heart of Expanse to make even complex requirements a breeze.
Learn more@group("docs", prefix="/docs")
class DocumentationController:
def __init__(self, docs: Documentation) -> None:
# A Documentation instance will automatically
# be injected when instantiating the controller
@get("/{page}", name="page")
def show_page(
self, request: Request, page: str
) -> Response:
# The current request will be injected automatically
from expanse.contracts.routing.registrar import Registrar
from app.http.controllers.documentation import DocumentationController
def routes(router: Registrar) -> None:
router.controller(DocumentationController)
{% for user in users %}
<a href="{{ route("users.show", {"user_id", user.id} }}">
User {{ user.name }}
</a>
{% endfor %}
from expanse.view.view_factory import ViewFactory
def list_users(view: ViewFactory):
users = ...
return view.make("users.list", {"users": users})
from expanse.common.http.form import Form
from expanse.http.response import Response
from app.http.request.models.forms.article import ArticleModel
def create_article(article: Form[ArticleModel]) -> Response:
if form.is_submitted() and form.is_valid():
# Save article
...
from pydantic import BaseModel
class ArticleModel(BaseModel):
title: str
content: str
Powerful database management made easy
Expanse relies on SQLAlchemy to provide a powerful, yet intuitive, database experience. Its integration is seamless: Raw queries, ORM and migrations, they are all readily available, so that you can focus on what matters.
Learn morefrom typing import Annotated
from expanse.database.connection import Connection
from expanse.routing.helpers import get
from expanse.view.view import View
@get("/articles/{article_id}")
def show_article(
connection: Connection,
analytics: Annotated[Connection, "analytics"],
article_id: int,
) -> View:
article = connection.execute(
"SELECT * FROM articles WHERE id = :id",
{"id": article_id},
)
analytics.execute(
"INSERT INTO article_views (article_id) VALUES (:id)",
{"id": article_id},
)
from expanse.routing.router import Registrar
from app.http.controllers.articles import show_article
def routes(router: Registrar) -> None:
router.handler(show_article)
from expanse.database.orm import column
from sqlalchemy.orm import Mapped
from app.models.model import Model, primary_key
class User(Model):
__tablename__: str = "users"
id: Mapped[primary_key] = column()
first_name: Mapped[str] = column()
last_name: Mapped[str | None] = column(default=None)
email: Mapped[str] = column()
from typing import Annotated
from expanse.database.orm import column
from expanse.database.orm.model import Model
from sqlalchemy import BigInteger, Identity
from sqlalchemy.dialects import sqlite
# Declared once, reused by every model that needs it.
primary_key = Annotated[
int,
column(
BigInteger().with_variant(sqlite.INTEGER(), "sqlite"),
Identity(always=True),
primary_key=True,
),
]
from collections.abc import Sequence
from typing import Annotated
from expanse.database.session import Session
from expanse.routing.helpers import get
from sqlalchemy import select
from app.models.user import User
from app.schemas.user import UserData
@get("/users")
def list_users(session: Session) -> Sequence[Annotated[User, UserData]]:
return session.scalars(select(User)).all()
$
./beam make migration "Create the posts table" --auto
- Generating
database/migrations/versions/2026_08_29_143512_e776feeeb86d_create_the_posts_table.py... Done
$
./beam db migrate
-
Applying migration
a4f1c9d02b73
(Create the users table)
-
Applying migration
7c5b18ee4a90
(Add an index on emails)
-
Applying migration
3d0ba62f14c8
(Create the tags table)
-
Applying migration
e776feeeb86d
(Create the posts table)
"""
Create the posts table
Revision ID: e776feeeb86d
Revises:
Create Date: 2026-08-29 14:35:12.933029
"""
from alembic import op
import sqlalchemy as sa
revision: str = "e776feeeb86d"
down_revision: str | None = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"posts",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("content", sa.Text(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###
def downgrade() -> None:
op.drop_table("posts")
Move slow work
off the request.
Improve the responsiveness of your application by deferring heavy tasks. Expanse has a comprehensive jobs and queue system, backed by a standalone message bus: delays, retries and failure tracking are supported out of the box.
One dispatch, five stops
From dispatch to processing, your messages are handled reliably by the framework at each step. Easily switch transports when needed, and add your own middleware to extend the capabilities of the message bus.
Dispatch jobs and move on
Background jobs are just messages at heart, with syntactic sugar on top to make them
easier to define and dispatch. Create a job class, implement the
execute()
method. It gets the same dependency injection as your route handlers, so ask for what
you need and let a worker do the rest.
Batteries included, and durable.
Encryption, caching, API documentation and more: everything you need to build great applications is readily available and just a few environment variables away.
Native encryption
Protect sensitive information with a secure encryption component, using proven standards.
Multi-tier caching
Speed up your application by leveraging a robust cache system, with an in-memory first tier for that extra boost in performance.
OpenAPI documentation
Focus on writing your API endpoints, and let Expanse generate the OpenAPI documentation for you, by analyzing your code and extracting the relevant information.
Unified storage
One API, multiple storage backends. Swap backends with an environment variable without touching your code.