Extensions: what a host application adds¶
cartolex’s app runs on its own. A host application — an institution’s
portal, a lab’s service — adds pages, routes, build stages and more by passing
cartolex.app.Extension objects to the app, and to the command line
when it ships its own:
from cartolex.app import AppSettings, create_app
from cartolex.cli import main
app = create_app(AppSettings(project=folder), extensions=[my_extension]) # an ASGI app
main(extensions=[my_extension]) # its own `cartolex`
cartolex never looks for extensions by itself: no entry points, no folder scanned, no environment variable. What is not passed does not exist.
An extension is a plain frozen dataclass; every field but id is optional.
create_app checks the extensions it is given, alone and together, and
refuses what does not fit with ExtensionError (a
ValueError) naming the extension and the field.
The fields¶
field |
what it adds |
|---|---|
|
the extension’s name: lower-case letters, digits, |
|
FastAPI |
|
a folder served at |
|
ES modules in |
|
override catalogues per interface locale ( |
|
a |
|
|
|
|
|
changes to cartolex’s stage declarations, by stage id: any |
|
the corpus slots ( |
|
|
|
the projected sets ( |
|
|
|
flags the interface reads in the manifest ( |
|
Starlette |
|
the name of the app’s own folder on the computer (default |
|
a folder of prompt templates replacing the packaged ones for every stage (the AI clean-up’s). One extension at most sets it |
|
function words added or removed on top of every project’s own ( |
|
|
|
|
|
|
The host’s prompt folder and function words are part of the host’s code, like cartolex’s packaged lists: changing them does not by itself make a result out of date, and the stages that read them are forced after such a change (The build).
Stages of a host’s own¶
A build stage writes its results into derived/<stage id>/, and the project
format lists the stages a project may hold (Derived results, caches, outputs and logs); a stage
id outside that list is refused by the format’s models. An extension can
therefore replace or change cartolex’s stages, not add stages of its own.
Letting hosts declare stages (with ids of their own, such as
ext.<extension>.<name>, recorded like any other) is a change of the format,
left for a decision on the format.
An example¶
A host that adds a « reports » page with its own route, a folder slot for the reports it receives, its name and accent, and a default for the top level of the theme tree:
from pathlib import Path
from fastapi import APIRouter, Depends
from cartolex.app import Branding, Extension, NavEntry, StatusArea
from cartolex.app.deps import ProjectContext, project_context
from cartolex.project.models import Slot
router = APIRouter()
@router.get("/summary")
def summary(ctx: ProjectContext = Depends(project_context)) -> dict:
return {"project": ctx.project.config.name}
reports = Extension(
id="reports",
routers=(router,), # GET /api/ext/reports/summary
static_dir=Path(__file__).with_name("static"), # /static/ext/reports/…
modules=("index.js",), # export function register(api) {…}
i18n={"en": {"nav.reports": "Reports"}, "fr": {"nav.reports": "Rapports"}},
branding=Branding(name="Example portal", logo="logo.svg", accent="#2b47a8"),
nav=(NavEntry("reports", "nav.reports", "/reports", "pages/reports.js", order=70),),
stage_patches={"themes.group": {"defaults": {"top_groups": 12}}},
corpus_slots=(Slot(id="reports", kind="folder", fit=True, trajectory=False),),
identity_provider=lambda new: {"domain_description": f"Maintained by the portal: {new.name}"},
status_keys=(StatusArea("reports", "area.reports", stages=("map.layout",)),),
capabilities={"reports": True},
settings_dir_name="example-portal",
)
tests/_app_extension.py is a generic extension of this kind (a page, a slot,
a stage declaration); the tests run the app without it and with it.