Package: aio.app
Application runner for theaioasyncio frameworkBuild statusInstallationRequires python >= 3.4Install with:pipinstallaio.appQuick start - hello world schedulerSave the following into a file “hello.conf”[schedule/EXAMPLE]every=2func=my_example.schedule_handlerAnd save the following into a file named “my_example.py”importasynciodefschedule_handler(event):yield fromasyncio.sleep(1)print("Received scheduled:%s"%event.name)Run with the aio run commandaiorun-chello.confTheaio configcommandWhen saving or reading configuration options, configuration files are searched for in order from the following locationsaio.confetc/aio.conf/etc/aio/aio.confTo dump the system configuration you can runaioconfigTo dump a configuration section you can use -g or –get with the section nameaioconfig-gaioaioconfig--getaio/commandsTo get a configuration option, you can use -g with the section name and optionaioconfig-gaio:log_levelaioconfig--getlisten/example:example-signalYou can set a configuration option with -s or –setOptions containing interpolation should be enclosed in single quotesMulti-line options should be enclosed in “ and separated with “\n”aioconfig--setaio:log_levelDEBUGaioconfig-saio/otherapp:log_level'${aio:log_level}'aioconfig-slisten/example:example-signal"my.listener\nmy.listener2"If no configuration files are present in the standard locations, aio will attempt to save in “aio.conf” in the current working directoryTo get or set an option in a particular file you can use the -f flagaioconfig-gaio:modules-fcustom.confaioconfig-saio:log_levelDEBUG-fcustom.confWhen getting config values with the -f flag,ExtendedInterpolationis not used, and you therefore see the raw valuestheaio runcommandYou can run an aio app as follows:aiorunOr with a custom configuration fileaio-ccustom.confrunOn startup aio run sets up the followingConfiguration - system-wide configurationModules - initialization and configuration of modulesLogging - system logging policiesSchedulers - functions called at set timesServers - listening on tcp/udp or other type of socketSignals - functions called in response to eventsConfigurationConfiguration is in ini syntax[aio]foo=eggsspamWhile the app is running the system configuration is importable from aio.appfromaio.appimportconfigConfiguration is parsed usingExtendedInterpolationas followsaio.app defaults readuser configuration read to initialize modules“aio.conf” read from initialized modules where presentuser configuration read againLoggingLogging policies can be placed in the configuration file, following pythonsfileConfigformatAs the configuration is parsed withExtendedInterpolationyou can use options from other sections[logger_root]level=${aio:log_level}handlers=consoleHandlerqualname=aioThe default aio:log_level is INFOAny sections that begin with handler, logger, or formatter will automattically be added to the relevant logging sectionSo by adding a section such as[logger_custom]level=${aio:log_level}handlers=consoleHandlerqualname=custom“logger_custom” will automatically be added to the logger keys:[loggers]keys=root,customModulesYou can list any modules that should be imported at runtime in the configuration[aio]modules=aio.web.serveraio.manhole.serverConfiguration for each module is read from a file named “aio.conf” in the module’s path, if it exists.The initialized modules can be accessed from aio.appfromaio.appimportmodulesSchedulersSchedule definition sections are in the following format[schedule/SCHEDULE_NAME]Specify the frequency and the function to call. The function will be wrapped in a coroutine if it isnt one already[schedule/example]every=2func=my.scheduler.example_schedulerThe scheduler function receives a ScheduledEvent objectdefexample_scheduler(event):yield fromasyncio.sleep(2)# do somethingprint(event.name)passServersServer definition sections are in the following format[server/SERVER_NAME]The server requires either a factory or a protocol to startProtocol configuration example:[server/example]protocol=my.example.MyServerProtocolport=8888Protocol example code:classMyServerProtocol(asyncio.Protocol):defconnection_made(self,transport):self.transport=transportdefdata_received(self,data):# do stuffself.transport.close()For the protocol option you can either specify a subclass of asyncio.Protocol or you can use a function decorated with aio.app.server.protocol[server/example]protocol=my.example.protocolport=8888Example code for a server protocol functionimportasyncioimportaio.app@aio.app.server.protocoldefserver_protocol():yield fromasyncio.sleep(1)# do somethingreturnMyServerProtocolIf you need further control over how the protocol is attached you can specify a factory methodFactory configuration example:[server/example]factory=my.example.server_factoryport=8080The factory method must be wrapped in aio.app.server.factory, and is called in a coroutine@aio.app.server.factorydefserver_factory(name,protocol,address,port):yield fromasyncio.sleep(1)# do somethingloop=asyncio.get_event_loop()return(yield fromloop.create_server(MyServerProtocol,address,port))SignalsSignal definition sections are in the following format[signal/SIGNAL_NAME]An example listen configuration section[listen/example]example-signal=my.example.listenerAnd an example listener function. The listener function will be called as a coroutinedeflistener(signal):yield fromasyncio.sleep(2)print(signal.data)Signals are emitted in a coroutineyield fromapp.signals.emit('example-signal',"BOOM!")You can add multiple subscriptions within each configuration sectionYou can also subscribe multiple functions to a signal, and you can have multiple “listen/” sections[listen/example]example-signal=my.example.listenerexample-signal-2=my.example.listener2my.example.listener[listen/example-2]example-signal-3=my.example.listener2Theaio testcommandYou can test the modules set in the aio:modules configuration option[aio]modules=aio.configaio.coreaio.signalsBy default the aio test command will test all of your test modulesaiotestYou can also specify a module, or modulesaiotestaio.appaiotestaio.appaio.coreIf you want to specify a set of modules for testing other than your app modules, you can list them in aio/testing:modules[aio/testing]modules=aio.configaio.coreThese can include the app modules[aio/testing]modules=${aio:modules}aio.web.pageaio.web.serverDependenciesaio.app depends on the following packagesaio.coreaio.signalsaio.configRelated softwareaio.testingaio.http.serveraio.web.serveraio.manhole.serveraio.app usageThe aio command can be run with any commands listed in the [aio/commands] section of its configurationThere are also 3 builtin commands - run, config and testInitially 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():
...     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 appWe 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 listenerWe 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 importableThe listener needs to be wrapped with aio.app.signal.listener and is called in a coroutine>>> import asyncio>>> @aio.app.signal.listener
... def listener(signal):
...     yield from asyncio.sleep(1)
...     print("Listener received: %s" % signal.data)>>> aio.app.tests._example_listener = listenerRunning the test…>>> @aio.testing.run_until_complete
... def run_app(message):
...     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):
...     runner(['run'])
...
...     aio.app.signals.listen('test-signal-2', aio.app.signal.listener(listener))
...     yield from aio.app.signals.emit('test-signal-2', message)
...     aio.app.clear()>>> run_app('BOOM AGAIN!')
Listener received: BOOM AGAIN!Adding app modulesWhen 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):
...     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 schedulerA 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 is wrapped in a coroutine>>> def scheduler(event):
...      print('HIT: %s' % event.name)>>> aio.app.tests._example_scheduler = schedulerWe 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():
...     runner(['run'], config_string=config)
...
...     return aio.app.clearRunning the test for 5 seconds we get 3 hits>>> run_app()
HIT: test-scheduler
HIT: test-scheduler
HIT: test-schedulerRunning a serverLets set up and run an addition serverAt 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 = AdditionServerProtocolAfter the server is set up, let’s call it with a simple addition>>> @aio.testing.run_forever
... def run_addition_server(config_string, addition):
...     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')
7If 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 decorated with aio.app.server.factory>>> @aio.app.server.factory
... 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