Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/renderers.py : 34%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from functools import partial
2import json
3import os
4import re
6from zope.interface import implementer, providedBy
7from zope.interface.registry import Components
9from pyramid.interfaces import IJSONAdapter, IRendererFactory, IRendererInfo
11from pyramid.compat import string_types, text_type
13from pyramid.csrf import get_csrf_token
14from pyramid.decorator import reify
16from pyramid.events import BeforeRender
18from pyramid.httpexceptions import HTTPBadRequest
20from pyramid.path import caller_package
22from pyramid.response import _get_response_factory
23from pyramid.threadlocal import get_current_registry
24from pyramid.util import hide_attrs
26# API
29def render(renderer_name, value, request=None, package=None):
30 """ Using the renderer ``renderer_name`` (a template
31 or a static renderer), render the value (or set of values) present
32 in ``value``. Return the result of the renderer's ``__call__``
33 method (usually a string or Unicode).
35 If the ``renderer_name`` refers to a file on disk, such as when the
36 renderer is a template, it's usually best to supply the name as an
37 :term:`asset specification`
38 (e.g. ``packagename:path/to/template.pt``).
40 You may supply a relative asset spec as ``renderer_name``. If
41 the ``package`` argument is supplied, a relative renderer path
42 will be converted to an absolute asset specification by
43 combining the package ``package`` with the relative
44 asset specification ``renderer_name``. If ``package``
45 is ``None`` (the default), the package name of the *caller* of
46 this function will be used as the package.
48 The ``value`` provided will be supplied as the input to the
49 renderer. Usually, for template renderings, this should be a
50 dictionary. For other renderers, this will need to be whatever
51 sort of value the renderer expects.
53 The 'system' values supplied to the renderer will include a basic set of
54 top-level system names, such as ``request``, ``context``,
55 ``renderer_name``, and ``view``. See :ref:`renderer_system_values` for
56 the full list. If :term:`renderer globals` have been specified, these
57 will also be used to augment the value.
59 Supply a ``request`` parameter in order to provide the renderer
60 with the most correct 'system' values (``request`` and ``context``
61 in particular).
63 """
64 try:
65 registry = request.registry
66 except AttributeError:
67 registry = None
68 if package is None:
69 package = caller_package()
70 helper = RendererHelper(
71 name=renderer_name, package=package, registry=registry
72 )
74 with hide_attrs(request, 'response'):
75 result = helper.render(value, None, request=request)
77 return result
80def render_to_response(
81 renderer_name, value, request=None, package=None, response=None
82):
83 """ Using the renderer ``renderer_name`` (a template
84 or a static renderer), render the value (or set of values) using
85 the result of the renderer's ``__call__`` method (usually a string
86 or Unicode) as the response body.
88 If the renderer name refers to a file on disk (such as when the
89 renderer is a template), it's usually best to supply the name as a
90 :term:`asset specification`.
92 You may supply a relative asset spec as ``renderer_name``. If
93 the ``package`` argument is supplied, a relative renderer name
94 will be converted to an absolute asset specification by
95 combining the package ``package`` with the relative
96 asset specification ``renderer_name``. If you do
97 not supply a ``package`` (or ``package`` is ``None``) the package
98 name of the *caller* of this function will be used as the package.
100 The ``value`` provided will be supplied as the input to the
101 renderer. Usually, for template renderings, this should be a
102 dictionary. For other renderers, this will need to be whatever
103 sort of value the renderer expects.
105 The 'system' values supplied to the renderer will include a basic set of
106 top-level system names, such as ``request``, ``context``,
107 ``renderer_name``, and ``view``. See :ref:`renderer_system_values` for
108 the full list. If :term:`renderer globals` have been specified, these
109 will also be used to argument the value.
111 Supply a ``request`` parameter in order to provide the renderer
112 with the most correct 'system' values (``request`` and ``context``
113 in particular). Keep in mind that any changes made to ``request.response``
114 prior to calling this function will not be reflected in the resulting
115 response object. A new response object will be created for each call
116 unless one is passed as the ``response`` argument.
118 .. versionchanged:: 1.6
119 In previous versions, any changes made to ``request.response`` outside
120 of this function call would affect the returned response. This is no
121 longer the case. If you wish to send in a pre-initialized response
122 then you may pass one in the ``response`` argument.
124 """
125 try:
126 registry = request.registry
127 except AttributeError:
128 registry = None
129 if package is None:
130 package = caller_package()
131 helper = RendererHelper(
132 name=renderer_name, package=package, registry=registry
133 )
135 with hide_attrs(request, 'response'):
136 if response is not None:
137 request.response = response
138 result = helper.render_to_response(value, None, request=request)
140 return result
143def get_renderer(renderer_name, package=None, registry=None):
144 """ Return the renderer object for the renderer ``renderer_name``.
146 You may supply a relative asset spec as ``renderer_name``. If
147 the ``package`` argument is supplied, a relative renderer name
148 will be converted to an absolute asset specification by
149 combining the package ``package`` with the relative
150 asset specification ``renderer_name``. If ``package`` is ``None``
151 (the default), the package name of the *caller* of this function
152 will be used as the package.
154 You may directly supply an :term:`application registry` using the
155 ``registry`` argument, and it will be used to look up the renderer.
156 Otherwise, the current thread-local registry (obtained via
157 :func:`~pyramid.threadlocal.get_current_registry`) will be used.
158 """
159 if package is None:
160 package = caller_package()
161 helper = RendererHelper(
162 name=renderer_name, package=package, registry=registry
163 )
164 return helper.renderer
167# concrete renderer factory implementations (also API)
170def string_renderer_factory(info):
171 def _render(value, system):
172 if not isinstance(value, string_types):
173 value = str(value)
174 request = system.get('request')
175 if request is not None:
176 response = request.response
177 ct = response.content_type
178 if ct == response.default_content_type:
179 response.content_type = 'text/plain'
180 return value
182 return _render
185_marker = object()
188class JSON(object):
189 """ Renderer that returns a JSON-encoded string.
191 Configure a custom JSON renderer using the
192 :meth:`~pyramid.config.Configurator.add_renderer` API at application
193 startup time:
195 .. code-block:: python
197 from pyramid.config import Configurator
199 config = Configurator()
200 config.add_renderer('myjson', JSON(indent=4))
202 Once this renderer is registered as above, you can use
203 ``myjson`` as the ``renderer=`` parameter to ``@view_config`` or
204 :meth:`~pyramid.config.Configurator.add_view`:
206 .. code-block:: python
208 from pyramid.view import view_config
210 @view_config(renderer='myjson')
211 def myview(request):
212 return {'greeting':'Hello world'}
214 Custom objects can be serialized using the renderer by either
215 implementing the ``__json__`` magic method, or by registering
216 adapters with the renderer. See
217 :ref:`json_serializing_custom_objects` for more information.
219 .. note::
221 The default serializer uses ``json.JSONEncoder``. A different
222 serializer can be specified via the ``serializer`` argument. Custom
223 serializers should accept the object, a callback ``default``, and any
224 extra ``kw`` keyword arguments passed during renderer construction.
225 This feature isn't widely used but it can be used to replace the
226 stock JSON serializer with, say, simplejson. If all you want to
227 do, however, is serialize custom objects, you should use the method
228 explained in :ref:`json_serializing_custom_objects` instead
229 of replacing the serializer.
231 .. versionadded:: 1.4
232 Prior to this version, there was no public API for supplying options
233 to the underlying serializer without defining a custom renderer.
234 """
236 def __init__(self, serializer=json.dumps, adapters=(), **kw):
237 """ Any keyword arguments will be passed to the ``serializer``
238 function."""
239 self.serializer = serializer
240 self.kw = kw
241 self.components = Components()
242 for type, adapter in adapters:
243 self.add_adapter(type, adapter)
245 def add_adapter(self, type_or_iface, adapter):
246 """ When an object of the type (or interface) ``type_or_iface`` fails
247 to automatically encode using the serializer, the renderer will use
248 the adapter ``adapter`` to convert it into a JSON-serializable
249 object. The adapter must accept two arguments: the object and the
250 currently active request.
252 .. code-block:: python
254 class Foo(object):
255 x = 5
257 def foo_adapter(obj, request):
258 return obj.x
260 renderer = JSON(indent=4)
261 renderer.add_adapter(Foo, foo_adapter)
263 When you've done this, the JSON renderer will be able to serialize
264 instances of the ``Foo`` class when they're encountered in your view
265 results."""
267 self.components.registerAdapter(
268 adapter, (type_or_iface,), IJSONAdapter
269 )
271 def __call__(self, info):
272 """ Returns a plain JSON-encoded string with content-type
273 ``application/json``. The content-type may be overridden by
274 setting ``request.response.content_type``."""
276 def _render(value, system):
277 request = system.get('request')
278 if request is not None:
279 response = request.response
280 ct = response.content_type
281 if ct == response.default_content_type:
282 response.content_type = 'application/json'
283 default = self._make_default(request)
284 return self.serializer(value, default=default, **self.kw)
286 return _render
288 def _make_default(self, request):
289 def default(obj):
290 if hasattr(obj, '__json__'):
291 return obj.__json__(request)
292 obj_iface = providedBy(obj)
293 adapters = self.components.adapters
294 result = adapters.lookup(
295 (obj_iface,), IJSONAdapter, default=_marker
296 )
297 if result is _marker:
298 raise TypeError('%r is not JSON serializable' % (obj,))
299 return result(obj, request)
301 return default
304json_renderer_factory = JSON() # bw compat
306JSONP_VALID_CALLBACK = re.compile(r"^[$a-z_][$0-9a-z_\.\[\]]+[^.]$", re.I)
309class JSONP(JSON):
310 """ `JSONP <https://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper
311 which implements a hybrid json/jsonp renderer. JSONP is useful for
312 making cross-domain AJAX requests.
314 Configure a JSONP renderer using the
315 :meth:`pyramid.config.Configurator.add_renderer` API at application
316 startup time:
318 .. code-block:: python
320 from pyramid.config import Configurator
322 config = Configurator()
323 config.add_renderer('jsonp', JSONP(param_name='callback'))
325 The class' constructor also accepts arbitrary keyword arguments. All
326 keyword arguments except ``param_name`` are passed to the ``json.dumps``
327 function as its keyword arguments.
329 .. code-block:: python
331 from pyramid.config import Configurator
333 config = Configurator()
334 config.add_renderer('jsonp', JSONP(param_name='callback', indent=4))
336 .. versionchanged:: 1.4
337 The ability of this class to accept a ``**kw`` in its constructor.
339 The arguments passed to this class' constructor mean the same thing as
340 the arguments passed to :class:`pyramid.renderers.JSON` (including
341 ``serializer`` and ``adapters``).
343 Once this renderer is registered via
344 :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use
345 ``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or
346 :meth:`pyramid.config.Configurator.add_view``:
348 .. code-block:: python
350 from pyramid.view import view_config
352 @view_config(renderer='jsonp')
353 def myview(request):
354 return {'greeting':'Hello world'}
356 When a view is called that uses the JSONP renderer:
358 - If there is a parameter in the request's HTTP query string that matches
359 the ``param_name`` of the registered JSONP renderer (by default,
360 ``callback``), the renderer will return a JSONP response.
362 - If there is no callback parameter in the request's query string, the
363 renderer will return a 'plain' JSON response.
365 .. versionadded:: 1.1
367 .. seealso::
369 See also :ref:`jsonp_renderer`.
370 """
372 def __init__(self, param_name='callback', **kw):
373 self.param_name = param_name
374 JSON.__init__(self, **kw)
376 def __call__(self, info):
377 """ Returns JSONP-encoded string with content-type
378 ``application/javascript`` if query parameter matching
379 ``self.param_name`` is present in request.GET; otherwise returns
380 plain-JSON encoded string with content-type ``application/json``"""
382 def _render(value, system):
383 request = system.get('request')
384 default = self._make_default(request)
385 val = self.serializer(value, default=default, **self.kw)
386 ct = 'application/json'
387 body = val
388 if request is not None:
389 callback = request.GET.get(self.param_name)
391 if callback is not None:
392 if not JSONP_VALID_CALLBACK.match(callback):
393 raise HTTPBadRequest(
394 'Invalid JSONP callback function name.'
395 )
397 ct = 'application/javascript'
398 body = '/**/{0}({1});'.format(callback, val)
399 response = request.response
400 if response.content_type == response.default_content_type:
401 response.content_type = ct
402 return body
404 return _render
407@implementer(IRendererInfo)
408class RendererHelper(object):
409 def __init__(self, name=None, package=None, registry=None):
410 if name and '.' in name:
411 rtype = os.path.splitext(name)[1]
412 else:
413 # important.. must be a string; cannot be None; see issue 249
414 rtype = name or ''
416 if registry is None:
417 registry = get_current_registry()
419 self.name = name
420 self.package = package
421 self.type = rtype
422 self.registry = registry
424 @reify
425 def settings(self):
426 settings = self.registry.settings
427 if settings is None:
428 settings = {}
429 return settings
431 @reify
432 def renderer(self):
433 factory = self.registry.queryUtility(IRendererFactory, name=self.type)
434 if factory is None:
435 raise ValueError('No such renderer factory %s' % str(self.type))
436 return factory(self)
438 def get_renderer(self):
439 return self.renderer
441 def render_view(self, request, response, view, context):
442 system = {
443 'view': view,
444 'renderer_name': self.name, # b/c
445 'renderer_info': self,
446 'context': context,
447 'request': request,
448 'req': request,
449 'get_csrf_token': partial(get_csrf_token, request),
450 }
451 return self.render_to_response(response, system, request=request)
453 def render(self, value, system_values, request=None):
454 renderer = self.renderer
455 if system_values is None:
456 system_values = {
457 'view': None,
458 'renderer_name': self.name, # b/c
459 'renderer_info': self,
460 'context': getattr(request, 'context', None),
461 'request': request,
462 'req': request,
463 'get_csrf_token': partial(get_csrf_token, request),
464 }
466 system_values = BeforeRender(system_values, value)
468 registry = self.registry
469 registry.notify(system_values)
470 result = renderer(value, system_values)
471 return result
473 def render_to_response(self, value, system_values, request=None):
474 result = self.render(value, system_values, request=request)
475 return self._make_response(result, request)
477 def _make_response(self, result, request):
478 # broken out of render_to_response as a separate method for testing
479 # purposes
480 response = getattr(request, 'response', None)
481 if response is None:
482 # request is None or request is not a pyramid.response.Response
483 registry = self.registry
484 response_factory = _get_response_factory(registry)
485 response = response_factory(request)
487 if result is not None:
488 if isinstance(result, text_type):
489 response.text = result
490 elif isinstance(result, bytes):
491 response.body = result
492 elif hasattr(result, '__iter__'):
493 response.app_iter = result
494 else:
495 response.body = result
497 return response
499 def clone(self, name=None, package=None, registry=None):
500 if name is None:
501 name = self.name
502 if package is None:
503 package = self.package
504 if registry is None:
505 registry = self.registry
506 return self.__class__(name=name, package=package, registry=registry)
509class NullRendererHelper(RendererHelper):
510 """ Special renderer helper that has render_* methods which simply return
511 the value they are fed rather than converting them to response objects;
512 useful for testing purposes and special case view configuration
513 registrations that want to use the view configuration machinery but do
514 not want actual rendering to happen ."""
516 def __init__(self, name=None, package=None, registry=None):
517 # we override the initializer to avoid calling get_current_registry
518 # (it will return a reference to the global registry when this
519 # thing is called at module scope; we don't want that).
520 self.name = None
521 self.package = None
522 self.type = ''
523 self.registry = None
525 @property
526 def settings(self):
527 return {}
529 def render_view(self, request, value, view, context):
530 return value
532 def render(self, value, system_values, request=None):
533 return value
535 def render_to_response(self, value, system_values, request=None):
536 return value
538 def clone(self, name=None, package=None, registry=None):
539 return self
542null_renderer = NullRendererHelper()