diff options
author | Ryan Petrello <lists@ryanpetrello.com> | 2012-11-02 17:25:34 -0400 |
---|---|---|
committer | Ryan Petrello <lists@ryanpetrello.com> | 2012-11-03 07:17:11 -0400 |
commit | eccc1eff14b39c9758ea3865d62865890130cc1c (patch) | |
tree | 1ffb0ea9a19036fe1315e58e87a9de605e75ef35 | |
parent | d8e53a2c8318d97952e6b13a19d2f5c44cbbb55a (diff) | |
download | pecan-eccc1eff14b39c9758ea3865d62865890130cc1c.tar.gz |
Add support for content type detection via Accept headers.
-rw-r--r-- | pecan/core.py | 36 | ||||
-rw-r--r-- | pecan/tests/test_base.py | 48 |
2 files changed, 79 insertions, 5 deletions
diff --git a/pecan/core.py b/pecan/core.py index 1ff1663..20576e1 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -3,7 +3,7 @@ from routing import lookup_controller, NonCanonicalPath from util import _cfg, encode_if_needed from middleware.recursive import ForwardRequestException -from webob import Request, Response, exc +from webob import Request, Response, exc, acceptparse from threading import local from itertools import chain from mimetypes import guess_type, add_type @@ -367,6 +367,7 @@ class Pecan(object): # by the file extension on the URI path = request.pecan['routing_path'] + # attempt to guess the content type based on the file extension if not request.pecan['content_type'] and '.' in path.split('/')[-1]: path, extension = splitext(path) request.pecan['extension'] = extension @@ -392,10 +393,35 @@ class Pecan(object): # if unsure ask the controller for the default content type if not request.pecan['content_type']: - request.pecan['content_type'] = cfg.get( - 'content_type', - 'text/html' - ) + # attempt to find a best match based on accept headers (if they + # exist) + if 'Accept' in request.headers: + best_default = acceptparse.MIMEAccept( + request.headers['Accept'] + ).best_match( + cfg.get('content_types', {}).keys() + ) + + if best_default is None: + import warnings + msg = "Controller '%s' defined does not support " + \ + "content_type '%s'. Supported type(s): %s" + warnings.warn( + msg % ( + controller.__name__, + request.pecan['content_type'], + cfg.get('content_types', {}).keys() + ), + RuntimeWarning + ) + raise exc.HTTPNotFound + + request.pecan['content_type'] = best_default + else: + request.pecan['content_type'] = cfg.get( + 'content_type', + 'text/html' + ) elif cfg.get('content_type') is not None and \ request.pecan['content_type'] not in \ cfg.get('content_types', {}): diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py index 0b81bd1..d651132 100644 --- a/pecan/tests/test_base.py +++ b/pecan/tests/test_base.py @@ -908,6 +908,54 @@ class TestFileTypeExtensions(unittest.TestCase): assert r.status_int == 404 +class TestAcceptHeaders(unittest.TestCase): + + @property + def app_(self): + """ + Test extension splits + """ + class RootController(object): + + @expose(content_type='text/html') + @expose(content_type='application/json') + def index(self, *args): + return 'Foo' + + return TestApp(Pecan(RootController())) + + def test_quality(self): + r = self.app_.get('/', headers={ + 'Accept': 'text/html,application/json;q=0.9,*/*;q=0.8' + }) + assert r.status_int == 200 + assert r.content_type == 'text/html' + + r = self.app_.get('/', headers={ + 'Accept': 'application/json,text/html;q=0.9,*/*;q=0.8' + }) + assert r.status_int == 200 + assert r.content_type == 'application/json' + + def test_file_extension_has_higher_precedence(self): + r = self.app_.get('/index.html', headers={ + 'Accept': 'application/json,text/html;q=0.9,*/*;q=0.8' + }) + assert r.status_int == 200 + assert r.content_type == 'text/html' + + def test_no_match(self): + r = self.app_.get('/', headers={ + 'Accept': 'application/xml', + }, status=404) + assert r.status_int == 404 + + def test_accept_header_missing(self): + r = self.app_.get('/') + assert r.status_int == 200 + assert r.content_type == 'text/html' + + class TestCanonicalRouting(unittest.TestCase): @property |