FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins.
It is based on top of fastAPI, uvicorn, typer, and pluggy.
FPS is an experimental project.
To better understand the motivations behind this project, please refer to the Jupyter server team compass.
The main purpose of FPS is to provide hooks to register endpoints, static mounts, CLI setups/teardowns, etc.
An application can then be composed by multiple plugins providing specific/specialized endpoints. Those can be registered using fps.hooks.register_router with a fastapi.APIRouter.
The most important parts will be to have a nice configuration system and also a logger working through multiprocesses, with homogeneous formatters to give devs/ops/users a smooth experience.
Few concepts are extensively used in FPS:
- a
hook, orhookimplementation, is a method tagged as implementing ahookspecification- a hook specification is the declaration of the hook
@pluggy.HookspecMarker(HookType.ROUTER.value) def router() -> APIRouter: pass
- hooks are automatically collected by
FPSusing Python'sentry_points, and ran at the right time[options.entry_points] fps_router = fps_helloworld_router = fps_helloworld.routes fps_config = fps_helloworld_config = fps_helloworld.config
- multiple
entry_points groups are defined (e.g.fps_router,fps_config, etc.)- a hook MUST be declared in its corresponding group to be collected
- in the previous example,
HookType.ROUTER.valueequalsfps_router, so therouterhook is declared in that group
fps.hooks.register_<hook-name>helpers are returning such hooksdef register_router(r: APIRouter): def router_callback() -> APIRouter: return r return pluggy.HookimplMarker(HookType.ROUTER.value)( function=router_callback, specname="router" )
- a hook specification is the declaration of the hook
- a
pluginis a Python module declared in aFPS'sentry_point- a plugin may contain zero or more hooks
- in the following
helloworldexample, the hookconfigis declared but not theplugin_nameone. Both are hooks of thefps_configgroup.from fps.config import PluginModel from fps.hooks import register_config class HelloConfig(PluginModel): random: bool = True c = register_config(HelloConfig)
- a
plugins packageis a Python package declaring one or more plugins
FPS now support configuration using toml format.
For now, the loading sequence of the configuration is: fps.toml < <plugin-name>.toml < <cli-passed-file> < <cli-arg>.
fps.toml and <cli-passed-file> files can contain configuration of any plugin, while <plugin-name>.toml file
will only be used for that specific plugin.
fps.toml and <plugin-name>.toml currently have to be in the current working directory. Support for loading from user home
directory or system-wide application directory will be soon implemented.
Note: the environment variable FPS_CONFIG_FILE is used to store cli-passed filename and make it available to subprocesses.
At this time the merging strategy between multiple config sources is pretty simple:
- dict values for higher precedence source win
- no appending/prepending on sequences
FPS has a testing module leveraging pytest fixtures and fastAPI dependencies override.