Skip to content

Commit

Permalink
Add support for services and configuring Flask directly
Browse files Browse the repository at this point in the history
  • Loading branch information
cnorthwood committed Dec 27, 2012
1 parent f933cb0 commit 3424ea3
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 25 deletions.
3 changes: 3 additions & 0 deletions conf/default.conf
@@ -1,3 +1,6 @@
[services]
i18n = flask.ext.babel:Babel

[weather]
module = molly.apps.weather
provider.metoffice = molly.apps.weather.providers.metoffice
Expand Down
2 changes: 1 addition & 1 deletion molly/apps/weather/__init__.py
Expand Up @@ -7,7 +7,7 @@ class App(object):

module = 'http://mollyproject.org/apps/weather'

def __init__(self, instance_name, config, providers=[]):
def __init__(self, instance_name, config, providers, services):
self.instance_name = instance_name
self._provider = providers.pop()

Expand Down
49 changes: 36 additions & 13 deletions molly/config.py
Expand Up @@ -7,9 +7,43 @@ def load_from_config(self, config):
apps = []
parser = ConfigParser()
parser.readfp(config)

if parser.has_section('services'):
services = self._initialise_services(parser.items('services'))
else:
services = {}

if parser.has_section('global'):
global_config = dict((key.upper(), eval(value)) for (key, value) in parser.items('global'))
else:
global_config = {}

for section in parser.sections():
apps.append(self._initialise_app(section, dict(parser.items(section))))
return apps
if section not in ('services', 'global'):
apps.append(self._initialise_app(section, dict(parser.items(section)), services))

return global_config, apps, services

def _initialise_services(self, config):
services = {}
for service_name, service_package in config:
if ':' in service_package:
service_module, service_class = service_package.split(':')
else:
service_module, service_class = service_package, 'Service'
services[service_name] = getattr(import_module(service_module), service_class)()
return services

def _initialise_app(self, name, config, services):
providers = self._initialise_providers(config)
module_name = config.get('module')
if module_name is None:
raise ConfigError('Module name not defined for app ' + name)
try:
app_module = import_module(module_name)
except ImportError:
raise ConfigError("Unable to find module: " + config['module'])
return app_module.App(name, config, providers, services)

def _initialise_providers(self, config):
providers = []
Expand All @@ -33,17 +67,6 @@ def _get_provider_config(self, config):
configs[key_parts[1]][1][key_parts[2]] = value
return configs.values()

def _initialise_app(self, name, config):
providers = self._initialise_providers(config)
module_name = config.get('module')
if module_name is None:
raise ConfigError('Module name not defined for app ' + name)
try:
app_module = import_module(module_name)
except ImportError:
raise ConfigError("Unable to find module: " + config['module'])
return app_module.App(name, config, providers)


class ConfigError(Exception):
pass
7 changes: 4 additions & 3 deletions molly/rest.py
@@ -1,17 +1,18 @@
import os

from flask import Flask
from flask.ext.babel import Babel

from molly.config import ConfigLoader
from molly.apps.homepage import App as Homepage

flask_app = Flask(__name__)
Babel(flask_app)

with open(os.environ.get('MOLLY_CONFIG', 'conf/default.conf')) as fd:
config_loader = ConfigLoader()
apps = config_loader.load_from_config(fd)
apps, services = config_loader.load_from_config(fd)

for service in services.values():
service.init_app(flask_app)

for app in apps:
flask_app.register_blueprint(app.blueprint, url_prefix='/' + app.instance_name)
Expand Down
2 changes: 1 addition & 1 deletion tests/molly/apps/weather/test_weather_app.py
Expand Up @@ -18,7 +18,7 @@ def setUp(self):
self._observations_endpoint.get.methods = ('GET',)
self._observations_endpoint.get.required_methods = ()
weather.ObservationsEndpoint = Mock(return_value=self._observations_endpoint)
self._weather_app = weather.App('weather', {}, [self._provider])
self._weather_app = weather.App('weather', {}, [self._provider], {})

