def lookup_template_engine(self, tgl): """Return the template engine data. Provides a convenience method to get the proper engine, content_type, template, and exclude_names for a particular tg_format (which is pulled off of the request headers). """ request = tgl.request response = tgl.response try: render_custom_format = request._render_custom_format[ self.controller] except: render_custom_format = self.render_custom_format if render_custom_format: (content_type, engine, template, exclude_names, render_params) = self.custom_engines[render_custom_format] else: if self.default_engine: content_type = self.default_engine elif self.engines: if response.content_type is not None: # Check for overridden content type from the controller call accept_types = create_accept_header(response.content_type) elif request._response_type and request._response_type in self.engines: # Check for content type detected by request extensions accept_types = create_accept_header(request._response_type) else: accept_types = request.accept best_matches = ( accept_types.acceptable_offers(self.engines_keys) or # If none of the available engines matches with the # available options, just suggest usage of the first engine. ( (self.engines_keys[0], None), )) content_type = best_matches[0][0] else: content_type = 'text/html' # check for overridden templates try: cnt_override_mapping = request._override_mapping[ self.controller] engine, template, exclude_names, render_params = cnt_override_mapping[ content_type.split(";")[0]] except (AttributeError, KeyError): (engine, template, exclude_names, render_params) = self.engines.get(content_type, (None, ) * 4) return content_type, engine, template, exclude_names, render_params
def lookup_template_engine(self, tgl): """Return the template engine data. Provides a convenience method to get the proper engine, content_type, template, and exclude_names for a particular tg_format (which is pulled off of the request headers). """ request = tgl.request response = tgl.response try: render_custom_format = request._render_custom_format[self.controller] except: render_custom_format = self.render_custom_format if render_custom_format: (content_type, engine, template, exclude_names, render_params ) = self.custom_engines[render_custom_format] else: if self.default_engine: content_type = self.default_engine elif self.engines: if response.content_type is not None: # Check for overridden content type from the controller call accept_types = create_accept_header(response.content_type) elif request._response_type and request._response_type in self.engines: # Check for content type detected by request extensions accept_types = create_accept_header(request._response_type) else: accept_types = request.accept best_matches = ( accept_types.acceptable_offers(self.engines_keys) or # If none of the available engines matches with the # available options, just suggest usage of the first engine. ((self.engines_keys[0], None), ) ) content_type = best_matches[0][0] else: content_type = 'text/html' # check for overridden templates try: cnt_override_mapping = request._override_mapping[self.controller] engine, template, exclude_names, render_params = cnt_override_mapping[content_type.split(";")[0]] except (AttributeError, KeyError): (engine, template, exclude_names, render_params ) = self.engines.get(content_type, (None,) * 4) return content_type, engine, template, exclude_names, render_params
def generate_response(self, environ, start_response): if self.content_length is not None: del self.content_length headerlist = list(self.headerlist) accept_value = environ.get('HTTP_ACCEPT', '') accept_header = create_accept_header(header_value=accept_value) acceptable_offers = accept_header.acceptable_offers( offers=['text/html', 'application/json'], ) match = acceptable_offers[0][0] if acceptable_offers else None if match == 'text/html': content_type = 'text/html' body = self.html_body(environ) elif match == 'application/json': content_type = 'application/json' body = self.json_body(environ) else: content_type = 'text/plain' body = self.plain_body(environ) resp = Response(body, status=self.status, headerlist=headerlist, content_type=content_type, ) resp.content_type = content_type return resp(environ, start_response)
def prepare(self, environ): if not self.body: accept_value = environ.get("HTTP_ACCEPT", "") accept = create_accept_header(accept_value) # Attempt to match XML or JSON, if those don't match, we will fall back to defaulting to JSON # since browsers add HTML automatically and it is closer to XML, we 'allow' it only to catch this # explicit case and fallback to JSON manually match = accept.best_match([ CONTENT_TYPE_TEXT_HTML, CONTENT_TYPE_APP_JSON, CONTENT_TYPE_TEXT_XML, CONTENT_TYPE_APP_XML ], default_match=CONTENT_TYPE_APP_JSON) if match == CONTENT_TYPE_TEXT_HTML: match = CONTENT_TYPE_APP_JSON if match == CONTENT_TYPE_APP_JSON: self.content_type = CONTENT_TYPE_APP_JSON # json exception response should not have status 200 if self.status_code == HTTPOk.code: self.status = HTTPInternalServerError.code class JsonPageTemplate(object): def __init__(self, excobj): self.excobj = excobj def substitute(self, code, locator, message): status = self.excobj.status title = getattr(self.excobj, "code", None) data = self.excobj.json_formatter(status=status, body=message, title=title, environ=environ) data["exception"] = { "code": code or "", "locator": locator or "", "message": message or "", } return json.dumps(data) page_template = JsonPageTemplate(self) args = { "code": self.code, "locator": self.locator, "message": self.message } else: self.content_type = CONTENT_TYPE_TEXT_XML page_template = self.page_template args = { "code": self.code, "locator": self.locator, "message": self.message or "", } page = page_template.substitute(**args) if isinstance(page, str): page = page.encode(self.charset if self.charset else "UTF-8") self.app_iter = [page] self.body = page
def generate_response(self, environ, start_response): if self.content_length is not None: del self.content_length headerlist = list(self.headerlist) accept_value = environ.get("HTTP_ACCEPT", "") accept_header = create_accept_header(header_value=accept_value) acceptable_offers = accept_header.acceptable_offers( offers=["text/html", "application/json"] ) match = acceptable_offers[0][0] if acceptable_offers else None if match == "text/html": content_type = "text/html" body = self.html_body(environ) elif match == "application/json": content_type = "application/json" body = self.json_body(environ) else: content_type = "text/plain" body = self.plain_body(environ) resp = Response( body, status=self.status, headerlist=headerlist, content_type=content_type ) resp.content_type = content_type return resp(environ, start_response)
def test_it_sets_response_header_based_on_value_of_accept( self, pyramid_request, testview, accept, expected_header): pyramid_request.accept = create_accept_header(accept) res = version_media_type_header(testview)(None, pyramid_request) assert res.headers["Hypothesis-Media-Type"] == expected_header
def test_it_replaces_context_with_406_if_accept_set_and_invalid( self, pyramid_request, testview): # None of these is valid pyramid_request.accept = create_accept_header( "application/something+json, foo/bar") fake_context = mock.Mock() validate_media_types(testview)(fake_context, pyramid_request) context, _ = testview.call_args[0] assert isinstance(context, HTTPNotAcceptable)
def test_it_does_not_modify_context_if_any_accept_values_ok( self, pyramid_request, testview): # At least one of these is valid pyramid_request.accept = create_accept_header( "application/json, foo/bar") fake_context = mock.Mock() validate_media_types(testview)(fake_context, pyramid_request) context, _ = testview.call_args[0] assert context == fake_context
def prepare(self, environ): if not self.body: accept_value = environ.get("HTTP_ACCEPT", "") accept = create_accept_header(accept_value) # Attempt to match xml or json, if those don't match, we will fall through to defaulting to xml match = accept.best_match( [CONTENT_TYPE_TEXT_XML, CONTENT_TYPE_APP_JSON]) if match == CONTENT_TYPE_APP_JSON: self.content_type = CONTENT_TYPE_APP_JSON # json exception response should not have status 200 if self.status_code == HTTPOk.code: self.status = HTTPInternalServerError.code class JsonPageTemplate(object): def __init__(self, excobj): self.excobj = excobj def substitute(self, code, locator, message): status = self.excobj.status data = self.excobj.json_formatter(status=status, body=message, title=None, environ=environ) data["exception"] = { "code": code or "", "locator": locator or "", "message": message or "", } return json.dumps(data) page_template = JsonPageTemplate(self) else: self.content_type = CONTENT_TYPE_TEXT_XML page_template = self.page_template args = { "code": self.code, "locator": self.locator, "message": self.message or "", } page = page_template.substitute(**args) if isinstance(page, str): page = page.encode(self.charset if self.charset else "UTF-8") self.app_iter = [page] self.body = page
def _get_accept(self): if self._accept is None: self._accept = create_accept_header(None) return self._accept
def _set_accept(self, value): self._accept = create_accept_header(value)
def pyramid_request(self, pyramid_request): # Set an empty accept on the request, imitating what pyramid does # in real life if no Accept header is set on the incoming request pyramid_request.accept = create_accept_header(None) return pyramid_request
def __call__(self, environ, start_response): ''' Implements the WSGI specification for Pecan applications, utilizing ``WebOb``. ''' # create the request and response object req = self.request_cls(environ) resp = self.response_cls() state = RoutingState(req, resp, self) environ['pecan.locals'] = {'request': req, 'response': resp} controller = None # track internal redirects internal_redirect = False # handle the request try: # add context and environment to the request req.context = environ.get('pecan.recursive.context', {}) req.pecan = dict(content_type=None) controller, args, kwargs = self.find_controller(state) self.invoke_controller(controller, args, kwargs, state) except Exception as e: # if this is an HTTP Exception, set it as the response if isinstance(e, exc.HTTPException): # if the client asked for JSON, do our best to provide it accept_header = acceptparse.create_accept_header( getattr(req.accept, 'header_value', '*/*') or '*/*') offers = accept_header.acceptable_offers( ('text/plain', 'text/html', 'application/json')) best_match = offers[0][0] if offers else None state.response = e if best_match == 'application/json': json_body = dumps({ 'code': e.status_int, 'title': e.title, 'description': e.detail }) if isinstance(json_body, six.text_type): e.text = json_body else: e.body = json_body state.response.content_type = best_match environ['pecan.original_exception'] = e # note if this is an internal redirect internal_redirect = isinstance(e, ForwardRequestException) # if this is not an internal redirect, run error hooks on_error_result = None if not internal_redirect: on_error_result = self.handle_hooks( self.determine_hooks(state.controller), 'on_error', state, e) # if the on_error handler returned a Response, use it. if isinstance(on_error_result, WebObResponse): state.response = on_error_result else: if not isinstance(e, exc.HTTPException): raise # if this is an HTTP 405, attempt to specify an Allow header if isinstance(e, exc.HTTPMethodNotAllowed) and controller: allowed_methods = _cfg(controller).get('allowed_methods', []) if allowed_methods: state.response.allow = sorted(allowed_methods) finally: # if this is not an internal redirect, run "after" hooks if not internal_redirect: self.handle_hooks(self.determine_hooks(state.controller), 'after', state) self._handle_empty_response_body(state) # get the response return state.response(environ, start_response)
def find_controller(self, state): ''' The main request handler for Pecan applications. ''' # get a sorted list of hooks, by priority (no controller hooks yet) req = state.request pecan_state = req.pecan # store the routing path for the current application to allow hooks to # modify it pecan_state['routing_path'] = path = req.path_info # handle "on_route" hooks self.handle_hooks(self.hooks, 'on_route', state) # lookup the controller, respecting content-type as requested # by the file extension on the URI pecan_state['extension'] = None # attempt to guess the content type based on the file extension if self.guess_content_type_from_ext \ and not pecan_state['content_type'] \ and '.' in path: _, extension = splitext(path.rstrip('/')) # preface with a letter to ensure compat for 2.5 potential_type = guess_type('x' + extension)[0] if extension and potential_type is not None: path = ''.join(path.rsplit(extension, 1)) pecan_state['extension'] = extension pecan_state['content_type'] = potential_type controller, remainder = self.route(req, self.root, path) cfg = _cfg(controller) if cfg.get('generic_handler'): raise exc.HTTPNotFound # handle generic controllers im_self = None if cfg.get('generic'): im_self = six.get_method_self(controller) handlers = cfg['generic_handlers'] controller = handlers.get(req.method, handlers['DEFAULT']) handle_security(controller, im_self) cfg = _cfg(controller) # add the controller to the state so that hooks can use it state.controller = controller # if unsure ask the controller for the default content type content_types = cfg.get('content_types', {}) if not pecan_state['content_type']: # attempt to find a best match based on accept headers (if they # exist) accept = getattr(req.accept, 'header_value', '*/*') or '*/*' if accept == '*/*' or (accept.startswith('text/html,') and list( content_types.keys()) in self.SIMPLEST_CONTENT_TYPES): pecan_state['content_type'] = cfg.get('content_type', 'text/html') else: best_default = None accept_header = acceptparse.create_accept_header(accept) offers = accept_header.acceptable_offers( list(content_types.keys())) if offers: # If content type matches exactly use matched type best_default = offers[0][0] else: # If content type doesn't match exactly see if something # matches when not using parameters for k in content_types.keys(): if accept.startswith(k): best_default = k break if best_default is None: msg = "Controller '%s' defined does not support " + \ "content_type '%s'. Supported type(s): %s" logger.error( msg % (controller.__name__, pecan_state['content_type'], content_types.keys())) raise exc.HTTPNotAcceptable() pecan_state['content_type'] = best_default elif cfg.get('content_type') is not None and \ pecan_state['content_type'] not in content_types: msg = "Controller '%s' defined does not support content_type " + \ "'%s'. Supported type(s): %s" logger.error(msg % (controller.__name__, pecan_state['content_type'], content_types.keys())) raise exc.HTTPNotFound # fetch any parameters if req.method == 'GET': params = req.GET elif req.content_type in ('application/json', 'application/javascript'): try: if not isinstance(req.json, dict): raise TypeError('%s is not a dict' % req.json) params = NestedMultiDict(req.GET, req.json) except (TypeError, ValueError): params = req.params else: params = req.params # fetch the arguments for the controller args, varargs, kwargs = self.get_args(state, params.mixed(), remainder, cfg['argspec'], im_self) state.arguments = Arguments(args, varargs, kwargs) # handle "before" hooks self.handle_hooks(self.determine_hooks(controller), 'before', state) return controller, args + varargs, kwargs
def prepare(self, environ): if not self.has_body and not self.empty_body: html_comment = '' comment = self.comment or '' accept_value = environ.get('HTTP_ACCEPT', '') accept = create_accept_header(accept_value) # Attempt to match text/html or application/json, if those don't # match, we will fall through to defaulting to text/plain acceptable = accept.acceptable_offers(['text/html', 'application/json']) acceptable = [offer[0] for offer in acceptable] + ['text/plain'] match = acceptable[0] if match == 'text/html': self.content_type = 'text/html' escape = _html_escape page_template = self.html_template_obj br = '<br/>' if comment: html_comment = '<!-- %s -->' % escape(comment) elif match == 'application/json': self.content_type = 'application/json' self.charset = None escape = _no_escape br = '\n' if comment: html_comment = escape(comment) class JsonPageTemplate(object): def __init__(self, excobj): self.excobj = excobj def substitute(self, status, body): jsonbody = self.excobj._json_formatter( status=status, body=body, title=self.excobj.title, environ=environ) return json.dumps(jsonbody) page_template = JsonPageTemplate(self) else: self.content_type = 'text/plain' escape = _no_escape page_template = self.plain_template_obj br = '\n' if comment: html_comment = escape(comment) args = { 'br': br, 'explanation': escape(self.explanation), 'detail': escape(self.detail or ''), 'comment': escape(comment), 'html_comment': html_comment, } body_tmpl = self.body_template_obj if HTTPException.body_template_obj is not body_tmpl: # Custom template; add headers to args for k, v in environ.items(): if (not k.startswith('wsgi.')) and ('.' in k): # omit custom environ variables, stringifying them may # trigger code that should not be executed here; see # https://github.com/Pylons/pyramid/issues/239 continue args[k] = escape(v) for k, v in self.headers.items(): args[k.lower()] = escape(v) body = body_tmpl.substitute(args) page = page_template.substitute(status=self.status, body=body) if isinstance(page, text_type): page = page.encode(self.charset if self.charset else 'UTF-8') self.app_iter = [page] self.body = page
def prepare(self, environ): if not self.has_body and not self.empty_body: html_comment = '' comment = self.comment or '' accept_value = environ.get('HTTP_ACCEPT', '') accept = create_accept_header(accept_value) # Attempt to match text/html or application/json, if those don't # match, we will fall through to defaulting to text/plain acceptable = accept.acceptable_offers( ['text/html', 'application/json']) acceptable = [offer[0] for offer in acceptable] + ['text/plain'] match = acceptable[0] if match == 'text/html': self.content_type = 'text/html' escape = _html_escape page_template = self.html_template_obj br = '<br/>' if comment: html_comment = '<!-- %s -->' % escape(comment) elif match == 'application/json': self.content_type = 'application/json' self.charset = None escape = _no_escape br = '\n' if comment: html_comment = escape(comment) class JsonPageTemplate(object): def __init__(self, excobj): self.excobj = excobj def substitute(self, status, body): jsonbody = self.excobj._json_formatter( status=status, body=body, title=self.excobj.title, environ=environ, ) return json.dumps(jsonbody) page_template = JsonPageTemplate(self) else: self.content_type = 'text/plain' escape = _no_escape page_template = self.plain_template_obj br = '\n' if comment: html_comment = escape(comment) args = { 'br': br, 'explanation': escape(self.explanation), 'detail': escape(self.detail or ''), 'comment': escape(comment), 'html_comment': html_comment, } body_tmpl = self.body_template_obj if HTTPException.body_template_obj is not body_tmpl: # Custom template; add headers to args for k, v in environ.items(): if (not k.startswith('wsgi.')) and ('.' in k): # omit custom environ variables, stringifying them may # trigger code that should not be executed here; see # https://github.com/Pylons/pyramid/issues/239 continue args[k] = escape(v) for k, v in self.headers.items(): args[k.lower()] = escape(v) body = body_tmpl.substitute(args) page = page_template.substitute(status=self.status, body=body) if isinstance(page, str): page = page.encode(self.charset if self.charset else 'UTF-8') self.app_iter = [page] self.body = page