

aio.app usage
-------------

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

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

>>> @aio.testing.run_until_complete
... 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...

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

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


We can also add listeners programatically

>>> @aio.testing.run_until_complete 
... 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)
...     aio.app.clear()  

>>> 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

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

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

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 ...>)


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 aio.testing.run_forever to wait for the scheduled events to occur

>>> @aio.testing.run_forever(timeout=5)
... def run_app():
...     yield from runner(['run'], config_string=config)
... 
...     return aio.app.clear
    
Running the test for 5 seconds we get 3 hits

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


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

>>> @aio.testing.run_forever
... 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()
...          aio.app.clear()
... 
...          print(int(result))
... 
...     return call_addition_server

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

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
  