def test_module_is_set_correctly(self):
self.assertEquals('http://mollyproject.org/apps/weather', self._weather_app.module)
Expand Down
35 changes: 29 additions & 6 deletions tests/molly/test_config_loader.py
Expand Up @@ -3,12 +3,12 @@
except ImportError:
from StringIO import StringIO

from mock import Mock
import unittest2 as unittest

from molly.config import ConfigLoader, ConfigError

from tests.test_providers.provider import Provider as TestProvider
from tests.test_services.service import Service as TestService

class ConfigLoaderTestCase(unittest.TestCase):

Expand All @@ -20,23 +20,23 @@ def tearDown(self):
PROVIDER_TEST_CONFIG.reset()

def test_config_loader_returns_list_of_apps(self):
apps = self._config_loader.load_from_config(StringIO(""))
config, apps, services = self._config_loader.load_from_config(StringIO(""))
self.assertEquals(0, len(apps))

def test_config_loader_loads_apps(self):
apps = self._config_loader.load_from_config(SIMPLE_TEST_CONFIG)
config, apps, services = self._config_loader.load_from_config(SIMPLE_TEST_CONFIG)
self.assertEquals('test', apps[0].instance_name)

def test_config_loader_passes_config_dict_to_app(self):
apps = self._config_loader.load_from_config(SIMPLE_TEST_CONFIG)
config, apps, services = self._config_loader.load_from_config(SIMPLE_TEST_CONFIG)
self.assertEquals('bar', apps[0].config['foo'])

def test_config_loader_creates_providers_for_app(self):
apps = self._config_loader.load_from_config(PROVIDER_TEST_CONFIG)
config, apps, services = self._config_loader.load_from_config(PROVIDER_TEST_CONFIG)
self.assertIsInstance(apps[0].providers[0], TestProvider)

def test_config_loader_puts_config_into_provider(self):
apps = self._config_loader.load_from_config(PROVIDER_TEST_CONFIG)
config, apps, services = self._config_loader.load_from_config(PROVIDER_TEST_CONFIG)
self.assertEquals('baz', apps[0].providers[0].config['bar'])

def test_config_loader_raises_config_exception_on_no_such_app(self):
Expand All @@ -51,6 +51,16 @@ def test_module_is_compulsory_field(self):
def test_provider_is_compulsory_field(self):
self.assertRaises(ConfigError, self._config_loader.load_from_config, MISSING_PROVIDER_CONFIG)

def test_services_are_passed_to_app(self):
config, apps, services = self._config_loader.load_from_config(SERVICES_CONFIG)
self.assertIsInstance(apps[0].services['test'], TestService)
self.assertIsInstance(services['test'], TestService)

def test_global_config_is_extracted(self):
config, apps, services = self._config_loader.load_from_config(GLOBAL_CONFIG)
self.assertEquals(True, config['DEBUG'])


SIMPLE_TEST_CONFIG = StringIO("""
[test]
module = tests.test_apps.app
Expand Down Expand Up @@ -86,3 +96,16 @@ def test_provider_is_compulsory_field(self):
module = tests.test_apps.app
provider.test.bar = baz
""")

SERVICES_CONFIG = StringIO("""
[test]
module = tests.test_apps.app
[services]
test = tests.test_services.service
""")

GLOBAL_CONFIG = StringIO("""
[global]
DEBUG = True
""")
3 changes: 2 additions & 1 deletion tests/test_apps/app.py
@@ -1,6 +1,7 @@
class App(object):

def __init__(self, instance_name, config, providers=[]):
def __init__(self, instance_name, config, providers, services):
self.instance_name = instance_name
self.config = config
self.providers = providers
self.services = services
1 change: 1 addition & 0 deletions tests/test_services/__init__.py
@@ -0,0 +1 @@
__author__ = 'chris'
2 changes: 2 additions & 0 deletions tests/test_services/service.py
@@ -0,0 +1,2 @@
class Service(object):
pass

0 comments on commit 3424ea3

Please sign in to comment.