Skip to main content

Library for building modular applications

Project description

Applipy

pip install applipy

Applipy is an application development framework that allows to define the application by installing modules and registering application handles.

Usage

An application can be defined by using a JSON (or YAML, if pyyaml is installed).

# dev.yaml
app:
  name: demo
  modules:
    - applipy_http.HttpModule
    - applipy_prometheus.PrometheusModule

logging.level: DEBUG

web:
  host: 0.0.0.0
  port: 8080

Save a file dev.yaml with the contents in the snipet above and run the following commands:

$ pip install pyyaml applipy applipy_http applipy_prometheus
$ python -m applipy

The configuration file above defines an application named demo that installs the applipy web module and the Prometheus module.

You can try it by going to http://localhost:8080. To see some metrics you have to call at least twice on the http://0.0.0.0:8080/metrics endpoint.

Applipy will search for a configuration file named ${APPLIPY_CONFIG_PATH}/${APPLIPY_ENV}.json (and ${APPLIPY_CONFIG_PATH}/${APPLIPY_ENV}.yaml, if pyyaml is installed). The default values are: APPLIPY_ENV=dev and APPLIPY_CONFIG_PATH=.

AppHandle

AppHandle is the interface through wich applipy manages the lifecycle of the application. An AppHandle implementation looks like this:

# demo_app.py

from applipy import AppHandle


class MyDemoApp(AppHandle):

    async def on_init(self):
        print('initialize resources')

    async def on_start(self):
        print('run long lived application here')
        while True:
            await sleep(3600)

    async def on_shutdown(self):
        print('close and release resources')

As you can see above there is three methods exposed by AppHandles that let applipy run your application.

Applipy is capable of running multiple concurrent AppHandles concurrently, taking advantage of async in python.

Simplifying a lot, applipy will run your AppHandles like this:

try:
    await all_app_handles.on_init()
    await all_app_handles.on_start()
finally:
    await allapp_handles.on_shutdown()

Generally, AppHandle implementations are added to the applipy application by including the modules they are part of and registering the AppHandle in the module configure() function.

Modules

In applipy, modules are the building blocks of an application. They allow to bind instances/classes/providers to types by exposing the an applipy_inject.Injector's bind() function, register application handles by exposing the Application's register() function and define dependencies across modules.

An example of a module implementation looks like this:

# mymodule.py

from applipy import Config, Module, LoggingModule
from logging import Logger
from demo_app import MyDemoApp


class MyModule(Module):

    def __init__(self, config: Config):
        self._config = config

    def configure(self, bind, register):
        bind(str, 'ModuleDemo')
        register(MyDemoApp)

    @classmethod
    def depends_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 key app.modules:

app:
  modules:
    - applipy_http.HttpModule
    - applipy_prometheus.PrometheusModule
    - mymodule.MyModule

Modules can only receive one parameter in their constructor and it is a Config instance, 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 than self).

The configure() method is run by the applipy Application when it is started and its purpose is to allow for binding types and registering application handles. Check the extended Module documentation in /docs/module.md.

Finally, the depends_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 a logging.Logger, the module declares a dependency with the LoggingModule because we know that it binds the logging.Logger type.

Advanced usage

Here is a small programmatically defined application:

# demo.py

from applipy import Application, AppHandle, Config, LoggingModule
from asyncio import sleep
from logging import Logger


class TestApp(AppHandle):
    def __init__(self, logger: Logger):
        self.logger = logger.getChild(self.__class__.__name__)

    async def on_init(self):
        self.logger.info('application init')

    async def on_start(self):
        self.logger.info('application start')
        while True:
            await sleep(1)
            self.logger.info('application update')

    async def on_shutdown(self):
        self.logger.info('application exit')

Application(Config({'logging.level': 'INFO'})) \
    .install(LoggingModule) \
    .register(TestApp) \
    .run()
$ python demo.py
INFO:root:Installing module `applipy.logging.module.LoggingModule`
INFO:TestApp:application init
INFO:TestApp:application start
INFO:TestApp:application update
INFO:TestApp:application update
INFO:TestApp:application update
^CINFO:root:Received SIGINT. Shutting down.
INFO:TestApp:application exit

More

For a deeper dive on the features and details feel free to check the /docs subdirectory and the code itself!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

applipy-1.0.0.tar.gz (12.2 kB view hashes)

Uploaded Source

Built Distribution

applipy-1.0.0-py3-none-any.whl (13.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page