Introduction
Okay, let us introduce you to Fluid Extensions. They are inspired by Flask extensions
and meant to extend the functionality of WebFluid. (Who would have guessed :O)
Extensions should not provide endpoints, serve files or frontend and other things like
that. Instead, they can hook into the framework CLI or provide bundled functionality
to improve the developers' experience.
The base extension
All extensions of WebFluid should be children of the frameworks FluidExtension.
This is the base extension that provides the default structure of Fluid Extensions. So let's
take a look at how to create your own extensions:
Creating your first extension
At first, we need to set up your extension as a PyProject. This is necessary because the
wf CLI uses the entry-points API to load your extensions' command line interface.
WebFluid uses Typer as its command line tooling. If you are not familiar with Typer, you can
read more about it here. We won't go into detail
about it.
Like we already said, we are building up on the current state of this documentation. So we are
assuming you do already have your project directory in place and continue working from inside it.
Setting up your PyProject
mkdir -p myext/src/extension
cd myext
[project]
name = "extension"
version = "1.0"
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["src"]
[project.entry-points."webfluid.extensions"]
myext = "extension.main:MyExtension"
This is a minimal example of what your PyProject could look like. There are two things to take note of:
-
We are using the entrypoint
"webfluid.extensions"to register your extension. The frameworks' CLI uses the name of your entrypoint to register your extensions' CLI as a sub-cli of itself. -
If you want to offer your own CLI, your extension must be
a child of the
FluidExtensionclass.
A minimal extension
Okay, let's start building your first extension. At first, we will just create a quick hello world example for your CLI:
from webfluid.extensions import FluidExtension
import typer
class MyExtension(FluidExtension):
_cli = typer.Typer(help="My first 'Hello World!' example.")
@staticmethod
@_cli.command()
def hello(name: str):
typer.secho(
f"Hello {name} :)",
fg=typer.colors.GREEN, bold=True
)
This is already it. Now you can install your extension as an editable package and test your first command. You can do this by running:
pip install -e .
wf myext hello "my friend"
Expanding your fluid
Now that you have created your first cli command, we can start expanding your fluid.
Therefore, you can override the FluidExtension.expand_fluid method. There
you get passed in the fluid instance, and since extensions are meant to be registered before
fluid.mix(), you can do whatever you want with it. This includes registering
lifecycle hooks, which we will talk about in the "Framework utility" chapter.
Anyway, more important is that you will have access to the fluids' config dictionary that is
parsed when your app initializes (like we explained in the previous chapter). So now we can
combine what we've learned so far and extend our current setup:
from webfluid.extensions import FluidExtension
from typing import TYPE_CHECKING, Optional
import typer
if TYPE_CHECKING:
from webfluid import Fluid
class MyExtension(FluidExtension):
_cli = typer.Typer(help="My first 'Hello World!' example.")
# Stick with the parents' signature so your IDE is happy
def __init__(self, fluid: Optional["Fluid"] = None, *_, **__):
self._foo = "default"
self._bar = "default"
super().__init__(fluid)
def expand_fluid(self, fluid: "Fluid", *_, **__):
self._foo = fluid.config.get("MYEXT_FOO", self._foo)
self._bar = fluid.config.get("MYEXT_BAR", self._bar)
fluid.jinja_env.globals["my_ext"] = self._my_util
def _my_util(self) -> str:
return f"Foo is {self._foo} but bar is {self._bar}!"
@staticmethod
@_cli.command()
def hello(name: str):
typer.secho(
f"Hello {name} :)",
fg=typer.colors.GREEN, bold=True
)
from webfluid import Fluid
from webfluid.core.config import register_config
from fastapi.responses import HTMLResponse
from extension.main import MyExtension
@register_config(10)
class MyConfig:
SESSION_COOKIE_SECURE = True
MYEXT_BAR = "crazy"
fluid = Fluid(__name__)
# FluidExtension.__init__(fluid) calls expand_fluid on itself
# automatically if fluid is not None.
my_ext = MyExtension(fluid)
@fluid.get("/", response_class=HTMLResponse)
async def home():
return await fluid.render(
"index.html",
title="Hello World!",
name="my friend"
)
@fluid.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
fluid.mix()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<script>
function healthCheck() {
const result = document.getElementById('result')
fetch('/health').then(res => {
if (res.ok) result.style.color = 'green'
else result.style.color = 'red'
return res.json()
}).then(data => {
result.innerText = JSON.stringify(data)
setTimeout(() => result.innerText = '', 2000)
})
}
</script>
</head>
<body>
<p>Hello <b>{{ name }}</b>!</p>
<button onclick="healthCheck()">Health Check</button>
<p id="result"></p>
<p>{{ my_ext() }}</p>
</body>
</html>
That's it for now. This is all you need to know about shipping your own extensions.
Continue reading
From here you can continue straight with Scheduling.