summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2012-11-02 17:25:34 -0400
committerRyan Petrello <lists@ryanpetrello.com>2012-11-03 07:17:11 -0400
commiteccc1eff14b39c9758ea3865d62865890130cc1c (patch)
tree1ffb0ea9a19036fe1315e58e87a9de605e75ef35
parentd8e53a2c8318d97952e6b13a19d2f5c44cbbb55a (diff)
downloadpecan-eccc1eff14b39c9758ea3865d62865890130cc1c.tar.gz
Add support for content type detection via Accept headers.
-rw-r--r--pecan/core.py36
-rw-r--r--pecan/tests/test_base.py48
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