Skip to main content

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 RedirectResponse on success.
  • Return a dict on 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 dev server 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for albedo-framework 0.1.1
File Size Uploaded
albedo_framework-0.1.1.tar.gz 665.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for albedo-framework 0.1.1
File Interpreter ABI Platform
albedo_framework-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 676.5 kB

Release files / albedo_framework-0.1.1.tar.gz

Download URL albedo_framework-0.1.1.tar.gz
Size 665.5 kB
Tags Source
SHA-256 checksum
How to use checksums
35f717f46d90cbd91b58cf78113e95471b856a6ff1a5fbca0a7e4711da1c8640
BLAKE2b-256 checksum
How to use checksums
10a448a72a372c115800afdf16024c3d385a94dc93f98a256b851b959580cf8d
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.1-py3-none-any.whl

Download URL albedo_framework-0.1.1-py3-none-any.whl
Size 11.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
982490de2f5f31a48a2cd73ed5ec6d30f2787447424bf5d1cb3baa7e44634b27
BLAKE2b-256 checksum
How to use checksums
1d1151e1d8176d8bfd79a3a263096354f81de83c9bd73ac0f8e634c52717495f
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 history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page