Package: fastapi-wire
Install from pypiA pre-release has been published on pypi with the namefastapi-wire. To install it simply run:python-mpipinstallfastapi-wireInstall from sourceFirst clone the repository fromgithubthen install using one of the method below.Install using poetrypoetryinstallInstall using scriptpython./install.pyInstall manuallyCreate a virtual environment:python-mvenv.venvUpdate python package toolkit (within the virtual environment):python-mpipinstall-UpipsetuptoolswheelbuildInstall the project in editable mode (within the virtual environment) with all extras:python-mpipinstall-e.[dev,oidc,telemetry]Run the appEither use thewiremodule:python-mwireexamples/app.yaml-cexamples/config.jsonThe command line interface:wire--helpOr useuvicornto start the application:CONFIG_FILEPATH=examples/config.jsonuvicorn--factorydemo_app.spec:spec.create_appNote that server config won't be applied since uvicorn is started from command line and not within Python process in this case.It's also possible to start the application with hot-reloading:CONFIG_FILEPATH=examples/config.jsonuvicorn--factorydemo_app.spec:spec.create_app--reloadConfigure the appApplication can be configured using environment variables or file, or options when using the CLI:Note: Environment variables take precedence over variables declared in file. For example, assuming the above configuration is declared in a file namedconfig.json, when running:PORT=8000 CONFIG_FILE=./demo/config.json python -m demo_app, application will listen on port8000and not7777.Note: When usinguvicorn,HOSTandPORTare ignored and must be specified as command line arguments if required.Design choicesApplication is wrapped within aContainer:AnContaineris created from:Somesettings: settings are defined as pydantic models. When they are not provided directly, values are parsed from environment or file.Somehooks: hooks are async context managers which can inject arbitrary resources into application state. In this application, a hook is used to add anIssuerinstance to the application state. See documentation onAsynchronous Context Managersand@contextlib.asynccontextmanagerto easily create context managers from coroutine functions. You can see how it's used inthe hook used by the example application.Someproviders: providers are functions which must accept a single argument, an application container, and can add additional features to the application. They are executed before the FastAPI application is initialized, unlike hooks, which are started after application is initiliazed, but before it is started. In the repo example, providers are used for example to optionally enable prometheus metrics and opentelemetry traces. TheCORS Provideris surely the most simple provider.Somerouters: routers are objects holding a bunch of API endpoints together. Those endpoints can share a prefix and some OpenAPI metadata.In order to faciliate creation and declaration of application containers, theAppSpecclass can be used as a container factory.See usage insrc/demo-app/demo_app/spec.pyObjectivesDistributable: Application can be distributed as a python package.Configurable: The database used in the application must be configurable using either a file or an environment variable.Configurable: The server configuration (host, port, debug mode) must be configurable using either a file or an environment variable.User friendly: A command line script should provide a quick and simply way to configure and start the application.Debug friendly: Application settings should be exposed on a/debug/settingsendpoint when application is started on debug mode.Observable: Adding metrics or tracing capabilities to the application should be straighforward and transparent.Explicit: Router endpoints must not use global variables but instead explicitely declare dependencies to be injected (such as database client or settings). This enablesefficient sharing of resources and facilitate eventual refactoring in the future.Conposable: Including additional routers or features in the future should require minimal work.Arbitrary hooks with access to application container within their scope can be registered. Those hooks are guaranteed to be started and stopped in order, and even if an exception is encountered during a hook exit, all remaining hooks will be closed before an exception is raised. It minimize risk of resource leak within the application. Hooks can be seen as contexts just like in the illustration below:Arbitrary tasks can be started along the application. Tasks are similar to hooks, and are defined using a coroutine function which takes the application container as argument and can stay alive as long as application
is alive. Unlike hooks, tasks have a status and can be:stoppedstartedrestarted
It's also possible to fetch the task status to create healthcheck handlers for example.Arbitrary providers with access to application container within their scope can be registered. Those providers are executed once, before the application is created. They can be used to add optional features such as tracing or metrics.Objects provided by hooks or providers can be accessed through dependency injection in the API endpoints. Checkthis exampleto see dependency injection in practice.Below is an illustration of an hypothetic application lifecycle:Declarative applicationIt's possible to declare application using YAML, JSON or INI files. An example application could be:---# Application metadatameta:name:demo_apptitle:Demo Appdescription:A declarative FastAPI application 🎉package:wire# Custom settings modelsettings:demo_app.settings.AppSettings# Declare providers# A few providers are available to use directly# It's quite easy to add new providersproviders:-wire.providers.structured_logging_provider-wire.providers.prometheus_metrics_provider-wire.providers.openid_connect_provider-wire.providers.openelemetry_traces_provider-wire.providers.cors_provider-wire.providers.debug_provider# It's possible to add routersrouters:-demo_app.routes.issuer_router-demo_app.routes.nats_router-demo_app.routes.demo_router# Or hookshooks:-demo_app.hooks.issuer_hook# Or tasks (not used in this example)tasks:[]# # It's also possible to declare default config file# config_file: ~/.quara.config.jsonAppSpeccontainer factoryIt's also possible to define applications using a python object instead of a text file.TheAppSpecclass can be used to create application containers according to an application specification.Adding new hooksUpdate the filedemo_app/spec.pyto add a new hook to your application.Thehooksargument of theAppSpecconstructor can be used to specify a list of hooks used by the application.Each hook must implement theAsyncContextManagerprotocol or be functions which might returnNoneor anAsyncContextManagerinstance.Object yielded by the hook is available in API endpoints using dependency injection.Note: It's possible to access any container attribute within hooks.Adding new routersUpdate the filedemo_app/spec.pyto register a new router within your application.Theroutersargument of theAppSpecconstructor can be used to specify a list of routers used by the application.Bothfastapi.APIRouterand functions which might returnNoneor anfastapi.APIRouterinstance are accepted as list items.Adding providers to the applicationProviders are functions which can modify the FastAPI application before it is started.They must accept an application container instance as unique argument, and can return a list of objects or None.
When None is returned, it is assumed that provider is disabled.
When a list is returned, each object present in the list will be available in API endpoints using dependency injection.Example providers are located insrc/quara-wiring/quara/wiring/providers/directory and are registered indemo_app/spec.py.Building the applicationRun the following command to build the application:python-mbuild.Advantages of thesrc/layoutThis project uses asrc/layout. It means that all source code can be found undersrc/directory. It might appear overkill at first, but it brings several benefits:Without src you get messy editable installs ("pip install -e"). Having no separation (no src dir) will force setuptools to put your project's root onsys.path- with all the junk in it (e.g.: setup.py and other test or configuration scripts will unwittingly become importable).You get import parity. The current directory is implicitly included insys.path; but not so when installing & importing from site-packages.You will be forced to test the installed code (e.g.: by installing in a virtualenv and performing an editable install). This will ensure that the deployed code works (it's packaged correctly) - otherwise your tests will fail.Simpler packaging code and manifest. It makes manifests very simple to write (e.g.: root directory of project is never considered by setuptools or other packaging toolswhen bundling files into package). Also, zero fuss for large libraries that have multiple packages. Clear separation of code being packaged and code doing the packaging.TelemetryBatchSpanProcessoris configurable with the following environment variables which correspond to constructor parameters:OTEL_BSP_SCHEDULE_DELAYOTEL_BSP_MAX_QUEUE_SIZEOTEL_BSP_MAX_EXPORT_BATCH_SIZEOTEL_BSP_EXPORT_TIMEOUT