Albedo Framework
Albedo is a lightweight, folder-based routing framework built on top of FastAPI. It combines the speed and dependency injection of FastAPI with the developer experience of modern meta-frameworks such as Next.js and Nuxt. Albedo is designed strictly for server-side rendered (SSR) applications using Jinja2 templates.
🚀 Quick Start
Albedo comes with a powerful CLI to handle the boilerplate involved in setting up a FastAPI project with SQLAlchemy and Alembic.
1. Scaffold a new project
albedo init my_project
This automatically generates your folder structure, database.py, models, and wires up Alembic migrations.
2. Start the development server
cd my_project
albedo dev
This starts Uvicorn with hot reloading enabled for both .py files and .html Jinja templates.
📂 Folder-Based Routing
Albedo translates your file system directly into FastAPI routes. All routes live inside the app/pages/ directory.
| File Path | URL Route | Description |
|---|---|---|
app/pages/page.py |
/ |
The root index page. |
app/pages/about/page.py |
/about |
Standard static route. |
app/pages/users/[id]/page.py |
/users/{id} |
Dynamic route. Captures id as a variable. |
app/pages/files/[...slug]/page.py |
/files/{slug:path} |
Catch-all route. |
🧠 Loaders & Actions
Instead of manually registering router.get() and router.post(), Albedo looks for specific functions inside your page.py files.
loader() for GET Requests
The loader fetches data for your page. Whatever dictionary you return is automatically passed to the colocated page.html Jinja template.
# app/pages/users/[id]/page.py
# Albedo automatically maps the folder name [id] to this parameter
def loader(id: int):
return {
"title": "User Profile",
"user_id": id
}
action() for POST Requests
The action handles form submissions and fully supports FastAPI's native Form() injection.
- Return a
RedirectResponseon success. - Return a
dicton failure. Albedo automatically re-renders the page with your error messages.
# app/pages/settings/page.py
from typing import Annotated
from fastapi import Form
from fastapi.responses import RedirectResponse
def loader():
return {"error": None}
def action(username: Annotated[str, Form()]):
if username == "admin":
# Fails: re-renders the page with the error
return {
"error": "Username taken!",
"username": username
}
# Succeeds: redirects the user
return RedirectResponse(url="/", status_code=303)
✨ Magic Database Injection
Albedo hooks directly into FastAPI's dependency injection system.
If your loader or action requests a parameter named db, Albedo automatically injects your SQLAlchemy Session through Depends(get_db).
# app/pages/dashboard/page.py
from sqlalchemy.orm import Session
from app.models.user import User
# The db session is automatically injected
def loader(db: Session):
users = db.query(User).all()
return {"users": users}
🎨 Auto-Resolving Layouts
Say goodbye to manually writing:
{% extends "base.html" %}
in every template.
If you place a _layout.html file in a directory, Albedo automatically wraps every page.html in that folder and its subfolders.
app/pages/_layout.html
<html>
<body>
<nav>My App Navbar</nav>
<!-- Albedo injects the specific page.html content here -->
{{ content | safe }}
</body>
</html>
app/pages/about/page.html
<!-- No HTML boilerplate needed here! -->
<h1>About Us</h1>
<p>This is injected into the layout automatically.</p>
Opting Out of a Layout
If a specific folder (like app/pages/auth/ for a login page) should not use the main layout, you can easily intercept the layout resolution.
Simply create a new _layout.html inside that specific folder and pass the content through as raw HTML. Because Albedo stops at the first layout it finds while walking up the directory tree, this prevents the root layout from applying.
app/pages/auth/_layout.html
<!-- Intercepts the main layout and renders the page as a blank slate -->
{{ content | safe }}
🛡️ Folder-Level Middleware (Guards)
Protecting an entire section of your application is as easy as dropping a _guard.py file into a directory.
Albedo executes guards from the top-down before any loader runs. Guards are standard FastAPI dependencies, meaning they also support auto-injection for request and db.
To redirect an unauthorized user, simply raise an HTTPException with a 303 status code.
app/pages/admin/_guard.py
from fastapi import Request, HTTPException
from sqlalchemy.orm import Session
def guard(request: Request, db: Session):
auth_token = request.cookies.get("session_token")
# If the user is missing a token, bounce them to login
if not auth_token:
raise HTTPException(status_code=303, headers={"Location": "/login"})
🖼️ Static Assets
Any file placed inside the root public/ directory is automatically served at the /static URL path.
Linking to a stylesheet:
<link rel="stylesheet" href="/static/styles.css">
Note: The
albedo devserver automatically reloads when changes are made to CSS files in this folder.
🚨 Custom Error Pages
Albedo intercepts HTTP errors and automatically renders custom Jinja templates, wrapping them seamlessly in your existing _layout.html.
To customize an error page, simply create an HTML file matching the HTTP status code directly inside your app/pages/ directory:
app/pages/404.html(Not Found)app/pages/500.html(Internal Server Error)
🔒 Security & Environment
Albedo includes modern security and environment management out of the box so you can focus on building your application safely.
Automatic CSRF Protection
Albedo natively implements the Double Submit Cookie pattern to protect your application from Cross-Site Request Forgery (CSRF).
On every GET request, Albedo automatically generates a secure token and passes it to your Jinja template context as {{ csrf_token }}. To protect your forms, simply add a hidden input field:
<form method="POST" action="/settings">
<!-- Albedo will automatically validate this on submission -->
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label for="username">Username:</label>
<input type="text" name="username">
<button type="submit">Save</button>
</form>
If a POST request is submitted without this token, or if the token doesn't match the user's secure cookie, Albedo will intercept the request and return a 403 Forbidden error before your action() logic ever executes.
Auto-Loading Environment Variables
No need to manually export secrets in your terminal. Albedo automatically scaffolds .env and .env.example files when you initialize a project.
Because Albedo injects python-dotenv at the very top of your main.py, you can safely use standard Python environment variables anywhere in your app:
import os
# Safely loaded from your .env file
API_KEY = os.environ.get("EXTERNAL_API_KEY")
Release files for albedo-framework 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| albedo_framework-0.1.0.tar.gz | 665.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| albedo_framework-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 676.5 kB
Release files / albedo_framework-0.1.0.tar.gz
| Download URL | albedo_framework-0.1.0.tar.gz |
|---|---|
| Size | 665.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a48c945814a49ca46d154189dbc11ebdd09d910868e292ad5e71b7789a967bd6
|
|
BLAKE2b-256 checksum How to use checksums |
d95adb111a931efe56d0e6c247d90451564181bfae8429eea808b6e7f2c6f607
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.3","id":"zena","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / albedo_framework-0.1.0-py3-none-any.whl
| Download URL | albedo_framework-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0aaaedadd82c6db3c38cf69ea008aca5ed35a49e0a4ec8d155398b379699e91
|
|
BLAKE2b-256 checksum How to use checksums |
f7afccdb4630390a7a17ea44a47887d9eaa98fc10da087eff52bd514b9a50879
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.3","id":"zena","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|