The limitless Python web framework
Expanse
Routing
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 moreAt 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.
class DocumentationController:
def __init__(self, docs: Documentation) -> None:
# A Documentation instance will automatically
# be injected when instantiating the controller
def show_page(
self, request: Request, page: str
) -> Response:
# The current request will be injected automatically
router.get(
"/docs/{page}",
DocumentationController.show_page,
name="docs.page"
)
Easily render and compose views. Expanse is powered by Jinja meaning that you have natively access to all 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.
def list_users(view: ViewFactory):
users = ...
return view.make("users.list", {"users": users})
{% for user in users %}
<a href="{{ route("users.show", user_id=user.id) }}">
User {{ user.name }}
</a>
{% endfor %}
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 pydantic import BaseModel
from expanse.http.form import Form
class ArticleModel(BaseModel):
title: str
content: str
def create_article(article: Form[ArticleModel]):
if form.is_submitted() and form.is_valid():
# Save article
...
from pydantic import BaseModel
from expanse.http.json import JSON
class ArticleModel(BaseModel):
title: str
content: str
def create_article(article: JSON[ArticleModel]):
# `article` is a fully validated instance of ArticleModel
...
from datetime import datetime
from pydantic import BaseModel
from expanse.http.query import Query
class QueryModel(BaseModel):
category: str
before: datetime
def list_articles(q: Query[QueryModel]):
# `q` is a fully validated instance of QueryModel
...
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",
}
]
}
Data access
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 moreAutomatic 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.
def list_users(connection: Connection, view: ViewFactory):
users = connection.execute(
"SELECT * FROM users WHERE is_active = :active",
{"active": True}
)
return view.make("users.list", {"users": users})
def list_users_from_sqlite(
connection: Annotated[Connection, "sqlite"]
):
...
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.
def list_users(connection: Connection, view: ViewFactory):
users = connection.execute(
"SELECT * FROM users WHERE is_active = :active",
{"active": True}
)
return view.make("users.list", {"users": users})
def list_users_from_sqlite(
connection: Annotated[Connection, "sqlite"]
):
...
Expanse relies on Alembic to manage database migrations and integrate it seamlessly with the rest of your application. Expanse uses sensible defaults that requires little to no additional configuration.
And you can even let Expanse automatically detect changes to your models and generate the necessary migrations for you.
# Automatically detect changes
./beam make migration "Add users table" --auto
# Apply any pending migrations
./beam db migrate
# Rollback the last migration
./beam db rollback