Additives

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:

structure text
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:

additives/pulse/manifest.json json
{
  "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):

additives/pulse/__init__.py python
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:

additives/pulse/app/index.py python
async def handle_request():
    from .. import additive
    return await additive.render("index.html")
additives/pulse/app/__init__.py python
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:

additives/pulse/templates/index.html html
{% 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:

app_configs/app.ini ini
[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.