supports changing the content type and streaming

This commit is contained in:
Mark McClain
2011-01-04 23:39:51 -05:00
parent 664e52b586
commit c862139f8e
3 changed files with 50 additions and 31 deletions

View File

@@ -28,6 +28,7 @@ def proxy(key):
return getattr(obj, attr)
def __setattr__(self, attr, value):
obj = getattr(state, key)
return setattr(obj, attr, value)
return ObjectProxy()
@@ -40,7 +41,6 @@ response = proxy('response')
def override_template(template):
request.override_template = template
def abort(status_code=None, detail='', headers=None, comment=None):
raise exc.status_map[status_code](detail=detail, headers=headers, comment=comment)
@@ -83,7 +83,8 @@ class Pecan(MonitorableProcess):
return {
'html' : 'text/html',
'xhtml' : 'text/html',
'json' : 'application/json'
'json' : 'application/json',
'txt' : 'text/plain'
}.get(format, 'text/html')
def route(self, node, path):
@@ -154,19 +155,20 @@ class Pecan(MonitorableProcess):
# get a sorted list of hooks, by priority (no controller hooks yet)
state.hooks = self.determine_hooks()
state.content_type = None
# handle "on_route" hooks
self.handle_hooks('on_route', state)
# lookup the controller, respecting content-type as requested
# by the file extension on the URI
path = state.request.path
content_type = None
if '.' in path.split('/')[-1]:
if state.content_type is None and '.' in path.split('/')[-1]:
path, format = path.split('.')
content_type = self.get_content_type(format)
state.content_type = self.get_content_type(format)
controller, remainder = self.route(self.root, path)
if controller.pecan.get('generic_handler'):
raise exc.HTTPNotFound
@@ -180,10 +182,9 @@ class Pecan(MonitorableProcess):
# add the controller to the state so that hooks can use it
state.controller = controller
# determine content type
if content_type is None:
content_type = controller.pecan.get('content_type', 'text/html')
# if unsure ask the controller for the default content type
if state.content_type is None:
state.content_type = controller.pecan.get('content_type', 'text/html')
# get a sorted list of hooks, by priority
state.hooks = self.determine_hooks(controller)
@@ -216,17 +217,24 @@ class Pecan(MonitorableProcess):
# get the result from the controller
result = controller(*positional_params, **params)
# a controller can return the response object which means they've taken
# care of filling it out
if result == response:
return
raw_namespace = result
# pull the template out based upon content type and handle overrides
template = controller.pecan.get('content_types', {}).get(content_type)
template = controller.pecan.get('content_types', {}).get(state.content_type)
template = getattr(request, 'override_template', template)
# if there is a template, render it
if template:
renderer = self.renderers.get(self.default_renderer, self.template_path)
if template == 'json':
renderer = self.renderers.get('json', self.template_path)
state.content_type = self.get_content_type('json')
else:
result['error_for'] = error_for
@@ -234,7 +242,6 @@ class Pecan(MonitorableProcess):
renderer = self.renderers.get(template.split(':')[0], self.template_path)
template = template.split(':')[1]
result = renderer.render(template, result)
content_type = renderer.content_type
# If we are in a test request put the namespace where it can be
# accessed directly
@@ -251,8 +258,8 @@ class Pecan(MonitorableProcess):
state.response.body = result
# set the content type
if content_type:
state.response.content_type = content_type
if state.content_type:
state.response.content_type = state.content_type
def __call__(self, environ, start_response):
# create the request and response object
@@ -287,8 +294,9 @@ class Pecan(MonitorableProcess):
return state.response(environ, start_response)
finally:
# clean up state
del state.content_type
del state.hooks
del state.request
del state.response
del state.hooks
if hasattr(state, 'controller'):
del state.controller

View File

@@ -7,8 +7,6 @@ _builtin_renderers = {}
#
class JsonRenderer(object):
content_type = 'application/json'
def __init__(self, path, extra_vars):
pass
@@ -27,8 +25,6 @@ try:
from genshi.template import TemplateLoader
class GenshiRenderer(object):
content_type = 'text/html'
def __init__(self, path, extra_vars):
self.loader = TemplateLoader([path], auto_reload=True)
self.extra_vars = extra_vars
@@ -51,8 +47,6 @@ try:
from mako.lookup import TemplateLookup
class MakoRenderer(object):
content_type = 'text/html'
def __init__(self, path, extra_vars):
self.loader = TemplateLookup(directories=[path])
self.extra_vars = extra_vars
@@ -74,8 +68,6 @@ try:
from kajiki.loader import FileLoader
class KajikiRenderer(object):
content_type = 'text/html'
def __init__(self, path, extra_vars):
self.loader = FileLoader(path, reload=True)
self.extra_vars = extra_vars
@@ -100,10 +92,10 @@ class ExtraNamespace(object):
def make_ns(self, ns):
if self.namespace:
retval = {}
retval.update(self.namespace)
retval.update(ns)
return retval
val = {}
val.update(self.namespace)
val.update(ns)
return val
else:
return ns

View File

@@ -1,6 +1,5 @@
import os
from pecan import Pecan, expose, request, redirect, abort
from pecan import Pecan, expose, request, redirect, abort
from pecan import Pecan, expose, request, response, redirect, abort
from pecan.templating import _builtin_renderers as builtin_renderers
from webtest import TestApp
from formencode import Schema, validators
@@ -305,3 +304,23 @@ class TestEngines(object):
r = app.get('/test/1/2/3/4')
assert r.status_int == 200
assert r.body == 'it worked'
def test_streaming_response(self):
class RootController(object):
@expose(content_type='text/plain')
def test(self, foo):
if foo == 'stream':
response.content_type='application/octet-stream'
response.body = 'stream'
return response
else:
return 'plain text'
app = TestApp(Pecan(RootController()))
r = app.get('/test/stream/')
assert r.content_type == 'application/octet-stream'
assert r.body == 'stream'
r = app.get('/test/plain/')
assert r.content_type == 'text/plain'
assert r.body == 'plain text'