Hide keyboard shortcuts

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 

5 

6from zope.interface import implementer, providedBy 

7from zope.interface.registry import Components 

8 

9from pyramid.interfaces import IJSONAdapter, IRendererFactory, IRendererInfo 

10 

11from pyramid.compat import string_types, text_type 

12 

13from pyramid.csrf import get_csrf_token 

14from pyramid.decorator import reify 

15 

16from pyramid.events import BeforeRender 

17 

18from pyramid.httpexceptions import HTTPBadRequest 

19 

20from pyramid.path import caller_package 

21 

22from pyramid.response import _get_response_factory 

23from pyramid.threadlocal import get_current_registry 

24from pyramid.util import hide_attrs 

25 

26# API 

27 

28 

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). 

34 

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``). 

39 

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. 

47 

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. 

52 

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. 

58 

59 Supply a ``request`` parameter in order to provide the renderer 

60 with the most correct 'system' values (``request`` and ``context`` 

61 in particular). 

62 

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 ) 

73 

74 with hide_attrs(request, 'response'): 

75 result = helper.render(value, None, request=request) 

76 

77 return result 

78 

79 

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. 

87 

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`. 

91 

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. 

99 

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. 

104 

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. 

110 

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. 

117 

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. 

123 

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 ) 

134 

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) 

139 

140 return result 

141 

142 

143def get_renderer(renderer_name, package=None, registry=None): 

144 """ Return the renderer object for the renderer ``renderer_name``. 

145 

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. 

153 

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 

165 

166 

167# concrete renderer factory implementations (also API) 

168 

169 

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 

181 

182 return _render 

183 

184 

185_marker = object() 

186 

187 

188class JSON(object): 

189 """ Renderer that returns a JSON-encoded string. 

190 

191 Configure a custom JSON renderer using the 

192 :meth:`~pyramid.config.Configurator.add_renderer` API at application 

193 startup time: 

194 

195 .. code-block:: python 

196 

197 from pyramid.config import Configurator 

198 

199 config = Configurator() 

200 config.add_renderer('myjson', JSON(indent=4)) 

201 

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`: 

205 

206 .. code-block:: python 

207 

208 from pyramid.view import view_config 

209 

210 @view_config(renderer='myjson') 

211 def myview(request): 

212 return {'greeting':'Hello world'} 

213 

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. 

218 

219 .. note:: 

220 

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. 

230 

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 """ 

235 

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) 

244 

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. 

251 

252 .. code-block:: python 

253 

254 class Foo(object): 

255 x = 5 

256 

257 def foo_adapter(obj, request): 

258 return obj.x 

259 

260 renderer = JSON(indent=4) 

261 renderer.add_adapter(Foo, foo_adapter) 

262 

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.""" 

266 

267 self.components.registerAdapter( 

268 adapter, (type_or_iface,), IJSONAdapter 

269 ) 

270 

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``.""" 

275 

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) 

285 

286 return _render 

287 

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) 

300 

301 return default 

302 

303 

304json_renderer_factory = JSON() # bw compat 

305 

306JSONP_VALID_CALLBACK = re.compile(r"^[$a-z_][$0-9a-z_\.\[\]]+[^.]$", re.I) 

307 

308 

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. 

313 

314 Configure a JSONP renderer using the 

315 :meth:`pyramid.config.Configurator.add_renderer` API at application 

316 startup time: 

317 

318 .. code-block:: python 

319 

320 from pyramid.config import Configurator 

321 

322 config = Configurator() 

323 config.add_renderer('jsonp', JSONP(param_name='callback')) 

324 

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. 

328 

329 .. code-block:: python 

330 

331 from pyramid.config import Configurator 

332 

333 config = Configurator() 

334 config.add_renderer('jsonp', JSONP(param_name='callback', indent=4)) 

335 

336 .. versionchanged:: 1.4 

337 The ability of this class to accept a ``**kw`` in its constructor. 

338 

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``). 

342 

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``: 

347 

348 .. code-block:: python 

349 

350 from pyramid.view import view_config 

351 

352 @view_config(renderer='jsonp') 

353 def myview(request): 

354 return {'greeting':'Hello world'} 

355 

356 When a view is called that uses the JSONP renderer: 

357 

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. 

361 

362 - If there is no callback parameter in the request's query string, the 

363 renderer will return a 'plain' JSON response. 

364 

365 .. versionadded:: 1.1 

366 

367 .. seealso:: 

368 

369 See also :ref:`jsonp_renderer`. 

370 """ 

371 

372 def __init__(self, param_name='callback', **kw): 

373 self.param_name = param_name 

374 JSON.__init__(self, **kw) 

375 

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``""" 

381 

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) 

390 

391 if callback is not None: 

392 if not JSONP_VALID_CALLBACK.match(callback): 

393 raise HTTPBadRequest( 

394 'Invalid JSONP callback function name.' 

395 ) 

396 

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 

403 

404 return _render 

405 

406 

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 '' 

415 

416 if registry is None: 

417 registry = get_current_registry() 

418 

419 self.name = name 

420 self.package = package 

421 self.type = rtype 

422 self.registry = registry 

423 

424 @reify 

425 def settings(self): 

426 settings = self.registry.settings 

427 if settings is None: 

428 settings = {} 

429 return settings 

430 

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) 

437 

438 def get_renderer(self): 

439 return self.renderer 

440 

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) 

452 

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 } 

465 

466 system_values = BeforeRender(system_values, value) 

467 

468 registry = self.registry 

469 registry.notify(system_values) 

470 result = renderer(value, system_values) 

471 return result 

472 

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) 

476 

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) 

486 

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 

496 

497 return response 

498 

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) 

507 

508 

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 .""" 

515 

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 

524 

525 @property 

526 def settings(self): 

527 return {} 

528 

529 def render_view(self, request, value, view, context): 

530 return value 

531 

532 def render(self, value, system_values, request=None): 

533 return value 

534 

535 def render_to_response(self, value, system_values, request=None): 

536 return value 

537 

538 def clone(self, name=None, package=None, registry=None): 

539 return self 

540 

541 

542null_renderer = NullRendererHelper()