Introduction
Extensions taught the framework new tricks. Additives are the other half of WebFluid's modularity: self-contained sub-apps with their own routes, templates, static files and even their own frontend. They are the unit you build features in — and the unit you ship, because a manifest makes them distributable.
What an Additive is
An Additive is a Python package that lives inside your project's additives
directory and exposes a single additive object. When your app starts, every
enabled Additive is mounted under its own url prefix, with its routes, templates
and static files wired in automatically. Think of it as a small WebFluid app nested inside
the main one.
A minimal Additive has a clear shape:
additives/
└── pulse/
├── __init__.py # exposes `additive: Additive`
├── manifest.json # id, version, type, frontend, ...
├── app/ # HTML routes
│ ├── __init__.py
│ └── index.py
└── templates/
└── index.html
The manifest
Every Additive is described by a manifest.json. Four fields are required —
id, version, type and frontend —
the rest is optional metadata. Our first Additive is a plain default with no frontend:
{
"id": "pulse",
"version": "1.0.0",
"type": "default",
"frontend": { "type": "none" },
"name": "Pulse"
}
The additive object
The package's __init__.py creates the Additive and registers its
routes. Routes are attached inside a before_enable hook, because the framework
includes the Additive's routers into your app the moment it is enabled. Every Additive ships
three routers: app for HTML, api for JSON (mounted under
/api) and ws for WebSockets (under /ws):
from webfluid import Additive
additive = Additive(__name__)
@additive.before_enable
def before_enable(_):
from .app import index
additive.app.get("/")(index)
The handler itself looks just like a main-app route, except it renders through the
Additive. additive.render resolves templates inside the Additive's own
namespace, so a plain "index.html" finds
additives/pulse/templates/index.html:
async def handle_request():
from .. import additive
return await additive.render("index.html")
from .index import handle_request as index
__all__ = ["index"]
And because Additives can render through the framework base just like your main app, the template feels completely at home:
{% extends "fluid_base.html" %}
{% block title %}{{ _('Pulse') }}{% endblock %}
{% block content %}
<h1>{{ _('A fresh liquid is flowing.') }}</h1>
{% endblock %}
Enabling an Additive
Additives are optioned twice: the WF_ADDITIVES feature has to be on, and each
Additive is then toggled by its id in the [additives] section
of your app config. Nothing is loaded unless it is explicitly switched on:
[general]
SECRET_KEY = supersecret
[features]
WF_PROCESSING = 1
WF_ADDITIVES = 1
[additives]
pulse = 1
Run the app and the Additive's id becomes its url prefix. Our pulse page now
lives at /pulse, its (future) API would sit at /pulse/api/... and
its sockets at /pulse/ws/....
Hand-rolling this folder is great for understanding the anatomy, but you will almost never do it manually. wf create additive scaffolds exactly this structure (and a frontend, and a requirements list) interactively. We'll get there in the CLI chapter.
That's a whole feature, isolated in its own package, mounted with one switch. Next we'll see how Additives can stand on the shoulders of others through base Additives.
Continue reading
From here you can continue straight with Base Additives.