The limitless Python web framework
Expanse gives you the tools to build high-performance, scalable web applications with ease. Start building in minutes, stay 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 morefrom expanse.http.response import Response
from expanse.routing.router import Router
from app.documentation import Documentation
@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)
Dependency injection
At the heart of Expanse is a powerful — yet intuitive — dependency injection mechanism that orchestrates every service that your application might need.
Type-hint any dependencies in your route functions or controllers, and they will be automatically injected for you.
In most cases you will be able to benefit from dependency injection for your own classes without doing anything.
{% 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})
Composable views
Easily render and compose views. Expanse is powered by Jinja meaning that you have natively access to all the features it provides.
Expanse provides additional features and helpers to make common actions more intuitive. And if it's not enough you can easily add your own, if needed.
Intuitive validation
Need to validate a form, a JSON payload or query parameters? Just specify a validation model and let Expanse handle the validation and report the errors
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
from expanse.common.http.json import JSON
from app.http.request.models.json.article import ArticleModel
def create_article(article_data: JSON[ArticleModel]):
# `article_data` is a fully validated instance of ArticleModel
article = ArticleModel(**article_data.model_dump())
from pydantic import BaseModel
class ArticleModel(BaseModel):
title: str
content: str
from expanse.http.query import Query
from app.http.request.models.queries.article import ListArticleModel
def list_articles(session: Session, q: Query[ListArticleModel]):
# `q` is a fully validated instance of ArticleModel
articles = session.scalars(
select(Article).where(Article.category == q.category)
).all()
from pydantic import BaseModel
class ListArticleModel(BaseModel):
category: str
before: datetime
POST https://example.com/articles HTTP/1.1 Content-Type: application/json {"content": "Content"}
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "code": "validation_error", "detail": [ { "loc": ["title"], "message": "Field required", "type": "missing", } ] }
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 list_articles(
connection: Connection,
analytics_connection: Annotated[Connection, "analytics"],
article_id: int,
) -> View:
article = connection.execute("SELECT * FROM articles WHERE id = :id", {"id": article_id})
analytics_connection.execute("INSERT INTO article_views (article_id) VALUES (:id)", {"id": article_id})
from expanse.routing.router import Registrar
from app.http.controllers.articles import list_articles
def routes(router: Registrar) -> None:
router.handler(list_articles)
Automatic connection management
Connecting to and using a database should be easy. Expanse manages your database connections automatically, so you can focus on retrieving and storing data.
Multiple databases support
You can configure as many databases as you want. Need to read from an SQLite database and write to a PostgreSQL one? No problem.
from expanse.database.orm.model import Model
primary_key = Annotated[
int,
column(
BigInteger().with_variant(sqlite.INTEGER(), "sqlite"),
Identity(always=True),
primary_key=True,
),
]
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 collections.abc import Sequence
from expanse.database.session import Session
@get("/users")
def list_users(session: Session) -> Sequence[Annotated[User, UserData]]:
users = session.scalars(select(User)).all()
return users
Intuitive models
Thanks to the model-as-dataclasses approach, you models are lean and easy to understand. Reuse type definition for even simpler models.
Built-in serialization
Serializing your models is also a breeze: define a serialization schema, annotate your return type with it and let Expanse do the rest.
"""
from alembic import op
import sqlalchemy as sa
def upgrade() -> None:
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"),
)
def downgrade() -> None:
op.drop_table("posts")
$ ./beam make migration create_users_table
$ ./beam db migrate
What's inside Expanse?
Native encryption
Keep your data safe with built-in encryption using proven standards.
Middleware
Intuitive middleware with dependency injection support.
Exception management
Easily spot errors with customizable reporting and a beautiful error page.