Skip to main content

Aio application runner

Project description

Detailed documentation
**********************

aio.app
=======

Application runner for the aio_ asyncio framework

.. _aio: https://github.com/phlax/aio


Build status
------------

.. image:: https://travis-ci.org/phlax/aio.app.svg?branch=master
:target: https://travis-ci.org/phlax/aio.app


Installation
------------

Install with:

.. code:: bash

pip install aio.app


Running an aio app
------------------

You can run an aio app as follows:

.. code:: bash

aio run

Or with a custom configuration file

.. code:: bash

aio -c custom.conf run


If you run the command without specifying a configuration file the aio command will look look for one in the following places on your filesystem

- aio.conf
- etc/aio.conf
- /etc/aio/aio.conf


The *aio run* command
---------------------

On startup aio run sets up the following

- Configuration - system-wide configuration
- Modules - initialization and configuration of modules
- Logging - system logging policies
- Schedulers - functions called at set times
- Servers - listening on tcp/udp or other type of socket
- Signals - functions called in response to events


Configuration
~~~~~~~~~~~~~

Configuration is in ini syntax

.. code:: ini

[aio]
modules = aio.app
aio.signals

While the app is running the system configuration is importable from aio.app

.. code:: python

from aio.app import config

Configuration is parsed using ExtendedInterpolation_ as follows

- aio.app defaults read
- user configuration read to initialize modules
- "aio.conf" read from initialized modules where present
- user configuration read again to ensure for precedence


Logging
~~~~~~~

Logging policies can be placed in the configuration file, following pythons fileConfig_ format

.. _fileConfig: https://docs.python.org/3/library/logging.config.html#logging-config-fileformat

As the configuration is parsed with ExtendedInterpolation_ you can use options from other sections

.. code:: ini

[logger_root]
level=${aio:log_level}
handlers=consoleHandler
qualname=aio

The default aio:log_level is INFO


Modules
~~~~~~~

You can list any modules that should be imported at runtime in the configuration

Default configuration for each of these modules is read from a file named aio.conf in the module's path, if it exists.

The system modules can be accessed from aio.app

.. code:: python

from aio.app import modules


Schedulers
----------

Any sections in the configuration that start with "schedule/" will create a scheduler.

Specify the frequency and the function to call. The function should be a co-routine.

.. code:: ini

[schedule/example]
every = 2
func = my.scheduler.example_scheduler

The scheduler function takes 1 argument the name of the scheduler

.. code:: python

@asyncio.coroutine
def example_scheduler(name):
yield from asyncio.sleep(2)
# do something
pass

Servers
-------

Any sections in the configuration that start with "server/" will create a server

The server requires either a factory or a protocol to start

Protocol configuration example:

.. code:: ini

[server/example]
protocol = my.example.ServerProtocol
port = 8888

Protocol example code:

.. code:: python

class ServerProtocol(asyncio.Protocol):

def connection_made(self, transport):
self.transport = transport

def data_received(self, data):
# do stuff
self.transport.close()

If you need further control over how the protocol is created and attached you can specify a factory method

Factory configuration example:

.. code:: ini

[server/example]
factory = my.example.server_factory
port = 8080

Factory code example:

.. code:: python

@asyncio.coroutine
def server_factory(name, protocol, address, port):
loop = asyncio.get_event_loop()
return (
yield from loop.create_server(
ServerProtocol, address, port))


Signals
~~~~~~~

Any section in the configuration that starts with "listen/" will subscribe listed functions to given events

An example listen configuration section

.. code:: ini

[listen/example]
example-signal = my.example.listener

And an example listener function

.. code:: python

@asyncio.coroutine
def listener(signal, message):
print(message)

Signals are emitted in a coroutine

.. code:: python

yield from app.signals.emit(
'example-signal', "BOOM!")

You can add multiple subscriptions within the section

.. code:: ini

[listen/example]
example-signal = my.example.listener
example-signal-2 = my.example.listener2

You can also subscribe multiple functions to a signal

.. code:: ini

[listen/example]
example-signal = my.example.listener
my.example.listener2


