Skip to content

Commit

Permalink
Webserver, initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
DonDebonair committed Feb 26, 2018
1 parent 9bedde8 commit 583728c
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 11 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,7 @@ crashlytics-build.properties
fabric.properties

# Jupyter is only used to experiment
*.ipynb
*.ipynb

# Pytest
.pytest_cache
26 changes: 20 additions & 6 deletions machine/core.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import sys
import inspect
import logging
import dill
import sys
from threading import Thread

import dill
from clint.textui import puts, indent, colored
import bottle

from machine.dispatch import EventDispatcher
from machine.plugins.base import MachineBasePlugin
from machine.settings import import_settings
from machine.singletons import Slack, Scheduler, Storage
from machine.slack import MessagingClient
from machine.storage import PluginStorage
from machine.utils.module_loading import import_string
from machine.plugins.base import MachineBasePlugin
from machine.dispatch import EventDispatcher
from machine.slack import MessagingClient
from machine.utils.text import show_valid, show_invalid, warn, error, announce

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -72,7 +75,7 @@ def load_plugins(self):
instance = cls(self._settings, MessagingClient(),
storage)
missing_settings = self._register_plugin(class_name, instance)
if(missing_settings):
if (missing_settings):
show_invalid(class_name)
with indent(4):
error_msg = "The following settings are missing: {}".format(
Expand Down Expand Up @@ -149,6 +152,9 @@ def _register_plugin_actions(self, plugin_class, metadata, cls_instance, fn_name
if action == 'schedule':
Scheduler.get_instance().add_job(fq_fn_name, trigger='cron', args=[cls_instance],
id=fq_fn_name, replace_existing=True, **config)
if action == 'route':
for route_config in config:
bottle.route(**route_config)(fn)

def _parse_human_help(self, doc):
summary = doc.splitlines()[0].split(':')
Expand Down Expand Up @@ -179,5 +185,13 @@ def run(self):
show_valid("Connected to Slack")
Scheduler.get_instance().start()
show_valid("Scheduler started")
if not self._settings['DISABLE_HTTP']:
self._bottle_thread = Thread(
target=bottle.run,
kwargs=dict(host='0.0.0.0', port=8080)
)
self._bottle_thread.daemon = True
self._bottle_thread.start()
show_valid("Web server started")
show_valid("Dispatcher started")
self._dispatcher.start()
24 changes: 23 additions & 1 deletion machine/plugins/decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from blinker import signal
import re

from blinker import signal


def process(slack_event_type):
"""Process Slack events of a specific type
Expand Down Expand Up @@ -152,3 +153,24 @@ def required_settings_decorator(f_or_cls):
return f_or_cls

return required_settings_decorator


def route(path, **kwargs):
"""Define a http route that should trigger the function
The parameters to this decorator will be passed to the ``route`` function of Bottle
:param path: path to match
:param kwargs: additional keyword arguments to be passed to Bottle
"""

def route_decorator(f):
f.metadata = getattr(f, "metadata", {})
f.metadata['plugin_actions'] = f.metadata.get('plugin_actions', {})
f.metadata['plugin_actions']['route'] = \
f.metadata['plugin_actions'].get('route', [])
kwargs['path'] = path
f.metadata['plugin_actions']['route'].append(kwargs)
return f

return route_decorator
3 changes: 2 additions & 1 deletion machine/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def import_settings(settings_module='local_settings'):
'machine.plugins.builtin.general.HelloPlugin',
'machine.plugins.builtin.help.HelpPlugin',
'machine.plugins.builtin.fun.memes.MemePlugin'],
'STORAGE_BACKEND': 'machine.storage.backends.memory.MemoryStorage'
'STORAGE_BACKEND': 'machine.storage.backends.memory.MemoryStorage',
'DISABLE_HTTP': False
}
settings = CaseInsensitiveDict(default_settings)
try:
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ slackclient==1.1.2
dill==0.2.7.1
apscheduler==3.5.1
blinker-alt==1.5
clint==0.5.1
clint==0.5.1
bottle==0.12.13
18 changes: 17 additions & 1 deletion tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest
from blinker import signal
from machine.plugins.decorators import process, listen_to, respond_to, schedule, on, \
required_settings
required_settings, route


@pytest.fixture(scope='module')
Expand Down Expand Up @@ -97,6 +97,13 @@ class C:

return C

@pytest.fixture(scope='module')
def route_f():
@route('/test', method='POST')
def f():
pass
return f


def test_process(process_f):
assert hasattr(process_f, 'metadata')
Expand Down Expand Up @@ -176,3 +183,12 @@ def test_required_settings_for_class(required_settings_class):
assert isinstance(required_settings_class.metadata['required_settings'], list)
assert 'setting_1' in required_settings_class.metadata['required_settings']
assert 'setting_2' in required_settings_class.metadata['required_settings']


def test_route(route_f):
assert hasattr(route_f, 'metadata')
assert 'plugin_actions' in route_f.metadata
assert 'route' in route_f.metadata['plugin_actions']
assert len(route_f.metadata['plugin_actions']['route']) == 1
assert route_f.metadata['plugin_actions']['route'][0]['path'] == '/test'
assert route_f.metadata['plugin_actions']['route'][0]['method'] == 'POST'

0 comments on commit 583728c

Please sign in to comment.