Package: applipy
ApplipyA library for building modular applications.pip install applipyApplipy lets you:implement self-contained or interdependantModulesthat you
can tell applipy to load for the application through the config fileimplementAppHandles: instances that implement application
lifecycle methodsmodules can register multipleAppHandles that will be run concurrentlyload your application from a configuration fileapplipy will gracefully manage the lifecycle of your applicationdefineprotocol
handlersfor a given URI scheme in configuration values. With these you can implementsecretsthat can be accessed by the application through the configuration.UsageAn application can be defined by using a JSON (or YAML, ifpyyamlis
installed).# dev.yamlapp:name:demomodules:-applipy_http.HttpModule-applipy_prometheus.PrometheusModulelogging.level:DEBUGhttp.servers:-host:0.0.0.0port:8080Save a filedev.yamlwith the contents in the snipet above and run the
following commands:$ pip install pyyaml applipy applipy_http applipy_prometheus
$ applipyThe configuration file above defines an application nameddemothat installs
the applipy web module and the Prometheus module.You can try it by going tohttp://localhost:8080. To
see some metrics you have to call at least twice on thehttp://0.0.0.0:8080/metricsendpoint.Applipy will search for a configuration file named${APPLIPY_CONFIG_PATH}/${APPLIPY_ENV}.json(and${APPLIPY_CONFIG_PATH}/${APPLIPY_ENV}.yaml, ifpyyamlis installed). The
default values are:APPLIPY_ENV=devandAPPLIPY_CONFIG_PATH=..Another option is to indicate the file directly, usingapplipy -f ./path/to/file.yaml.AppHandleAppHandle is the interface through wich applipy manages the lifecycle of the
application. An AppHandle implementation looks like this:# demo_app.pyfromasyncioimportget_event_loopfromloggingimportLoggerfromapplipyimportAppHandleclassMyDemoApp(AppHandle):def__init__(self,logger:Logger,name:str):self.logger=logger.getChild(name)asyncdefon_init(self):self.logger.info('initialize resources')self.future=get_event_loop().create_future()asyncdefon_start(self):self.logger.info('run long lived application here')awaitself.futureasyncdefon_shutdown(self):self.logger.info('close and release resources')self.future.set_result(None)As you can see above there is three methods exposed by AppHandles that let
applipy run your application.Applipy is capable of running multiple AppHandles concurrently,
taking advantage of async in python.The lifecycle of an application follow the following diagram:Application.start()
           │
           ▼
  AppHandle.on_init()
           │
           ▼
  AppHandle.on_start()        applipy cli
           │                SIGINT   SIGTERM   [Your own stopping mechanism]
           ▼                   └────────┴───────┬───────────┘
start_long_running_task()                       ▼
           │                            Application.stop()
           ▼                                    │
 AppHandle.on_shutdown()◄───────────────────────┘
           │
           ▼
  wait(shutdown_timeout)
           │
           ▼
   cancel_all_tasks()shutdown_timeoutcan be set in the app configuration using the keyapp.shutdown_timeout_seconds. Defaults to 1 second.It is expected thatAppHandle.on_shutdown()will be used to indicate any
long running tasks to stop and cleanup, and do any required cleanup itself.Generally, AppHandle implementations are added to the applipy application by
including the modules they are part of and registering the AppHandle in the
moduleconfigure()function.ModulesIn applipy, modules are the building blocks of an application. They allow to
bind instances/classes/providers to types by exposing the anapplipy_inject.Injector'sbind()function, register application handles by exposing the Application'sregister()function and define dependencies across modules.An example of a module implementation looks like this:# mymodule.pyfromapplipyimportConfig,Module,LoggingModulefromdemo_appimportMyDemoAppclassMyModule(Module):def__init__(self,config:Config):self._config=configdefconfigure(self,bind,register):bind(str,'ModuleDemo')register(MyDemoApp)@classmethoddefdepends_on(cls):return(LoggingModule,)The way you add modules to an application is through the configuration file by
defining a list of fully qualified names of Module implementations with
the keyapp.modules:app:modules:-applipy_http.HttpModule-applipy_prometheus.PrometheusModule-mymodule.MyModuleModules can only receive one parameter in their constructor and it is aConfiginstance, as shown in the code above. If your module does not need
access to the configuration, you can just not implement a__init__or have it
not have arguments (other thanself).Theconfigure()method is run by the applipyApplicationwhen it is started
and its purpose is to allow for binding types and registering application
handles. Check the extendedModuledocumentation in/docs/module.md.Finally, thedepends_on()class method returns a tuple of the module types the
module depends on. In the example above, because the application handle
registered by the module requires alogging.Logger, the module declares a dependency
with theLoggingModulebecause we know that it binds thelogging.Loggertype.MoreFor a deeper dive on the features and details feel free to check the/docssubdirectory and the code itself!