And you can have multiple "listen/" sections

.. code:: ini

[listen/example]
example-signal = my.example.listener
my.example.listener2

[listen/example2]
example-signal2 = my.example.listener2


The *aio config* command
------------------------

To dump the system configuration you can run

.. code:: bash

aio config

To dump a configuration section you can use -g or --get with the section name

.. code:: bash

aio config -g aio

aio config --get aio/commands

To get a configuration option, you can use -g with the section name and option

.. code:: bash

aio config -g aio:log_level

aio config --get listen/example:example-signal

You can set a configuration option with -s or --set

Multi-line options should be enclosed in " and separated with "\\n"

.. code:: bash

aio config --set aio:log_level DEBUG

aio config -s listen/example:example-signal "my.listener\nmy.listener2"

When saving configuration options, configuration files are searched for in order from the following locations

- aio.conf
- etc/aio.conf
- /etc/aio/aio.conf

If none are present aio will attempt to save it in "aio.conf" in the current working directory

To get or set an option in a particular file you can use the -f flag

.. code:: bash

aio config -g aio:modules -f custom.conf

aio config -s aio:log_level DEBUG -f custom.conf

When getting config values with the -f flag, ExtendedInterpolation_ is not used, and you therefore see the raw values


The *aio test* command
----------------------

You can test the installed modules using the aio test command

.. code:: ini

[aio]
modules = aio.app
aio.signals

.. code:: bash

aio test

You can also specify a module

.. code:: bash

aio test aio.app


Dependencies
------------

aio.app depends on the following packages

- aio.core_
- aio.signals_
- aio.config_


Related software
----------------

- aio.http.server_
- aio.web.server_


.. _aio.core: https://github.com/phlax/aio.core
.. _aio.signals: https://github.com/phlax/aio.signals
.. _aio.config: https://github.com/phlax/aio.config

.. _aio.http: https://github.com/phlax/aio.http.server
.. _aio.web.server: https://github.com/phlax/aio.web.server

.. _ExtendedInterpolation: https://docs.python.org/3/library/configparser.html#interpolation-of-values





The aio command runner
----------------------

The aio command can be run with any commands listed in the [aio/commands] section of its configuration

There are also 3 builtin commands - run, config and test

Initially aio.app does not have any config, signals, modules or servers

>>> import aio.app

>>> print(aio.app.signals, aio.app.config, aio.app.modules, aio.app.servers)
None None () {}


Lets start the app runner in a test loop with the default configuration and print out the signals and config objects

>>> from aio.testing import aiotest
>>> from aio.app.runner import runner

>>> @aiotest
... def run_app():
... yield from runner(['run'])
...
... print(aio.app.signals)
... print(aio.app.config)
... print(aio.app.modules)
... print(aio.app.servers)


>>> run_app()
<aio.signals.Signals object ...>
<configparser.ConfigParser ...>
(<module 'aio.app' from ...>,)
{}


Clear the app
-------------

We can clear the app vars.

This will also close any socket servers that are currently running

>>> aio.app.clear()

>>> print(aio.app.signals, aio.app.config, aio.app.modules, aio.app.servers)
None None () {}


Adding a signal listener
------------------------

We can add a signal listener in the app config

>>> config = """
... [listen/testlistener]
... test-signal = aio.app.tests._example_listener
... """

Lets create a test listener and make it importable

The listener needs to be a coroutine

>>> import asyncio

>>> @asyncio.coroutine
... def listener(signal, message):
... print("Listener received: %s" % message)

>>> aio.app.tests._example_listener = listener

Running the test...

>>> @aiotest
... def run_app(message):
... yield from runner(['run'], config_string=config)
... yield from aio.app.signals.emit('test-signal', message)

>>> run_app('BOOM!')
Listener received: BOOM!

>>> aio.app.clear()

We can also add listeners programatically

>>> @aiotest
... def run_app(message):
... yield from runner(['run'])
...
... aio.app.signals.listen('test-signal-2', asyncio.coroutine(listener))
... yield from aio.app.signals.emit('test-signal-2', message)

