From f14b744ef7ee2c7e24698394196583c13fa7044c Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 22:24:35 -0600 Subject: [PATCH 1/4] Added tests for Request.static_resource --- src/milla/tests/test_app.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/milla/tests/test_app.py b/src/milla/tests/test_app.py index 3e7dfda..cd5b1d5 100644 --- a/src/milla/tests/test_app.py +++ b/src/milla/tests/test_app.py @@ -457,3 +457,31 @@ def test_create_href_full_keywords(): request = milla.Request(environ) url = request.create_href_full('/bar', foo='baz') assert url == 'http://127.0.0.1/bar?foo=baz' + +def test_static_resource(): + '''Request.static_resource creates valid URL from config''' + + def controller(request): + return request.static_resource('/image.png') + + environ = environ_for_testing() + app = milla.Application(StubResolver(controller)) + app.config['milla.static_root'] = '/static' + response = ResponseMaker() + app_iter = app(environ, response.start_response) + response.finish_response(app_iter) + assert response.body == b'/static/image.png', response.body + +def test_static_resource_undefined(): + '''Request.static_resource returns the path unmodified with no root defined''' + + def controller(request): + return request.static_resource('/image.png') + + environ = environ_for_testing() + app = milla.Application(StubResolver(controller)) + response = ResponseMaker() + app_iter = app(environ, response.start_response) + response.finish_response(app_iter) + assert response.body == b'/image.png', response.body + \ No newline at end of file From f5f7e76dae6280d9317ecbd46750ebc8bedeeb6f Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 22:46:28 -0600 Subject: [PATCH 2/4] Added tests for Router's trailing slash handling options --- src/milla/dispatch/routing.py | 4 +-- src/milla/tests/test_routing.py | 55 ++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/milla/dispatch/routing.py b/src/milla/dispatch/routing.py index d16d1fa..d0d7e02 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -24,8 +24,6 @@ import functools import milla import re import sys -import urllib -import urlparse import warnings class Router(object): @@ -99,7 +97,7 @@ class Router(object): for attr in functools.WRAPPER_ASSIGNMENTS: try: value = getattr(controller, attr) - except AttributeError: + except AttributeError: #pragma: no cover pass else: setattr(func, attr, value) diff --git a/src/milla/tests/test_routing.py b/src/milla/tests/test_routing.py index 8c9597c..0f46590 100644 --- a/src/milla/tests/test_routing.py +++ b/src/milla/tests/test_routing.py @@ -6,6 +6,7 @@ :Updater: $Author$ ''' import milla.dispatch.routing +import nose.tools def fake_controller(): pass @@ -44,6 +45,7 @@ def test_urlvars(): assert func.keywords['bar'] == 'abc' assert func.keywords['baz'] == 'def' +@nose.tools.raises(milla.dispatch.UnresolvedPath) def test_regexp_urlvar(): '''Ensure the dispatcher can resolve alternate regexps in urlvars @@ -61,13 +63,9 @@ def test_regexp_urlvar(): assert func.func == controller assert func.keywords['arg'] == 'abcde' - try: - func = router.resolve('/test/1234') - except milla.dispatch.UnresolvedPath: - pass - else: - raise AssertionError + router.resolve('/test/1234') +@nose.tools.raises(milla.dispatch.UnresolvedPath) def test_unresolved(): '''Ensure the resolver raises an exception for unresolved paths @@ -80,12 +78,7 @@ def test_unresolved(): router = milla.dispatch.routing.Router() router.add_route('/test', controller) - try: - router.resolve('/tset') - except milla.dispatch.UnresolvedPath: - pass - else: - raise AssertionError + router.resolve('/tset') def test_unrelated(): '''Ensure the dispatcher is not confused by unrelated paths @@ -119,3 +112,41 @@ def test_string_controller(): router.add_route('/test', 'milla.tests.test_routing:fake_controller') func = router.resolve('/test') assert func.func == fake_controller + +@nose.tools.raises(milla.HTTPMovedPermanently) +def test_trailing_slash_redir(): + '''Paths that match except the trailing slash return a HTTP redirect + ''' + + def controller(): + pass + + router = milla.dispatch.routing.Router() + router.add_route('/test/', controller) + func = router.resolve('/test') + assert func is not controller + func() + +@nose.tools.raises(milla.dispatch.routing.UnresolvedPath) +def test_trailing_slash_none(): + '''Paths that match except the trailing slash are ignored + ''' + + def controller(): + pass + + router = milla.dispatch.routing.Router(None) + router.add_route('/test/', controller) + router.resolve('/test') + +def test_trailing_slash_silent(): + '''Paths that match except the trailing slash are treated the same + ''' + + def controller(): + pass + + router = milla.dispatch.routing.Router(milla.dispatch.routing.Router.SILENT) + router.add_route('/test/', controller) + func = router.resolve('/test') + assert func.func is controller \ No newline at end of file From d635e83431e643cf52093b7088fc543e921d43c8 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 22:47:06 -0600 Subject: [PATCH 3/4] Automatically create a Traverser instance if Application is given a root object --- src/milla/app.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/milla/app.py b/src/milla/app.py index 740057f..3250a90 100644 --- a/src/milla/app.py +++ b/src/milla/app.py @@ -24,7 +24,7 @@ Please give me a docstring! from milla.controllers import FaviconController from milla.util import asbool from webob.exc import HTTPNotFound, WSGIHTTPException, HTTPMethodNotAllowed -import milla.dispatch +import milla.dispatch.traversal import webob __all__ = ['Application'] @@ -36,9 +36,8 @@ class Application(object): alternatively, a root object that will be passed to a new :py:class:`milla.dispatch.traversal.Traverser`. - :param root: A root object, passed to a traverser, which is - automatically created if a root is given - :param dispatcher: An object implementing the dispatcher protocol + :param obj: An object implementing the dispatcher protocol, or an + object to be used as the root for a Traverser ``Application`` instances are WSGI applications. @@ -50,8 +49,11 @@ class Application(object): DEFAULT_ALLOWED_METHODS = ['GET', 'HEAD'] - def __init__(self, dispatcher): - self.dispatcher = dispatcher + def __init__(self, obj): + if not hasattr(obj, 'resolve'): + # Object is not a dispatcher, but the root object for traversal + obj = milla.dispatch.traversal.Traverser(obj) + self.dispatcher = obj self.config = {'milla.favicon': True} def __call__(self, environ, start_response): From 4085039997123663eccb28c12ee30c5b3036ab10 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 19 Dec 2012 15:26:19 -0600 Subject: [PATCH 4/4] Added simple inifile-to-dictionary parser --- src/milla/config.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/milla/config.py diff --git a/src/milla/config.py b/src/milla/config.py new file mode 100644 index 0000000..f656411 --- /dev/null +++ b/src/milla/config.py @@ -0,0 +1,50 @@ +try: + import configparser +except ImportError: + import ConfigParser as configparser + +def read_config(filename): + '''Parse an ini file into a nested dictionary + + :param string filename: Path to the ini file to read + :returns: A dictionary whose keys correspond to the section and + option, joined with a dot character (.) + + For example, consider the following ini file:: + + [xmen] + storm = Ororo Monroe + cyclops = Scott Summers + + [avengers] + hulk = Bruce Banner + iron_man = Tony Stark + + The resulting dictionary would look like this:: + + { + 'xmen.storm': 'Ororo Monroe', + 'xmen.cyclops': 'Scott Summers', + 'avengers.hulk': 'Bruce Banner', + 'avengers.iron_man': 'Tony Stark', + } + + Thus, the option values for any section can be obtained as follows:: + + config['xmen.storm'] + + This dictionary can be used to configure an :py:class:`~milla.Application` + instance by using the ``update`` method:: + + app = milla.Application(router) + app.config.update(config) + ''' + + cparser = configparser.SafeConfigParser() + cparser.readfp(open(filename)) + + config = {} + for section in cparser.sections(): + for option in cparser.options(section): + config['.'.join((section, option))] = cparser.get(section, option) + return config