Metadata-Version: 2.5
Name: albedo-framework
Version: 0.1.0
Summary: A lightweight, folder-based routing framework for FastAPI.
Author-email: Dale Siatong <daledev07@gmail.com>
Requires-Python: >=3.11
Requires-Dist: alembic>=1.19.1
Requires-Dist: fastapi>=0.141.1
Requires-Dist: jinja2>=3.1.6
Requires-Dist: python-dotenv>=1.2.3
Requires-Dist: python-multipart>=0.0.32
Requires-Dist: questionary>=2.1.1
Requires-Dist: typer>=0.27.2
Requires-Dist: uvicorn>=0.52.4
Description-Content-Type: text/markdown

# 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

```bash
albedo init my_project
```

This automatically generates your folder structure, `database.py`, models, and wires up Alembic migrations.

### 2. Start the development server

```bash
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.

```python
# 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.

```python
# 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)`.

```python
# 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:

```django
{% 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
<html>
    <body>
        <nav>My App Navbar</nav>

        <!-- Albedo injects the specific page.html content here -->
        {{ content | safe }}
    </body>
</html>
```

**`app/pages/about/page.html`**

```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`**

```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`**

```python
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:

```html
<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:

```html
<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:

```python
import os

# Safely loaded from your .env file
API_KEY = os.environ.get("EXTERNAL_API_KEY")
```