>>> run_app('BOOM AGAIN!')
Listener received: BOOM AGAIN!


Adding app modules
------------------

When you run the app with the default configuration, the only module listed is aio.app

>>> @aiotest
... def run_app(config_string=None):
... yield from runner(['run'], config_string=config_string)
... print(aio.app.modules)

>>> run_app()
(<module 'aio.app' from ...>,)

>>> aio.app.clear()

We can make the app runner aware of any modules that we want to include, these are imported at runtime

>>> config = """
... [aio]
... modules = aio.app
... aio.core
... """

>>> run_app(config_string=config)
(<module 'aio.app' from ...>, <module 'aio.core' from ...>)

>>> aio.app.clear()


Running a scheduler
-------------------

A basic configuration for a scheduler

>>> config = """
... [schedule/test-scheduler]
... every: 2
... func: aio.app.tests._example_scheduler
... """

Lets create a scheduler function and make it importable.

The scheduler function should be a coroutine

>>> @asyncio.coroutine
... def scheduler(name):
... print('HIT: %s' % name)

>>> aio.app.tests._example_scheduler = scheduler

We need to use a aiofuturetest to wait for the scheduled events to occur

>>> from aio.testing import aiofuturetest

>>> @aiofuturetest(timeout=5)
... def run_app():
... yield from runner(['run'], config_string=config)

Running the test for 5 seconds we get 3 hits

>>> run_app()
HIT: test-scheduler
HIT: test-scheduler
HIT: test-scheduler

>>> aio.app.clear()


Running a server
----------------

Lets set up and run an addition server

At a minimum we should provide a protocol and a port to listen on

>>> config_server_protocol = """
... [server/additiontest]
... protocol: aio.app.tests._example_AdditionServerProtocol
... port: 8888
... """

Lets create the server protocol and make it importable

>>> class AdditionServerProtocol(asyncio.Protocol):
...
... def connection_made(self, transport):
... self.transport = transport
...
... def data_received(self, data):
... nums = [
... int(x.strip())
... for x in
... data.decode("utf-8").split("+")]
... self.transport.write(str(sum(nums)).encode())
... self.transport.close()

>>> aio.app.tests._example_AdditionServerProtocol = AdditionServerProtocol

After the server is set up, let's call it with a simple addition

>>> @aiofuturetest
... def run_addition_server(config_string, addition):
... yield from runner(['run'], config_string=config_string)
...
... def call_addition_server():
... reader, writer = yield from asyncio.open_connection(
... '127.0.0.1', 8888)
... writer.write(addition.encode())
... yield from writer.drain()
... result = yield from reader.read()
...
... print(int(result))
...
... return call_addition_server

>>> run_addition_server(
... config_server_protocol,
... '2 + 2 + 3')
7

>>> aio.app.clear()

If you need more control over how the server protocol is created you can specify a factory instead

>>> config_server_factory = """
... [server/additiontest]
... factory = aio.app.tests._example_addition_server_factory
... port: 8888
... """

The factory method must be a coroutine

>>> @asyncio.coroutine
... def addition_server_factory(name, protocol, address, port):
... loop = asyncio.get_event_loop()
... return (
... yield from loop.create_server(
... AdditionServerProtocol,
... address, port))

>>> aio.app.tests._example_addition_server_factory = addition_server_factory

>>> run_addition_server(
... config_server_protocol,
... '17 + 5 + 1')
23

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

aio.app-0.0.24.tar.gz (13.7 kB view details)

Uploaded Source

File details

Details for the file aio.app-0.0.24.tar.gz.

File metadata

  • Download URL: aio.app-0.0.24.tar.gz
  • Upload date:
  • Size: 13.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for aio.app-0.0.24.tar.gz
Algorithm Hash digest
SHA256 1fa3f4f60a254d6448401882ea93fea80e7a8022b73139c8bda3caf9425e1fe4
MD5 8c95ab2c7b14ab9cab926ffc6db1f7c6
BLAKE2b-256 ff31a1558351e530020f5c82c6801fb84a401cb5741cd9915e3b0c861f4e8c72

See more details on using hashes here.

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