diff options
| author | Chris McDonough <chrism@agendaless.com> | 2010-07-26 00:26:10 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2010-07-26 00:26:10 +0000 |
| commit | 250c0218d0bd7dab6ea7e16c7051af71394f2a63 (patch) | |
| tree | d57c38c27b72a483a3db9b1150d20553a93472d8 | |
| parent | 2eb64f7a8bc7830667c3cb924bb5c13be3859b38 (diff) | |
| download | pyramid-250c0218d0bd7dab6ea7e16c7051af71394f2a63.tar.gz pyramid-250c0218d0bd7dab6ea7e16c7051af71394f2a63.tar.bz2 pyramid-250c0218d0bd7dab6ea7e16c7051af71394f2a63.zip | |
merge generic_rendering branch
| -rw-r--r-- | CHANGES.txt | 137 | ||||
| -rw-r--r-- | TODO.txt | 101 | ||||
| -rw-r--r-- | docs/api.rst | 1 | ||||
| -rw-r--r-- | docs/api/configuration.rst | 10 | ||||
| -rw-r--r-- | docs/api/renderers.rst | 13 | ||||
| -rw-r--r-- | docs/glossary.rst | 5 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 66 | ||||
| -rw-r--r-- | docs/narr/templates.rst | 236 | ||||
| -rw-r--r-- | repoze/bfg/chameleon_text.py | 50 | ||||
| -rw-r--r-- | repoze/bfg/chameleon_zpt.py | 50 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 273 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 9 | ||||
| -rw-r--r-- | repoze/bfg/renderers.py | 182 | ||||
| -rw-r--r-- | repoze/bfg/settings.py | 4 | ||||
| -rw-r--r-- | repoze/bfg/testing.py | 116 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_chameleon_text.py | 87 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_chameleon_zpt.py | 86 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 158 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_renderers.py | 307 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_testing.py | 71 |
20 files changed, 1382 insertions, 580 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 2520b8f5e..1dc24a83d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -42,11 +42,46 @@ Features - A new method of the ``Configurator`` exists: ``set_request_factory``. If used, this method will set the factory - used by the :mod:`repoze.bfg` router to create all request objects. + used by the ``repoze.bfg`` router to create all request objects. - The ``Configurator`` constructor takes an additional argument: ``request_factory``. If used, this argument will set the factory - used by the :mod:`repoze.bfg` router to create all request objects. + used by the ``repoze.bfg`` router to create all request objects. + +- The ``Configurator`` constructor takes an additional argument: + ``request_factory`. If used, this argument will set the factory + used by the ``repoze.bfg`` router to create all request objects. + +- A new method of the ``Configurator`` exists: + ``set_renderer_globals_factory``. If used, this method will set the + factory used by the ``repoze.bfg`` router to create renderer + globals. + +- A new method of the ``Configurator`` exists: ``get_settings``. If + used, this method will return the current settings object (performs + the same job as the ``repoze.bfg.settings.get_settings`` API). + +- The ``Configurator`` constructor takes an additional argument: + ``renderer_globals_factory`. If used, this argument will set the + factory used by the ``repoze.bfg`` router to create renderer + globals. + +- Add ``repoze.bfg.renderers.render``, + ``repoze.bfg.renderers.render_to_response`` and + ``repoze.bfg.renderers.get_renderer`` functions. These are + imperative APIs which will use the same rendering machinery used by + view configurations with a ``renderer=`` attribute/argument to + produce a rendering or renderer. Because these APIs provide a + central API for all rendering, they now form the preferred way to + perform imperative template rendering. Using functions named + ``render_*` from modules such as ``repoze.bfg.chameleon_zpt`` and + ``repoze.bfg.chameleon_text`` is now discouraged (although not + deprecated). The code the backing older templating-system-specific + APIs now calls into the newer ``repoze.bfg.renderer`` code. + +- The ``repoze.bfg.configuration.Configurator.testing_add_template`` + has been renamed to ``testing_add_renderer``. A backwards + compatibility alias is present using the old name. Documentation ------------- @@ -57,6 +92,15 @@ Documentation - The ``Hooks`` narrative chapter now contains a section about changing the request factory. +- The API documentation includes a new module: + ``repoze.bfg.renderers``. + +- The ``Templates`` chapter was updated; all narrative that used + templating-specific APIs within examples to perform rendering (such + as the ``repoze.bfg.chameleon_zpt.render_template_to_response`` + method) was changed to use ``repoze.bfg.renderers.render_*`` + functions. + Bug Fixes --------- @@ -67,6 +111,34 @@ Bug Fixes ``TypeError: expected string or buffer`` exception. Now, the predicate returns False as intended. +Deprecations +------------ + +- The ``repoze.bfg.renderers.rendered_response`` function was never an + official API, but may have been imported by extensions in the wild. + It is officially deprecated in this release. Use + ``repoze.bfg.renderers.render_to_response`` instead. + +- The following APIs are *documentation* deprecated (meaning they are + officially deprecated in documentation but do not raise a + deprecation error upon their usage, and may continue to work for an + indefinite period of time): + + In the ``repoze.bfg.chameleon_zpt`` module: ``get_renderer``, + ``get_template``, ``render_template``, + ``render_template_to_response``. The suggested alternatives are + documented within the docstrings of those methods (which are still + present in the documentation). + + In the ``repoze.bfg.chameleon_text`` module: ``get_renderer``, + ``get_template``, ``render_template``, + ``render_template_to_response``. The suggested alternatives are + documented within the docstrings of those methods (which are still + present in the documentation). + + In general, to perform template-related functions, one should now + use the various methods in the ``repoze.bfg.renderers`` module. + Backwards Incompatibilities --------------------------- @@ -98,6 +170,67 @@ Backwards Incompatibilities the logic which raised the ``NotFound`` exception in the view out into a custom view predicate. +- If, when you run your application's unit test suite under BFG 1.3, a + ``KeyError`` naming a template or a ``ValueError`` indicating that a + 'renderer factory' is not registered may is raised + (e.g. ``ValueError: No factory for renderer named '.pt' when looking + up karl.views:templates/snippets.pt``), you may need to perform some + extra setup in your test code. + + The best solution is to use the + ``repoze.bfg.configuration.Configurator.testing_add_renderer`` (or, + alternately the deprecated + ``repoze.bfg.testing.registerTemplateRenderer`` or + ``registerDummyRenderer``) API within the code comprising each + individual unit test suite to register a "dummy" renderer for each + of the templates and renderers used by code under test. For + example:: + + config = Configurator() + config.testing_add_renderer('karl.views:templates/snippets.pt') + + This will register a basic dummy renderer for this particular + missing template. The ``testing_add_renderer`` API actually + *returns* the renderer, but if you don't care about how the render + is used, you don't care about having a reference to it either. + + A more rough way to solve the issue exists. It causes the "real" + template implementations to be used while the system is under test, + which is suboptimal, because tests will run slower, and unit tests + won't actually *be* unit tests, but it is easier. Always ensure you + call the ``setup_registry()`` method of the Configurator . Eg:: + + reg = MyRegistry() + config = Configurator(registry=reg) + config.setup_registry() + + Calling ``setup_registry`` only has an effect if you're *passing in* + a ``registry`` argument to the Configurator constructor. + ``setup_registry`` is called by the course of normal operations + anyway if you do not pass in a ``registry``. + + If your test suite isn't using a Configurator yet, and is still + using the older ``repoze.bfg.testing`` APIs name ``setUp`` or + ``cleanUp``, these will register the renderers on your behalf. + + A variant on the symptom for this theme exists: you may already be + dutifully registering a dummy template or renderer for a template + used by the code you're testing using ``testing_register_renderer`` + or ``registerTemplateRenderer``, but (perhaps unbeknownst to you) + the code under test expects to be able to use a "real" template + renderer implementation to retrieve or render *another* template + that you forgot was being rendered as a side effect of calling the + code you're testing. This happened to work because it found the + *real* template while the system was under test previously, and now + it cannot. The solution is the same. + + It may also help reduce confusion to use a *resource specification* + to specify the template path in the test suite and code rather than + a relative path in either. A resource specification is unambiguous, + while a relative path needs to be relative to "here", where "here" + isn't always well-defined ("here" in a test suite may or may not be + the same as "here" in the code under test). + 1.3a5 (2010-07-14) ================== @@ -15,50 +15,10 @@ - ``decorator=`` parameter to bfg_view. -Renderer overhaul ------------------- - -Currently the division of responsibility between the BFG configurator -and a BFG renderer implementation is awkward and wrong. - -- Renderer factories have no ability to convert a raw ``renderer=`` - path (e.g. ``templates/foo.pt```) into something internally - meaningful. Instead, BFG mangles the string into a package-relative - spec before it is passed to the renderer factory. This is wrong, as - some renderers may not be interested in package-relative specs at - all (for instance, loader-style renderers which have a hardcoded set - of template locations). The reason, however, that BFG currently - does it this way is that the raw renderer path alone does not - contain enough information itself to be useful; knowledge of the - *package* is also required for package-based renderers to make sense - of relative renderer strings (e.g. ``templates/foo.pt`` could mean - the ``templates/foo.pt`` file within the ``mypackage`` package). - - To fix this, we need to provide some way to pass the package name to - the renderer factory as well as the renderer path. But the package - name isn't the only thing an *arbitrary* renderer might need. - Another renderer might need, for example, a deployment setting. So - we'll need to identify all the crap that *might* be useful to a - renderer factory and we'll need to pass all of it into the renderer - factory as a garbage barge dictionary; individual renderers will - make use of whatever they can from that garbage barge dictionary. - Garbage barge dict item candidates: ``package`` (the "current" - package), ``config`` (the configurator), ``package_name`` (the - current package's ``__name__``), ``settings`` (the deployment - settings), ``registry`` (the component registry). - -- A BFG renderer currently returns a *string*. It would be more - symmetric if a renderer always returned a Response object. Then the - calling machinery inside BFG could treat a view which happened to - use a renderer exactly the same as a view which returns a response - directly. Maybe. Problem: varying response attributes such as - ``content-type``, etc only makes sense when the view callable uses a - renderer; not when it doesn't, so there's some asymmetry there. - Maybe we make renderers return Responses, but still keep the - attribute-inspection stuff inside BFG, only used when we call a view - which we know uses a renderer. We *could* always call the attribute - inspection stuff, but it would be a slowdown in cases where views - really do always return a Response directly. +- Try to better explain the relationship between a renderer and a + template in the templates chapter and elsewhere. Scan the + documentation for reference to a renderer as *only* view + configuration (it's a larger concept now). - The ``system`` value passed to a renderer is not extensible. It should be extensible on a per-application basis. For example, you @@ -71,56 +31,3 @@ and a BFG renderer implementation is awkward and wrong. ``system`` values available to it as templates renderered via a view renderer. -To at least partially ameliorate the above, renderer factories should -be changed to things that have a set of interfaces something like -this:: - - class IRendererFactory(Interface): - def __call__(path, info): - "" Return an IRenderer.""" - - class IRenderer(Interface): - def __call__(value, system): - """ Return a Response """ - -A semi-pseudocode example: - - from webob import Response - - class SampleRendererFactory(object): - def __init__(self, **config): - self.config = config - - def __call__(self, path, info): - path = do_something_to_evaluate_path_using_info(path, info) - search_path = self.config['search_path'] - debug = self.config['debug'] - def renderer(value, system): - string = do_rendering(search_path, debug, path, value, system) - return Response(string) - return renderer - - if __name__ == '__main__': - - def view1(request): - return {'a':1} - - def view2(request): - return {'a':2} - - renderer_factory = SampleRendererFactory(search_path=['/a', '/b']) - - package_name = 'some.package' - - for view, path in [ - (view1, 'templates/foo.pt'), - (view2, 'templates/bar.pt'), - ]: - renderer = renderer_factory(path, dict(package_name=package_name)) - register_renderer_for_view(renderer, view) - -This is mostly an amelioration for the first and second bullet points -above. The second two bullet points are not addressed by it. - -Also: think about generalizing this a bit into something which wraps a -view callable like a decorator, which can influence input and output. diff --git a/docs/api.rst b/docs/api.rst index a97c79fa9..3bdb323ca 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -19,6 +19,7 @@ documentation is organized alphabetically by module name. api/interfaces api/location api/paster + api/renderers api/router api/scripting api/security diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index 68e3b8724..f555b18f0 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -5,7 +5,7 @@ .. automodule:: repoze.bfg.configuration - .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None) + .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None, renderer_globals_factory=None) .. attribute:: registry @@ -20,7 +20,9 @@ .. automethod:: unhook_zca() - .. automethod:: setup_registry(settings=None, root_factory=None, authentication_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None) + .. automethod:: get_settings + + .. automethod:: setup_registry(settings=None, root_factory=None, authentication_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None, renderer_globals_factory=None) .. automethod:: add_renderer(name, factory) @@ -54,11 +56,13 @@ .. automethod:: set_request_factory + .. automethod:: set_renderer_globals_factory + .. automethod:: testing_securitypolicy .. automethod:: testing_models .. automethod:: testing_add_subscriber - .. automethod:: testing_add_template + .. automethod:: testing_add_renderer diff --git a/docs/api/renderers.rst b/docs/api/renderers.rst new file mode 100644 index 000000000..775b8d8d8 --- /dev/null +++ b/docs/api/renderers.rst @@ -0,0 +1,13 @@ +.. _renderers_module: + +:mod:`repoze.bfg.renderers` +--------------------------- + +.. module:: repoze.bfg.renderers + +.. autofunction:: get_renderer + +.. autofunction:: render + +.. autofunction:: render_to_response + diff --git a/docs/glossary.rst b/docs/glossary.rst index a1adec50b..ce0d57111 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -756,3 +756,8 @@ Glossary cultural context. Often shortened to "l10" (because the word "localization" is L, 10 letters, then N). See also: :term:`Internationalization`. + + renderer globals + Values injected as names into a renderer based on application + policy. See :ref:`adding_renderer_globals` for more + information. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 0614b48fd..89f126b07 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -394,6 +394,72 @@ method: config = Configurator() config.set_request_factory(MyRequestFactory) +.. _adding_renderer_globals: + +Adding Renderer Globals +----------------------- + +Whenever :mod:`repoze.bfg` handles a request to perform a rendering +(after a view with a ``renderer=`` configuration attribute is invoked, +or when the any of the methods beginning with ``render`` within the +:mod:`repoze.bfg.renderers` module are called, *renderer globals* can +be injected into the *system* values sent to the renderer. By +default, no renderer globals are injected, and the "bare" system +values (such as ``request``, ``context``, and ``renderer_name``) are +the only values present in the system dictionary passed to every +renderer. + +A callback that :mod:`repoze.bfg` will call every time a renderer is +invoked can be added by passing a ``renderer_globals_factory`` +argument to the constructor of the :term:`configurator`. + +.. code-block:: python + :linenos: + + def globals_factory(system): + return {'a':1} + + config = Configurator(renderer_globals_factory=globals_factory) + +Such a callback must accept a single positional argument (notionally +named ``system``) which will contain the original system values. It +must return a dictionary of values that will be merged into the system +dictionary. + +A renderer globals factory can alternately be registered via ZCML as a +through the use of the ZCML ``utility`` directive. In the below, we +assume a ``globals_factory`` function lives in a package named +``mypackage.mymodule``. + +.. code-block:: xml + :linenos: + + <utility + component="mypackage.mymodule.renderer_globals_factory" + provides="repoze.bfg.interfaces.IRendererGlobalsFactory" + /> + +Lastly, if you're doing imperative configuration, and you'd rather do +it after you've already constructed a :term:`configurator` it can also +be registered via the +:meth:`repoze.bfg.configuration.Configurator.set_renderer_globals_factory` +method: + +.. code-block:: python + :linenos: + + from repoze.bfg.configuration import Configurator + from repoze.bfg.request import Request + +.. code-block:: python + :linenos: + + def globals_factory(system): + return {'a':1} + + config = Configurator() + config.set_renderer_globals_factory(globals_factory) + .. _registering_configuration_decorators: Registering Configuration Decorators diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index e289df1f9..dce0733b6 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -31,46 +31,109 @@ The most straightforward way to use a template within given templating engine to do so. :mod:`repoze.bfg` provides various APIs that allow you to render -:term:`Chameleon` templates directly from within a view callable. For -example, if there is a :term:`Chameleon` ZPT template named ``foo.pt`` -in a directory in your application named ``templates``, you can render -the template from within the body of a view callable like so: +templates directly from within a view callable. For example, if there +is a :term:`Chameleon` ZPT template named ``foo.pt`` in a directory in +your application named ``templates``, you can render the template from +within the body of a view callable like so: .. code-block:: python :linenos: - from repoze.bfg.chameleon_zpt import render_template_to_response + from repoze.bfg.renderers import render_to_response def sample_view(request): - return render_template_to_response('templates/foo.pt', foo=1, bar=2) + return render_to_response('templates/foo.pt', foo=1, bar=2, + request=request) + +.. warning:: Earlier iterations of this documentation +.. (pre-version-1.3) encouraged the application developer to use + ZPT-specific APIs such as + :func:`repoze.bfg.chameleon_zpt.render_template_to_response`, + :func:`repoze.bfg.chameleon_zpt.render_template_to_iterable`, and + :func:`repoze.bfg.chameleon_zpt.render_template` to render + templates directly. This style of rendering still works, but at + least for purposes of this documentation, those functions are + deprecated. Application developers are encouraged instead to use + the functions available in the :mod:`repoze.bfg.renderers` module + to perform rendering tasks. This set of functions works to render + templates for all renderer extensions registered with + :mod:`repoze.bfg`. The ``sample_view`` :term:`view callable` above returns a :term:`response` object which contains the body of the -``template/foo.pt`` template. The template author will have the names +``templates/foo.pt`` template. In this case, the ``templates`` +directory should live in the same directory as the module containing +the ``sample_view`` function. The template author will have the names ``foo`` and ``bar`` available as top-level names for replacement or comparison purposes. +In the example above, the path ``templates/foo.pt`` is relative to the +directory in which the file which defines the view configuration +lives. In this case, this is the directory containing the file that +defines the ``sample_view`` function. Although a renderer path is +usually just a simple relative pathname, a path named as a renderer +can be absolute, starting with a slash on UNIX or a drive letter +prefix on Windows. + +The path can alternately be a :term:`resource specification` in the +form ``some.dotted.package_name:relative/path``, making it possible to +address template resources which live in another package. For +example: + +.. code-block:: python + :linenos: + + from repoze.bfg.renderers import render_to_response + + def sample_view(request): + return render_to_response('mypackage:templates/foo.pt', foo=1, bar=2, + request=request) + +A resource specification points at a file within a Python *package*. +In this case, it points at a file named ``foo.pt`` within the +``templates`` directory of the ``mypackage`` package. Using a +resource specification instead of a relative template name is usually +a good idea, because calls to ``render_to_response`` using resource +specifications will continue to work properly if you move the code +containing them around. + +In the examples above we pass in a keyword argument named ``request`` +representing the current :mod:`repoze.bfg` request. Passing a request +keyword argument will cause the ``render_to_response`` function to +supply the renderer with more correct system values (see +:ref:`renderer_system_values`), because most of the information +required to compose proper system values is present in the request. +If you care about the correct system values being provided to the +renderer being called (in particular, if your template relies on the +name ``request`` or ``context``, or if you've configured special +:term:`renderer globals` make sure to pass ``request`` as a keyword +argument in every call to to a ``repoze.bfg.renderers.render_*`` +function. + Every view must return a :term:`response` object (except for views -which use a :term:`renderer`, which we'll see shortly). The -:func:`repoze.bfg.chameleon_zpt.render_template_to_response` function -is a shortcut function that actually returns a response object, but -not all template APIs know about responses. When you use a template -API that is "response-ignorant" you can also easily render a template -to a string, and construct your own response object as necessary with -the string as the body. - -For example, the :func:`repoze.bfg.chameleon_zpt.render_template` API -returns a string. We can manufacture a :term:`response` object -directly, and use that string as the body of the response: +which use a :term:`renderer` named via view configuration, which we'll +see shortly). The :func:`repoze.bfg.renders.render_to_response` +function is a shortcut function that actually returns a response +object. + +Obviously not all APIs you might call to get respnonse data will +return a response object. If you call a "response-ignorant" API that +returns information you'd like to use as a response (such as when you +render a template to a string), you must construct your own response +object as necessary with the string as the body. For example, the +:func:`repoze.bfg.renderers.render` API returns a string. We can +manufacture a :term:`response` object directly, and use that string as +the body of the response: .. code-block:: python :linenos: - from repoze.bfg.chameleon_zpt import render_template + from repoze.bfg.renderers import render from webob import Response def sample_view(request): - result = render_template('templates/foo.pt', foo=1, bar=2) + result = render('mypackage:templates/foo.pt', foo=1, bar=2, + request=request) response = Response(result) return response @@ -83,7 +146,7 @@ functions into your views module, use those APIs to generate a string, then return that string as the body of a :term:`WebOb` :term:`Response` object. -For example, here's an example of using `Mako +For example, here's an example of using raw `Mako <http://www.makotemplates.org/>`_ from within a :mod:`repoze.bfg` :term:`view`: @@ -100,6 +163,13 @@ For example, here's an example of using `Mako response = Response(result) return response +You probably wouldn't use this particular snippet in a project, +because it's easier to use the Mako renderer bindings which already +exist for :mod:`repoze.bfg` named ``repoze.bfg.mako`` (available from +`PyPI <http://pypi.python.org/pypi/repoze.bfg.mako>`_). But if your +favorite templating system is not supported as a renderer extension +for BFG, you can create your own simple conmbination as shown above. + .. note:: If you use third-party templating languages without cooperating BFG @@ -121,29 +191,31 @@ may set attributes on the response that influence these values. Here's an example of changing the content-type and status of the response object returned by -:func:`repoze.bfg.chameleon_zpt.render_template_to_response`: +:func:`repoze.bfg.renderers.render_to_response`: .. code-block:: python :linenos: - from repoze.bfg.chameleon_zpt import render_template_to_response + from repoze.bfg.renderers.render_to_response def sample_view(request): - response = render_template_to_response('templates/foo.pt', foo=1, bar=2) + response = render_to_response('templates/foo.pt', foo=1, bar=2, + request=request) response.content_type = 'text/plain' response.status_int = 204 return response -Here's an example of manufacturing a response object using the result of -:func:`repoze.bfg.chameleon_zpt.render_template` (a string): +Here's an example of manufacturing a response object using the result +of :func:`repoze.bfg.renderers.render` (a string): .. code-block:: python :linenos: - from repoze.bfg.chameleon_zpt import render_template + from repoze.bfg.renderers import render from webob import Response def sample_view(request): - result = render_template('templates/foo.pt', foo=1, bar=2) + result = render('mypackage:templates/foo.pt', foo=1, bar=2, + request=request) response = Response(result) response.content_type = 'text/plain' return response @@ -153,22 +225,50 @@ Here's an example of manufacturing a response object using the result of single: template renderers single: renderer (template) + +.. _renderer_system_values: + +System Values Used During Rendering +----------------------------------- + +When a template is rendered using +:func:`repoze.bfg.render_to_response` or :func:`repoze.bfg.render`, +the renderer representing the template will be provided with a number +of *system* values. These values are provided in a dictionary to the +renderer and include: + +``context`` + The current :mod:`repoze.bfg` context if ``request`` was provided as + a keyword argument or ``None``. + +``request`` + The request provided as a keyword argument. + +``renderer_name`` + The renderer name used to perform the rendering, + e.g. ``mypackage:templates/foo.pt``. + +What any particular renderer does with them is up to the renderer +itself, but most renderers, including al Chameleon renderers, make +these names available as top-level template variables. + .. _templates_used_as_renderers: -Templates Used as Renderers ---------------------------- +Templates Used as Renderers via Configuration +--------------------------------------------- -Instead of using templating system APIs within the body of a view -function directly to render a specific template, you may associate a -template written in a supported templating language with a view -indirectly by specifying it as a :term:`renderer`. +Instead of using the :func:`repoze.bfg.renderers.render_to_response` +API within the body of a view function directly to render a specific +template to a response, you may associate a template written in a +supported templating language with a view indirectly by specifying it +as a :term:`renderer` in *view configuration*. -To use a renderer, specify a template :term:`resource specification` -as the ``renderer`` argument or attribute to the :term:`view -configuration` of a :term:`view callable`. Then return a *dictionary* -from that view callable. The dictionary items returned by the view -callable will be made available to the renderer template as top-level -names. +To use a renderer via view configuration, specify a template +:term:`resource specification` as the ``renderer`` argument or +attribute to the :term:`view configuration` of a :term:`view +callable`. Then return a *dictionary* from that view callable. The +dictionary items returned by the view callable will be made available +to the renderer template as top-level names. The association of a template as a renderer for a :term:`view configuration` makes it possible to replace code within a :term:`view @@ -187,6 +287,18 @@ template renderer: def my_view(request): return {'foo':1, 'bar':2} +.. note:: It is not necessary to supply the ``request`` value as a key + in the dictionary result returned from a renderer-configured view + callable in order to ensure that the "most correct" system values + are supplied to the renderer as it is when you use + :func:`repoze.bfg.renderers.render` or + :func:`repoze.bfg.renderers.render_to_response`. This is handled + automatically. + +Similar renderer configuration can be done imperatively and via +:term:`ZCML`. See :ref:`views_which_use_a_renderer`. See also +:ref:`built_in_renderers`. + The ``renderer`` argument to the ``@bfg_view`` configuration decorator shown above is the template *path*. In the example above, the path ``templates/foo.pt`` is *relative*. Relative to what, you ask? @@ -201,13 +313,6 @@ a :term:`resource specification` in the form ``some.dotted.package_name:relative/path``, making it possible to address template resources which live in another package. -When a template :term:`renderer` is used to render the result of a -view callable, several names are passed into the template as top-level -names by default, including ``context`` and ``request``. Similar -renderer configuration can be done imperatively and via :term:`ZCML`. -See :ref:`views_which_use_a_renderer`. See also -:ref:`built_in_renderers`. - Not just any template from any arbitrary templating system may be used as a renderer. Bindings must exist specifically for :mod:`repoze.bfg` to use a templating language template as a renderer. Currently, @@ -217,17 +322,18 @@ discussion of their details. :mod:`repoze.bfg` also supports the use of :term:`Jinja2` templates as renderers. See :ref:`available_template_system_bindings`. -.. sidebar:: Why Use A Renderer - - Using a renderer is usually a better way to render templates than - using any templating API directly from within a :term:`view - callable` because it makes the view callable more unit-testable. - Views which use templating APIs directly must return a - :term:`Response` object. Making testing assertions about response - objects is typically an indirect process, because it means that - your test code often needs to somehow parse information - out of the response body (often HTML). View callables which use - renderers typically return a dictionary, and making assertions +.. sidebar:: Why Use A Renderer via View Configuration + + Using a renderer in view configuration is usually a better way to + render templates than using any rendering API directly from within + a :term:`view callable` because it makes the view callable more + unit-testable. Views which use templating or rendering APIs + directly must return a :term:`Response` object. Making testing + assertions about response objects is typically an indirect process, + because it means that your test code often needs to somehow parse + information out of the response body (often HTML). View callables + which are configured with renderers externally via view + configuration typically return a dictionary, and making assertions about the information is almost always more direct than needing to parse HTML. Specifying a renderer from within :term:`ZCML` (as opposed to imperatively or via a ``bfg_view`` decorator, or using a @@ -244,6 +350,11 @@ status attributes, you must set attributes on the *request* object within the view before returning the dictionary. See :ref:`response_request_attrs` for more information. +The same set of system values are provided to templates rendered via a +rendere view configuration as those provided to templates rendered +imperatively. See :ref:`renderer_system_values`. + + .. index:: single: Chameleon ZPT templates single: ZPT templates (Chameleon) @@ -285,10 +396,6 @@ the template as a :term:`renderer` like so: def my_view(request): return {'foo':1, 'bar':2} -If you'd rather use templates directly within a view callable (without -the indirection of using a renderer), see :ref:`chameleon_zpt_module` -for the API description. - See also :ref:`built_in_renderers` for more general information about renderers, including Chameleon ZPT renderers. @@ -323,9 +430,8 @@ Here's what a simple :term:`Chameleon` ZPT template used under Note the use of :term:`Genshi` -style ``${replacements}`` above. This is one of the ways that :term:`Chameleon` ZPT differs from standard ZPT. The above template expects to find a ``project`` key in the set -of keywords passed in to it via -:func:`repoze.bfg.chameleon_zpt.render_template` or -:func:`repoze.bfg.render_template_to_response`. Typical ZPT +of keywords passed in to it via :func:`repoze.bfg.renderers.render` or +:func:`repoze.bfg.renderers.render_to_response`. Typical ZPT attribute-based syntax (e.g. ``tal:content`` and ``tal:replace``) also works in these templates. diff --git a/repoze/bfg/chameleon_text.py b/repoze/bfg/chameleon_text.py index 7aca29529..bbf15ead1 100644 --- a/repoze/bfg/chameleon_text.py +++ b/repoze/bfg/chameleon_text.py @@ -1,7 +1,5 @@ import sys -from webob import Response - from zope.interface import implements try: @@ -20,12 +18,12 @@ except ImportError: # pragma: no cover class Parser(object): pass -from repoze.bfg.interfaces import IResponseFactory from repoze.bfg.interfaces import ITemplateRenderer from repoze.bfg.interfaces import IChameleonTranslate from repoze.bfg.decorator import reify -from repoze.bfg.renderers import template_renderer_factory +from repoze.bfg import renderers +from repoze.bfg.path import caller_package from repoze.bfg.settings import get_settings from repoze.bfg.threadlocal import get_current_registry @@ -40,7 +38,7 @@ class TextTemplateFile(TemplateFile): doctype, **kwargs) def renderer_factory(path): - return template_renderer_factory(path, TextTemplateRenderer) + return renderers.template_renderer_factory(path, TextTemplateRenderer) class TextTemplateRenderer(object): implements(ITemplateRenderer) @@ -82,15 +80,26 @@ def get_renderer(path): :term:`Chameleon` text template using the template implied by the ``path`` argument. The ``path`` argument may be a package-relative path, an absolute path, or a :term:`resource - specification`.""" - return renderer_factory(path) + specification`. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + :func:`repoze.bfg.renderers.get_renderer` instead. + """ + package = caller_package() + return renderers.renderer_from_name(path, package) def get_template(path): """ Return the underyling object representing a :term:`Chameleon` text template using the template implied by the ``path`` argument. The ``path`` argument may be a package-relative path, an absolute - path, or a :term:`resource specification`.""" - renderer = renderer_factory(path) + path, or a :term:`resource specification`. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + the ``implementation()`` method of a template renderer retrieved via + :func:`repoze.bfg.renderers.get_renderer` instead. + """ + package = caller_package() + renderer = renderers.renderer_from_name(path, package) return renderer.implementation() def render_template(path, **kw): @@ -99,9 +108,13 @@ def render_template(path, **kw): package-relative path, an absolute path, or a :term:`resource specification`. The arguments in ``*kw`` are passed as top-level names to the template, and so may be used within the template - itself. Returns a string.""" - renderer = renderer_factory(path) - return renderer(kw, {}) + itself. Returns a string. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + :func:`repoze.bfg.renderers.render` instead. + """ + package = caller_package() + return renderers._render(path, None, kw, {}, None, package) def render_template_to_response(path, **kw): """ Render a :term:`Chameleon` text template using the template @@ -110,9 +123,10 @@ def render_template_to_response(path, **kw): specification`. The arguments in ``*kw`` are passed as top-level names to the template, and so may be used within the template itself. Returns a :term:`Response` object with the body as the - template result..""" - renderer = renderer_factory(path) - result = renderer(kw, {}) - reg = get_current_registry() - response_factory = reg.queryUtility(IResponseFactory, default=Response) - return response_factory(result) + template result. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + :func:`repoze.bfg.renderers.render_to_response` instead. + """ + package = caller_package() + return renderers._render_to_response(path, None, kw, {}, None, package) diff --git a/repoze/bfg/chameleon_zpt.py b/repoze/bfg/chameleon_zpt.py index ba4c9863f..494db43d2 100644 --- a/repoze/bfg/chameleon_zpt.py +++ b/repoze/bfg/chameleon_zpt.py @@ -1,7 +1,5 @@ import sys -from webob import Response - from zope.interface import implements try: @@ -14,16 +12,16 @@ except ImportError: # pragma: no cover raise ImportError, exc, tb from repoze.bfg.interfaces import IChameleonTranslate -from repoze.bfg.interfaces import IResponseFactory from repoze.bfg.interfaces import ITemplateRenderer from repoze.bfg.decorator import reify -from repoze.bfg.renderers import template_renderer_factory +from repoze.bfg.path import caller_package +from repoze.bfg import renderers from repoze.bfg.settings import get_settings from repoze.bfg.threadlocal import get_current_registry def renderer_factory(path): - return template_renderer_factory(path, ZPTTemplateRenderer) + return renderers.template_renderer_factory(path, ZPTTemplateRenderer) class ZPTTemplateRenderer(object): implements(ITemplateRenderer) @@ -65,15 +63,26 @@ def get_renderer(path): :term:`Chameleon` ZPT template using the template implied by the ``path`` argument. The ``path`` argument may be a package-relative path, an absolute path, or a :term:`resource - specification`.""" - return renderer_factory(path) + specification`. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + :func:`repoze.bfg.renderers.get_renderer` instead. + """ + package = caller_package() + return renderers.renderer_from_name(path, package) def get_template(path): """ Return the underlying object representing a :term:`Chameleon` ZPT template using the template implied by the ``path`` argument. The ``path`` argument may be a package-relative path, an absolute - path, or a :term:`resource specification`.""" - renderer = renderer_factory(path) + path, or a :term:`resource specification`. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + the ``implementation()`` method of a template renderer retrieved via + :func:`repoze.bfg.renderers.get_renderer` instead. + """ + package = caller_package() + renderer = renderers.renderer_from_name(path, package) return renderer.implementation() def render_template(path, **kw): @@ -82,9 +91,13 @@ def render_template(path, **kw): package-relative path, an absolute path, or a :term:`resource specification`. The arguments in ``*kw`` are passed as top-level names to the template, and so may be used within the template - itself. Returns a string.""" - renderer = renderer_factory(path) - return renderer(kw, {}) + itself. Returns a string. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + :func:`repoze.bfg.renderers.render` instead. + """ + package = caller_package() + return renderers._render(path, None, kw, {}, None, package) def render_template_to_response(path, **kw): """ Render a :term:`Chameleon` ZPT template using the template @@ -93,10 +106,11 @@ def render_template_to_response(path, **kw): specification`. The arguments in ``*kw`` are passed as top-level names to the template, and so may be used within the template itself. Returns a :term:`Response` object with the body as the - template result..""" - renderer = renderer_factory(path) - result = renderer(kw, {}) - reg = get_current_registry() - response_factory = reg.queryUtility(IResponseFactory, default=Response) - return response_factory(result) + template result. + + .. warning:: This API is deprecated in :mod:`repoze.bfg` 1.3. Use + :func:`repoze.bfg.renderers.render_to_response` instead. + """ + package = caller_package() + return renderers._render_to_response(path, None, kw, {}, None, package) diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 443da9d61..124ce076a 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -4,7 +4,6 @@ import sys import threading import inspect -from webob import Response import venusian from translationstring import ChameleonTranslate @@ -26,16 +25,15 @@ from repoze.bfg.interfaces import ILocaleNegotiator from repoze.bfg.interfaces import IMultiView from repoze.bfg.interfaces import IPackageOverrides from repoze.bfg.interfaces import IRendererFactory +from repoze.bfg.interfaces import IRendererGlobalsFactory from repoze.bfg.interfaces import IRequest from repoze.bfg.interfaces import IRequestFactory -from repoze.bfg.interfaces import IResponseFactory from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import IRouteRequest from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import ISecuredView from repoze.bfg.interfaces import ISettings from repoze.bfg.interfaces import IStaticURLInfo -from repoze.bfg.interfaces import ITemplateRenderer from repoze.bfg.interfaces import ITranslationDirectories from repoze.bfg.interfaces import ITraverser from repoze.bfg.interfaces import IView @@ -44,6 +42,7 @@ from repoze.bfg.interfaces import IViewClassifier from repoze.bfg import chameleon_text from repoze.bfg import chameleon_zpt from repoze.bfg import renderers +from repoze.bfg.renderers import _render_to_response from repoze.bfg.authorization import ACLAuthorizationPolicy from repoze.bfg.compat import all from repoze.bfg.compat import md5 @@ -91,7 +90,8 @@ class Configurator(object): The Configurator accepts a number of arguments: ``registry``, ``package``, ``settings``, ``root_factory``, ``authentication_policy``, ``authorization_policy``, ``renderers`` - ``debug_logger``, ``locale_negotiator``, and ``request_factory``. + ``debug_logger``, ``locale_negotiator``, ``request_factory``, and + ``renderer_globals_factory``. If the ``registry`` argument is passed as a non-``None`` value, it must be an instance of the :class:`repoze.bfg.registry.Registry` @@ -118,7 +118,8 @@ class Configurator(object): If the ``settings`` argument is passed, it should be a Python dictionary representing the deployment settings for this application. These are later retrievable using the - :func:`repoze.bfg.settings.get_settings` API. + :meth:`repoze.bfg.configuration.Configurator.get_settings` and + :func:`repoze.bfg.settings.get_settings` APIs. If the ``root_factory`` argument is passed, it should be an object representing the default :term:`root factory` for your @@ -150,28 +151,21 @@ class Configurator(object): :term:`locale negotiator` implementation. See :ref:`custom_locale_negotiator`. - If ``request_factory`` is passed, it should be an object that implements - the same methods and attributes as the :class:`repoze.bfg.request.Request` - class (particularly ``__call__`` and ``blank``). This will be the - factory used by the :mod:`repoze.bfg` router to create all request - objects. If this attribute is ``None``, - the :class:`repoze.bfg.request.Request` class will be used as the - request factory. - - .. note:: The - :meth:`repoze.bfg.configuration.Configurator.set_request_factory` - method can be used to achieve the same purpose as passing - ``request_factory``to the Configurator constructor any time after the - configurator has been constructed. """ manager = manager # for testing injection venusian = venusian # for testing injection - def __init__(self, registry=None, package=None, settings=None, - root_factory=None, authentication_policy=None, - authorization_policy=None, renderers=DEFAULT_RENDERERS, + def __init__(self, + registry=None, + package=None, + settings=None, + root_factory=None, + authentication_policy=None, + authorization_policy=None, + renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, - request_factory=None): + request_factory=None, + renderer_globals_factory=None): self.package = package or caller_package() self.registry = registry if registry is None: @@ -185,7 +179,9 @@ class Configurator(object): renderers=renderers, debug_logger=debug_logger, locale_negotiator=locale_negotiator, - request_factory=request_factory) + request_factory=request_factory, + renderer_globals_factory=renderer_globals_factory + ) def _set_settings(self, mapping): settings = Settings(mapping or {}) @@ -200,15 +196,8 @@ class Configurator(object): factory = DefaultRootFactory self.registry.registerUtility(factory, IRootFactory) self.registry.registerUtility(factory, IDefaultRootFactory) # b/c - - def _renderer_from_name(self, path_or_spec): - if path_or_spec is None: - # check for global default renderer - factory = self.registry.queryUtility(IRendererFactory) - if factory is not None: - return factory(path_or_spec) - return None + def _renderer_factory_from_name(self, path_or_spec): if '.' in path_or_spec: name = os.path.splitext(path_or_spec)[1] spec = self._make_spec(path_or_spec) @@ -217,8 +206,21 @@ class Configurator(object): spec = path_or_spec factory = self.registry.queryUtility(IRendererFactory, name=name) + return name, spec, factory + + def _renderer_from_name(self, path_or_spec): + if path_or_spec is None: + # check for global default renderer + factory = self.registry.queryUtility(IRendererFactory) + if factory is not None: + return factory(path_or_spec) + return None + + name, spec, factory = self._renderer_factory_from_name(path_or_spec) if factory is None: - raise ValueError('No renderer for renderer name %r' % name) + raise ValueError( + 'No factory for renderer named %r when looking up %s' % + (name, spec)) return factory(spec) def _set_authentication_policy(self, policy, _info=u''): @@ -372,7 +374,8 @@ class Configurator(object): def setup_registry(self, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, - locale_negotiator=None, request_factory=None): + locale_negotiator=None, request_factory=None, + renderer_globals_factory=None): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial 'setup' is performed against the registry. This is because the registry @@ -387,9 +390,10 @@ class Configurator(object): initialization. ``setup_registry`` configures settings, a root factory, - security policies, renderers, a debug logger, and a locale - negotiator using the configurator's current registry, as per - the descriptions in the Configurator constructor.""" + security policies, renderers, a debug logger, a locale + negotiator, and various other settings using the + configurator's current registry, as per the descriptions in + the Configurator constructor.""" self._fix_registry() self._set_settings(settings) self._set_root_factory(root_factory) @@ -410,6 +414,8 @@ class Configurator(object): registry.registerUtility(locale_negotiator, ILocaleNegotiator) if request_factory: self.set_request_factory(request_factory) + if renderer_globals_factory: + self.set_renderer_globals_factory(renderer_globals_factory) # getSiteManager is a unit testing dep injection def hook_zca(self, getSiteManager=None): @@ -477,7 +483,8 @@ class Configurator(object): Configurator constructor with one or more 'setting' key/value pairs. A setting is a single key/value pair in the dictionary-ish object returned from the API - :func:`repoze.bfg.settings.get_settings`. + :func:`repoze.bfg.settings.get_settings` and + :meth:`repoze.bfg.configuration.Configurator.get_settings`. You may pass a dictionary:: @@ -487,9 +494,10 @@ class Configurator(object): config.add_settings(external_uri='http://example.com') - This function is useful when you need to test code that - calls the :func:`repoze.bfg.settings.get_settings` API and which - uses return values from that API. + This function is useful when you need to test code that calls + the :func:`repoze.bfg.settings.get_settings` API (or the + :meth:`repoze.bfg.configuration.Configurator.get_settings` + API) and which uses return values from that API. .. note:: This method is new as of :mod:`repoze.bfg` 1.2. """ @@ -501,6 +509,21 @@ class Configurator(object): utility.update(settings) utility.update(kw) + def get_settings(self): + """ + Return a 'settings' object for the current application. A + 'settings' object is a dictionary-like object that contains + key/value pairs based on the dictionary passed as the ``settings`` + argument to the :class:`repoze.bfg.configuration.Configurator` + constructor or the :func:`repoze.bfg.router.make_app` API. + + .. note:: For backwards compatibility, dictionary keys can also be + looked up as attributes of the settings object. + + .. note:: the :class:`repoze.bfg.settings.get_settings` function + performs the same duty.""" + return self.registry.queryUtility(ISettings) + def make_wsgi_app(self): """ Returns a :mod:`repoze.bfg` WSGI application representing the current configuration state and sends a @@ -1468,6 +1491,21 @@ class Configurator(object): """ self.registry.registerUtility(factory, IRequestFactory) + def set_renderer_globals_factory(self, factory): + """ The object passed as ``factory`` will be used by the + :mod:`repoze.bfg` rendering machinery as a renderers global + factory (see :ref:`adding_renderer_globals`). The factory + must return a dictionary of items that will be merged intto + the *system* dictionary passed in to every renderer used by + the application. + + .. note:: Using the :meth:``renderer_globals_factory`` + argument to the + :class:`repoze.bfg.configuration.Configurator` constructor + can be used to achieve the same purpose. + """ + self.registry.registerUtility(factory, IRendererGlobalsFactory) + def set_locale_negotiator(self, negotiator): """ Set the :term:`locale negotiator` for this application. The @@ -1479,6 +1517,10 @@ class Configurator(object): more information. .. note: This API is new as of :mod:`repoze.bfg` version 1.3. + + .. note:: Using the :meth:``locale_negotiator`` argument to + the :class:`repoze.bfg.configuration.Configurator` + constructor can be used to achieve the same purpose. """ self.registry.registerUtility(negotiator, ILocaleNegotiator) @@ -1712,26 +1754,44 @@ class Configurator(object): self.add_subscriber(subscriber, event_iface) return L - def testing_add_template(self, path, renderer=None): - """Unit/integration testing helper: register a template - renderer at ``path`` (usually a relative filename ala - ``templates/foo.pt``) and return the renderer object. If the - ``renderer`` argument is None, a 'dummy' renderer will be - used. This function is useful when testing code that calls - the - :func:`repoze.bfg.chameleon_zpt.render_template_to_response` - function or - :func:`repoze.bfg.chameleon_text.render_template_to_response` - function or any other ``render_template*`` API of any built-in - templating system (see :mod:`repoze.bfg.chameleon_zpt` and - :mod:`repoze.bfg.chameleon_text`). + def testing_add_renderer(self, path, renderer=None): + """Unit/integration testing helper: register a renderer at + ``path`` (usually a relative filename ala ``templates/foo.pt`` + or a resource specification) and return the renderer object. + If the ``renderer`` argument is None, a 'dummy' renderer will + be used. This function is useful when testing code that calls + the :func:`repoze.bfg.renderers.render` function or + :func:`repoze.bfg.renderers.render_to_response` function or + any other ``render_*`` or ``get_*`` API of the + :mod:`repoze.bfg.renderers` module. + + Note that calling this method for with a ``path`` argument + representing a renderer factory type (e.g. for ``foo.pt`` + usually implies the ``chameleon_zpt`` renderer factory) + clobbers any existing renderer factory registered for that + type. + + .. note:: This method is also available under the alias + ``testing_add_template`` (an older name for it). + + .. note:: This method is new in :mod:`repoze.bfg` 1.3 (the + method named ``testing_add_template`` had the same signature + and purpose in previous releases).. """ + from repoze.bfg.testing import DummyRendererFactory + name, spec, factory = self._renderer_factory_from_name(path) + if factory is None or not isinstance(factory, DummyRendererFactory): + factory = DummyRendererFactory(name, factory) + self.registry.registerUtility(factory, IRendererFactory, name=name) + from repoze.bfg.testing import DummyTemplateRenderer if renderer is None: renderer = DummyTemplateRenderer() - self.registry.registerUtility(renderer, ITemplateRenderer, path) + factory.add(spec, renderer) return renderer + testing_add_template = testing_add_renderer + def _make_predicates(xhr=None, request_method=None, path_info=None, request_param=None, header=None, accept=None, containment=None, request_type=None, @@ -1996,40 +2056,6 @@ def decorate_view(wrapped_view, original_view): pass return True -def rendered_response(renderer, response, view, context, request, - renderer_name): - if ( hasattr(response, 'app_iter') and hasattr(response, 'headerlist') - and hasattr(response, 'status') ): - return response - result = renderer(response, {'view':view, 'renderer_name':renderer_name, - 'context':context, 'request':request}) - try: - registry = request.registry - except AttributeError: - registry = get_current_registry() - response_factory = registry.queryUtility(IResponseFactory, - default=Response) - response = response_factory(result) - if request is not None: # in tests, it may be None - attrs = request.__dict__ - content_type = attrs.get('response_content_type', None) - if content_type is not None: - response.content_type = content_type - headerlist = attrs.get('response_headerlist', None) - if headerlist is not None: - for k, v in headerlist: - response.headers.add(k, v) - status = attrs.get('response_status', None) - if status is not None: - response.status = status - charset = attrs.get('response_charset', None) - if charset is not None: - response.charset = charset - cache_for = attrs.get('response_cache_for', None) - if cache_for is not None: - response.cache_expires = cache_for - return response - def requestonly(class_or_callable, attr=None): """ Return true of the class or callable accepts only a request argument, as opposed to something that accepts context, request """ @@ -2073,6 +2099,12 @@ def requestonly(class_or_callable, attr=None): return False +def is_response(ob): + if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and + hasattr(ob, 'status') ): + return True + return False + def _map_view(view, attr=None, renderer=None, renderer_name=None): wrapped_view = view @@ -2093,10 +2125,15 @@ def _map_view(view, attr=None, renderer=None, renderer_name=None): else: response = getattr(inst, attr)() if renderer is not None: - response = rendered_response(renderer, - response, inst, - context, request, - renderer_name) + if not is_response(response): + system = {'view':inst, 'renderer_name':renderer_name, + 'context':context, 'request':request} + response = _render_to_response(renderer_name, + request, + response, + system, + renderer, + None) return response wrapped_view = _bfg_class_requestonly_view else: @@ -2108,10 +2145,15 @@ def _map_view(view, attr=None, renderer=None, renderer_name=None): else: response = getattr(inst, attr)() if renderer is not None: - response = rendered_response(renderer, - response, inst, - context, request, - renderer_name) + if not is_response(response): + system = {'view':inst, 'renderer_name':renderer_name, + 'context':context, 'request':request} + response = _render_to_response(renderer_name, + request, + response, + system, + renderer, + None) return response wrapped_view = _bfg_class_view @@ -2125,10 +2167,15 @@ def _map_view(view, attr=None, renderer=None, renderer_name=None): response = getattr(view, attr)(request) if renderer is not None: - response = rendered_response(renderer, - response, view, - context, request, - renderer_name) + if not is_response(response): + system = {'view':view, 'renderer_name':renderer_name, + 'context':context, 'request':request} + response = _render_to_response(renderer_name, + request, + response, + system, + renderer, + None) return response wrapped_view = _bfg_requestonly_view @@ -2136,20 +2183,30 @@ def _map_view(view, attr=None, renderer=None, renderer_name=None): def _bfg_attr_view(context, request): response = getattr(view, attr)(context, request) if renderer is not None: - response = rendered_response(renderer, - response, view, - context, request, - renderer_name) + if not is_response(response): + system = {'view':view, 'renderer_name':renderer_name, + 'context':context, 'request':request} + response = _render_to_response(renderer_name, + request, + response, + system, + renderer, + None) return response wrapped_view = _bfg_attr_view elif renderer is not None: def _rendered_view(context, request): response = view(context, request) - response = rendered_response(renderer, - response, view, - context, request, - renderer_name) + if not is_response(response): + system = {'view':view, 'renderer_name':renderer_name, + 'context':context, 'request':request} + response = _render_to_response(renderer_name, + request, + response, + system, + renderer, + None) return response wrapped_view = _rendered_view diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 992ec80d9..def957dad 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -179,6 +179,15 @@ class IRendererFactory(Interface): def __call__(name): """ Return an object that implements ``IRenderer`` """ +class IRendererGlobalsFactory(Interface): + def __call__(system_values): + """ Return a dictionary of global renderer values (aka + top-level template names). The ``system_values`` value passed + in will be a dictionary that includes at least a ``request`` + key, indicating the current request, and the value + ``renderer_name``, which will be the name of the renderer in + use.""" + class ITemplateRenderer(IRenderer): def implementation(): """ Return the object that the underlying templating system diff --git a/repoze/bfg/renderers.py b/repoze/bfg/renderers.py index b54803b3b..151044ce5 100644 --- a/repoze/bfg/renderers.py +++ b/repoze/bfg/renderers.py @@ -1,6 +1,12 @@ import os import pkg_resources +from webob import Response + +from zope.deprecation import deprecated + +from repoze.bfg.interfaces import IRendererGlobalsFactory +from repoze.bfg.interfaces import IResponseFactory from repoze.bfg.interfaces import ITemplateRenderer from repoze.bfg.compat import json @@ -8,7 +14,78 @@ from repoze.bfg.path import caller_package from repoze.bfg.settings import get_settings from repoze.bfg.threadlocal import get_current_registry -# concrete renderer factory implementations +# API + +def render(_renderer_name, **values): + """ Using the renderer specified as ``renderer_name`` (a template + or a static renderer) render the set of values present in + ``**values``. Return the result of the renderer's ``__call__`` + method (usually a string or Unicode). + + If the renderer name refers to a file on disk (such as when the + renderer is a template), it's usually best to supply the name as a + :term:`resource specification`. You may supply a relative + filename as renderer name; it will be converted to a resource + specification by combining the package name of the *caller* of + this function with the relative filename. + + The ``values`` provided will be supplied as top-level names to the + renderer. These will be augmented by a basic set of top-level + system names, such as ``request``, ``context``, and + ``renderer_name` unless any of these names is already provided + within ``*values``. If :term:`renderer globals` have been + specified, these will also be used to agument the value. + + Supply a ``request`` parameter containing the current + :mod:`repoze.bfg` request as part of ``**values`` in order to + provide the renderer with the most correct 'system' values + (``request`` and ``context`` in particular). + + .. note:: This API is new in :mod:`repoze.bfg` 1.3. + + """ + package = caller_package() + request = values.pop('request', None) + return _render(_renderer_name, request, values, None, None, package) + +def render_to_response(_renderer_name, **values): + """ Using the renderer specified as ``renderer_name`` (a template + or a static renderer) render the set of values present in + ``**values``. Return a :term:`Response` object wrapping the result + of of the renderer's ``__call__`` method. + + If the renderer name refers to a file on disk (such as when the + renderer is a template), it's usually best to supply the name as a + :term:`resource specification`. You may supply a relative + filename as renderer name; it will be converted to a resource + specification by combining the package name of the *caller* of + this function with the relative filename. + + The ``values`` provided will be supplied as top-level names to the + renderer. These will be augmented by a basic set of top-level + system names, such as ``request``, ``context``, and + ``renderer_name` unless any of these names is already provided + within ``*values``. If :term:`renderer globals` have been + specified, these will also be used to agument the value. + + Supply a ``request`` parameter containing the current + :mod:`repoze.bfg` request as part of ``**values`` in order to + provide the renderer with the most correct 'system' values + (``request`` and ``context`` in particular). + + .. note:: This API is new in :mod:`repoze.bfg` 1.3. + """ + package = caller_package() + request = values.pop('request', None) + return _render_to_response(_renderer_name, request, values, None, None, + package) + +def get_renderer(spec): + """ Return the renderer object for the renderer named as ``spec`` """ + package = caller_package() + return renderer_from_name(spec, package) + +# concrete renderer factory implementations (also API) def json_renderer_factory(name): def _render(value, system): @@ -30,7 +107,7 @@ def string_renderer_factory(name): return value return _render -# utility functions +# utility functions, not API def template_renderer_factory(spec, impl): reg = get_current_registry() @@ -49,8 +126,8 @@ def template_renderer_factory(spec, impl): try: package_name, filename = spec.split(':', 1) except ValueError: # pragma: no cover - # unit test or somehow we were passed a relative pathname; - # this should die + # somehow we were passed a relative pathname; this + # should die package_name = caller_package(4).__name__ filename = spec abspath = pkg_resources.resource_filename(package_name, filename) @@ -64,12 +141,99 @@ def template_renderer_factory(spec, impl): return renderer -def renderer_from_name(path): +def _reload_resources(): + settings = get_settings() + return settings and settings.get('reload_resources') + +def renderer_from_name(path, package=None): from repoze.bfg.configuration import Configurator reg = get_current_registry() - config = Configurator(reg) + config = Configurator(reg, package=package) return config._renderer_from_name(path) -def _reload_resources(): - settings = get_settings() - return settings and settings.get('reload_resources') +def _render(renderer_name, request, values, system_values, renderer, package): + try: + registry = request.registry + except AttributeError: + registry = get_current_registry() + + if renderer is None: + from repoze.bfg.configuration import Configurator + config = Configurator(registry, package=package) + renderer = config._renderer_from_name(renderer_name) + + if system_values is None: + system_values = { + 'view':None, + 'renderer_name':renderer_name, + 'context':getattr(request, 'context', None), + 'request':request, + } + + globals_factory = registry.queryUtility(IRendererGlobalsFactory) + + if globals_factory is not None: + renderer_globals = globals_factory(system_values) + if renderer_globals: + system_values.update(renderer_globals) + + result = renderer(values, system_values) + return result + +def _render_to_response(renderer_name, request, values, system_values, + renderer, package): + result = _render(renderer_name, request, values, system_values, renderer, + package) + return _make_response(request, result) + +def rendered_response(renderer, result, view, context, request, renderer_name): + # XXX: deprecated, left here only to not break old code; use + # render_to_response instead + if ( hasattr(result, 'app_iter') and hasattr(result, 'headerlist') + and hasattr(result, 'status') ): + return result + + system = {'view':view, 'renderer_name':renderer_name, + 'context':context, 'request':request} + + return _render_to_response(renderer_name, request, result, system, renderer, + None) + +deprecated('rendered_response', + "('repoze.bfg.renderers.rendered_response' is not a public API; it is " + "officially deprecated as of repoze.bfg 1.3; " + "use repoze.bfg.renderers.render_to_response instead')", + ) + +def _make_response(request, result): + try: + registry = request.registry + except AttributeError: + registry = get_current_registry() + + response_factory = registry.queryUtility(IResponseFactory, + default=Response) + + response = response_factory(result) + + if request is not None: + attrs = request.__dict__ + content_type = attrs.get('response_content_type', None) + if content_type is not None: + response.content_type = content_type + headerlist = attrs.get('response_headerlist', None) + if headerlist is not None: + for k, v in headerlist: + response.headers.add(k, v) + status = attrs.get('response_status', None) + if status is not None: + response.status = status + charset = attrs.get('response_charset', None) + if charset is not None: + response.charset = charset + cache_for = attrs.get('response_cache_for', None) + if cache_for is not None: + response.cache_expires = cache_for + + return response + diff --git a/repoze/bfg/settings.py b/repoze/bfg/settings.py index ff292dc7c..5e1181809 100644 --- a/repoze/bfg/settings.py +++ b/repoze/bfg/settings.py @@ -71,6 +71,10 @@ def get_settings(): .. note:: For backwards compatibility, dictionary keys can also be looked up as attributes of the settings object. + + .. note:: the + :class:`repoze.bfg.configuration.Configurator.get_settings` method + performs the same duty. """ reg = get_current_registry() return reg.queryUtility(ISettings) diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index b68539f43..0d79b559c 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -1,4 +1,5 @@ import copy +import types from webob import Response @@ -122,12 +123,10 @@ def registerTemplateRenderer(path, renderer=None): filename ala ``templates/foo.pt``) and return the renderer object. If the ``renderer`` argument is None, a 'dummy' renderer will be used. This function is useful when testing code that calls the - :func:`repoze.bfg.chameleon_zpt.render_template_to_response` - function or - :func:`repoze.bfg.chameleon_text.render_template_to_response` - function or any other ``render_template*`` API of any built-in - templating system (see :mod:`repoze.bfg.chameleon_zpt` and - :mod:`repoze.bfg.chameleon_text`). + :func:`repoze.bfg.renderers.render` function or + :func:`repoze.bfg.renderers.render_to_response` function or any + other ``render_*`` or ``get_*`` API of the + :mod:`repoze.bfg.renderers` module. .. warning:: This API is deprecated as of :mod:`repoze.bfg` 1.2. Instead use the @@ -140,7 +139,7 @@ def registerTemplateRenderer(path, renderer=None): return config.testing_add_template(path, renderer) # registerDummyRenderer is a deprecated alias that should never be removed -# (far too much usage in the wild) +# (too much usage in the wild) registerDummyRenderer = registerTemplateRenderer def registerView(name, result='', view=None, for_=(Interface, Interface), @@ -371,7 +370,7 @@ class DummyRootFactory(object): if 'bfg.routes.matchdict' in request: self.__dict__.update(request['bfg.routes.matchdict']) -class DummySecurityPolicy: +class DummySecurityPolicy(object): """ A standin for both an IAuthentication and IAuthorization policy """ def __init__(self, userid=None, groupids=(), permissive=True): self.userid = userid @@ -401,7 +400,7 @@ class DummySecurityPolicy: def principals_allowed_by_permission(self, context, permission): return self.effective_principals(None) -class DummyTemplateRenderer: +class DummyTemplateRenderer(object): """ An instance of this class is returned from :func:`repoze.bfg.testing.registerTemplateRenderer`. It has a @@ -412,14 +411,25 @@ class DummyTemplateRenderer: def __init__(self, string_response=''): self._received = {} - self.string_response = string_response - + self._string_response = string_response + self._implementation = MockTemplate(string_response) + + # For in-the-wild test code that doesn't create its own renderer, + # but mutates our internals instead. When all you read is the + # source code, *everything* is an API! + def _get_string_response(self): + return self._string_response + def _set_string_response(self, response): + self._string_response = response + self._implementation.response = response + string_response = property(_get_string_response, _set_string_response) + def implementation(self): - def callit(**kw): - return self(kw) - return callit + return self._implementation def __call__(self, kw, system=None): + if system: + self._received.update(system) self._received.update(kw) return self.string_response @@ -427,13 +437,15 @@ class DummyTemplateRenderer: """ Backwards compatibility """ val = self._received.get(k, _marker) if val is _marker: - raise AttributeError(k) + val = self._implementation._received.get(k, _marker) + if val is _marker: + raise AttributeError(k) return val def assert_(self, **kw): """ Accept an arbitrary set of assertion key/value pairs. For each assertion key/value pair assert that the renderer - (eg. :func:`repoze.bfg.chameleon_zpt.render_template_to_response`) + (eg. :func:`repoze.bfg.renderer.render_to_response`) received the key with a value that equals the asserted value. If the renderer did not receive the key at all, or the value received by the renderer doesn't match the assertion @@ -441,8 +453,11 @@ class DummyTemplateRenderer: for k, v in kw.items(): myval = self._received.get(k, _marker) if myval is _marker: - raise AssertionError( - 'A value for key "%s" was not passed to the renderer' % k) + myval = self._implementation._received.get(k, _marker) + if myval is _marker: + raise AssertionError( + 'A value for key "%s" was not passed to the renderer' + % k) if myval != v: raise AssertionError( @@ -533,7 +548,7 @@ class DummyModel: inst.__parent__ = __parent__ return inst -class DummyRequest: +class DummyRequest(object): """ A dummy request object (imitates a :term:`request` object). The ``params``, ``environ``, ``headers``, ``path``, and @@ -675,6 +690,24 @@ def setUp(registry=None, request=None, hook_zca=True): if registry is None: registry = Registry('testing') config = Configurator(registry=registry) + if hasattr(registry, 'registerUtility'): + # Sometimes nose calls us with a non-registry object because + # it thinks this function is module test setup. Likewise, + # someone may be passing us an esoteric "dummy" registry, and + # the below won't succeed if it doesn't have a registerUtility + # method. + from repoze.bfg.configuration import DEFAULT_RENDERERS + for name, renderer in DEFAULT_RENDERERS: + # Cause the default renderers to be registered because + # in-the-wild test code relies on being able to call + # e.g. ``repoze.bfg.chameleon_zpt.render_template`` + # without registering a .pt renderer, expecting the "real" + # template to be rendered. This is a holdover from when + # individual template system renderers weren't indirected + # by the ``repoze.bfg.renderers`` machinery, and + # ``render_template`` and friends went behind the back of + # any existing renderer factory lookup system. + config.add_renderer(name, renderer) hook_zca and config.hook_zca() config.begin(request=request) return config @@ -732,3 +765,48 @@ def cleanUp(*arg, **kw): extensive production usage, it will never be removed.""" return setUp(*arg, **kw) +class DummyRendererFactory(object): + """ Registered by + ``repoze.bfg.configuration.Configurator.testing_add_template`` as + a dummy renderer factory. The indecision about what to use as a + key (a spec vs. a relative name) is caused by test suites in the + wild believing they can register either. The ``factory`` argument + passed to this constructor is usually the *real* template renderer + factory, found when ``testing_add_renderer`` is called.""" + def __init__(self, name, factory): + self.name = name + self.factory = factory # the "real" renderer factory reg'd previously + self.renderers = {} + + def add(self, spec, renderer): + self.renderers[spec] = renderer + if ':' in spec: + package, relative = spec.split(':', 1) + self.renderers[relative] = renderer + + def __call__(self, spec): + renderer = self.renderers.get(spec) + if renderer is None: + if ':' in spec: + package, relative = spec.split(':', 1) + renderer = self.renderers.get(relative) + if renderer is None: + if self.factory: + renderer = self.factory(spec) + else: + raise KeyError('No testing renderer registered for %r' % + spec) + return renderer + + +class MockTemplate(object): + def __init__(self, response): + self._received = {} + self.response = response + def __getattr__(self, attrname): + return self + def __getitem__(self, attrname): + return self + def __call__(self, *arg, **kw): + self._received.update(kw) + return self.response diff --git a/repoze/bfg/tests/test_chameleon_text.py b/repoze/bfg/tests/test_chameleon_text.py index cb30f489a..d9cefbd67 100644 --- a/repoze/bfg/tests/test_chameleon_text.py +++ b/repoze/bfg/tests/test_chameleon_text.py @@ -166,88 +166,33 @@ class GetRendererTests(Base, unittest.TestCase): from repoze.bfg.chameleon_text import get_renderer return get_renderer(name) - def test_nonabs_registered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_text import TextTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.txt') - utility = TextTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result, utility) - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_nonabs_unregistered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_text import TextTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.txt') - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), None) - utility = TextTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result, utility) - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_explicit_registration(self): - from repoze.bfg.interfaces import ITemplateRenderer + def test_it(self): + from repoze.bfg.interfaces import IRendererFactory class Dummy: template = object() - utility = Dummy() - self._registerUtility(utility, ITemplateRenderer, name='foo') + def implementation(self): pass + renderer = Dummy() + def rf(spec): + return renderer + self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is utility) - -class GetTemplateTests(unittest.TestCase, Base): - def setUp(self): - Base.setUp(self) - - def tearDown(self): - Base.tearDown(self) + self.failUnless(result is renderer) +class GetTemplateTests(Base, unittest.TestCase): def _callFUT(self, name): from repoze.bfg.chameleon_text import get_template return get_template(name) - def test_nonabs_registered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_text import TextTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.txt') - utility = TextTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result.filename, minimal) - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_nonabs_unregistered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_text import TextTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.txt') - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), None) - utility = TextTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result.filename, minimal) - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_explicit_registration(self): - from repoze.bfg.interfaces import ITemplateRenderer + def test_it(self): + from repoze.bfg.interfaces import IRendererFactory class Dummy: template = object() def implementation(self): return self.template - utility = Dummy() - self._registerUtility(utility, ITemplateRenderer, name='foo') + renderer = Dummy() + def rf(spec): + return renderer + self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is utility.template) - - - - + self.failUnless(result is renderer.template) diff --git a/repoze/bfg/tests/test_chameleon_zpt.py b/repoze/bfg/tests/test_chameleon_zpt.py index 4ea5fc281..a0f01701a 100644 --- a/repoze/bfg/tests/test_chameleon_zpt.py +++ b/repoze/bfg/tests/test_chameleon_zpt.py @@ -128,6 +128,12 @@ class RenderTemplateTests(Base, unittest.TestCase): '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') class RenderTemplateToResponseTests(Base, unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + def _callFUT(self, name, **kw): from repoze.bfg.chameleon_zpt import render_template_to_response return render_template_to_response(name, **kw) @@ -157,82 +163,32 @@ class GetRendererTests(Base, unittest.TestCase): from repoze.bfg.chameleon_zpt import get_renderer return get_renderer(name) - def test_nonabs_registered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.pt') - utility = ZPTTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result, utility) - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_nonabs_unregistered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.pt') - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), None) - utility = ZPTTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result, utility) - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_explicit_registration(self): - from repoze.bfg.interfaces import ITemplateRenderer + def test_it(self): + from repoze.bfg.interfaces import IRendererFactory class Dummy: template = object() - utility = Dummy() - self._registerUtility(utility, ITemplateRenderer, name='foo') + def implementation(self): pass + renderer = Dummy() + def rf(spec): + return renderer + self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is utility) + self.failUnless(result is renderer) class GetTemplateTests(Base, unittest.TestCase): def _callFUT(self, name): from repoze.bfg.chameleon_zpt import get_template return get_template(name) - def test_nonabs_registered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.pt') - utility = ZPTTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result.filename, minimal) - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_nonabs_unregistered(self): - from repoze.bfg.threadlocal import get_current_registry - from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer - from repoze.bfg.interfaces import ITemplateRenderer - minimal = self._getTemplatePath('minimal.pt') - reg = get_current_registry() - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), None) - utility = ZPTTemplateRenderer(minimal) - self._registerUtility(utility, ITemplateRenderer, name=minimal) - result = self._callFUT(minimal) - self.assertEqual(result.filename, minimal) - self.assertEqual(reg.queryUtility(ITemplateRenderer, minimal), utility) - - def test_explicit_registration(self): - from repoze.bfg.interfaces import ITemplateRenderer + def test_it(self): + from repoze.bfg.interfaces import IRendererFactory class Dummy: template = object() def implementation(self): return self.template - utility = Dummy() - self._registerUtility(utility, ITemplateRenderer, name='foo') + renderer = Dummy() + def rf(spec): + return renderer + self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') - self.failUnless(result is utility.template) - - - - - + self.failUnless(result is renderer.template) diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 25c5544b6..0cb8ff5e8 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -290,6 +290,15 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(IRequestFactory) self.assertEqual(utility, 'abc') + def test_setup_registry_renderer_globals_factory(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IRendererGlobalsFactory + reg = Registry() + config = self._makeOne(reg) + config.setup_registry(renderer_globals_factory='abc') + utility = reg.getUtility(IRendererGlobalsFactory) + self.assertEqual(utility, 'abc') + def test_setup_registry_alternate_renderers(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import IRendererFactory @@ -300,6 +309,19 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(reg.getUtility(IRendererFactory, 'yeah'), renderer) + def test_get_settings_nosettings(self): + from repoze.bfg.registry import Registry + reg = Registry() + config = self._makeOne(reg) + self.assertEqual(config.get_settings(), None) + + def test_get_settings_withsettings(self): + from repoze.bfg.interfaces import ISettings + settings = {'a':1} + config = self._makeOne() + config.registry.registerUtility(settings, ISettings) + self.assertEqual(config.get_settings(), settings) + def test_add_settings_settings_already_registered(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import ISettings @@ -2462,7 +2484,52 @@ class ConfiguratorTests(unittest.TestCase): config.unhook_zca(getSiteManager=gsm) self.assertEqual(gsm.unhooked, True) + def test_testing_add_renderer(self): + config = self._makeOne() + renderer = config.testing_add_renderer('templates/foo.pt') + from repoze.bfg.testing import DummyTemplateRenderer + self.failUnless(isinstance(renderer, DummyTemplateRenderer)) + from repoze.bfg.renderers import render_to_response + # must provide request to pass in registry (this is a functest) + request = DummyRequest() + request.registry = config.registry + render_to_response('templates/foo.pt', foo=1, bar=2, request=request) + renderer.assert_(foo=1) + renderer.assert_(bar=2) + renderer.assert_(request=request) + + def test_testing_add_renderer_explicitrenderer(self): + config = self._makeOne() + class E(Exception): pass + def renderer(kw, system): + self.assertEqual(kw, {'foo':1, 'bar':2}) + raise E + renderer = config.testing_add_renderer('templates/foo.pt', renderer) + from repoze.bfg.renderers import render_to_response + # must provide request to pass in registry (this is a functest) + request = DummyRequest() + request.registry = config.registry + try: + render_to_response( + 'templates/foo.pt', foo=1, bar=2, request=request) + except E: + pass + else: # pragma: no cover + raise AssertionError + def test_testing_add_template(self): + config = self._makeOne() + renderer = config.testing_add_template('templates/foo.pt') + from repoze.bfg.testing import DummyTemplateRenderer + self.failUnless(isinstance(renderer, DummyTemplateRenderer)) + from repoze.bfg.renderers import render_to_response + # must provide request to pass in registry (this is a functest) + request = DummyRequest() + request.registry = config.registry + render_to_response('templates/foo.pt', foo=1, bar=2, request=request) + renderer.assert_(foo=1) + renderer.assert_(bar=2) + renderer.assert_(request=request) class Test__map_view(unittest.TestCase): def setUp(self): @@ -2778,97 +2845,6 @@ class Test__map_view(unittest.TestCase): request = self._makeRequest() self.assertEqual(result(None, request).body, 'Hello!') -class Test_rendered_response(unittest.TestCase): - def setUp(self): - testing.setUp() - - def tearDown(self): - testing.tearDown() - - def _callFUT(self, renderer, response, view=None, - context=None, request=None, renderer_name=None): - from repoze.bfg.configuration import rendered_response - if request is None: - request = DummyRequest() - return rendered_response(renderer, response, view, - context, request, renderer_name) - - def _makeRenderer(self): - def renderer(*arg): - return 'Hello!' - return renderer - - def test_is_response(self): - renderer = self._makeRenderer() - response = DummyResponse() - result = self._callFUT(renderer, response) - self.assertEqual(result, response) - - def test_calls_renderer(self): - renderer = self._makeRenderer() - response = {'a':'1'} - result = self._callFUT(renderer, response) - self.assertEqual(result.body, 'Hello!') - - def test_with_content_type(self): - renderer = self._makeRenderer() - response = {'a':'1'} - request = DummyRequest() - attrs = {'response_content_type':'text/nonsense'} - request.__dict__.update(attrs) - result = self._callFUT(renderer, response, request=request) - self.assertEqual(result.content_type, 'text/nonsense') - - def test_with_headerlist(self): - renderer = self._makeRenderer() - response = {'a':'1'} - request = DummyRequest() - attrs = {'response_headerlist':[('a', '1'), ('b', '2')]} - request.__dict__.update(attrs) - result = self._callFUT(renderer, response, request=request) - self.assertEqual(result.headerlist, - [('Content-Type', 'text/html; charset=UTF-8'), - ('Content-Length', '6'), - ('a', '1'), - ('b', '2')]) - - def test_with_status(self): - renderer = self._makeRenderer() - response = {'a':'1'} - request = DummyRequest() - attrs = {'response_status':'406 You Lose'} - request.__dict__.update(attrs) - result = self._callFUT(renderer, response, request=request) - self.assertEqual(result.status, '406 You Lose') - - def test_with_charset(self): - renderer = self._makeRenderer() - response = {'a':'1'} - request = DummyRequest() - attrs = {'response_charset':'UTF-16'} - request.__dict__.update(attrs) - result = self._callFUT(renderer, response, request=request) - self.assertEqual(result.charset, 'UTF-16') - - def test_with_cache_for(self): - renderer = self._makeRenderer() - response = {'a':'1'} - request = DummyRequest() - attrs = {'response_cache_for':100} - request.__dict__.update(attrs) - result = self._callFUT(renderer, response, request=request) - self.assertEqual(result.cache_control.max_age, 100) - - def test_with_real_request(self): - # functional - from repoze.bfg.request import Request - renderer = self._makeRenderer() - response = {'a':'1'} - request = Request({}) - request.response_status = '406 You Lose' - result = self._callFUT(renderer, response, request=request) - self.assertEqual(result.status, '406 You Lose') - class Test_decorate_view(unittest.TestCase): def _callFUT(self, wrapped, original): from repoze.bfg.configuration import decorate_view diff --git a/repoze/bfg/tests/test_renderers.py b/repoze/bfg/tests/test_renderers.py index 1f4df3556..8a8a578eb 100644 --- a/repoze/bfg/tests/test_renderers.py +++ b/repoze/bfg/tests/test_renderers.py @@ -120,21 +120,29 @@ class TestRendererFromName(unittest.TestCase): def tearDown(self): cleanUp() - def _callFUT(self, path): + def _callFUT(self, path, package=None): from repoze.bfg.renderers import renderer_from_name - return renderer_from_name(path) + return renderer_from_name(path, package) def test_it(self): from repoze.bfg.interfaces import IRendererFactory import os here = os.path.dirname(os.path.abspath(__file__)) fixture = os.path.join(here, 'fixtures/minimal.pt') - renderer = {} def factory(path, **kw): - return renderer + return path testing.registerUtility(factory, IRendererFactory, name='.pt') result = self._callFUT(fixture) - self.assertEqual(result, renderer) + self.assertEqual(result, fixture) + + def test_with_package(self): + from repoze.bfg.interfaces import IRendererFactory + def factory(path, **kw): + return path + testing.registerUtility(factory, IRendererFactory, name='.pt') + import repoze.bfg.tests + result = self._callFUT('fixtures/minimal.pt', repoze.bfg.tests) + self.assertEqual(result, 'repoze.bfg.tests:fixtures/minimal.pt') def test_it_no_renderer(self): self.assertRaises(ValueError, self._callFUT, 'foo') @@ -199,6 +207,295 @@ class Test_string_renderer_factory(unittest.TestCase): renderer(None, {'request':request}) self.assertEqual(request.response_content_type, 'text/mishmash') +class Test_rendered_response(unittest.TestCase): + def setUp(self): + testing.setUp() + from zope.deprecation import __show__ + __show__.off() + + def tearDown(self): + testing.tearDown() + from zope.deprecation import __show__ + __show__.on() + + def _callFUT(self, renderer, response, view=None, + context=None, request=None, renderer_name=None): + from repoze.bfg.renderers import rendered_response + if request is None: + request = testing.DummyRequest() + return rendered_response(renderer, response, view, + context, request, renderer_name) + + def _makeRenderer(self): + def renderer(*arg): + return 'Hello!' + return renderer + + def test_is_response(self): + renderer = self._makeRenderer() + response = DummyResponse() + result = self._callFUT(renderer, response) + self.assertEqual(result, response) + + def test_calls_renderer(self): + renderer = self._makeRenderer() + response = {'a':'1'} + result = self._callFUT(renderer, response) + self.assertEqual(result.body, 'Hello!') + +class Test__make_response(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, request, result): + from repoze.bfg.renderers import _make_response + return _make_response(request, result) + + def test_with_content_type(self): + request = testing.DummyRequest() + attrs = {'response_content_type':'text/nonsense'} + request.__dict__.update(attrs) + response = self._callFUT(request, 'abc') + self.assertEqual(response.content_type, 'text/nonsense') + self.assertEqual(response.body, 'abc') + + def test_with_headerlist(self): + request = testing.DummyRequest() + attrs = {'response_headerlist':[('a', '1'), ('b', '2')]} + request.__dict__.update(attrs) + response = self._callFUT(request, 'abc') + self.assertEqual(response.headerlist, + [('Content-Type', 'text/html; charset=UTF-8'), + ('Content-Length', '3'), + ('a', '1'), + ('b', '2')]) + self.assertEqual(response.body, 'abc') + + def test_with_status(self): + request = testing.DummyRequest() + attrs = {'response_status':'406 You Lose'} + request.__dict__.update(attrs) + response = self._callFUT(request, 'abc') + self.assertEqual(response.status, '406 You Lose') + self.assertEqual(response.body, 'abc') + + def test_with_charset(self): + request = testing.DummyRequest() + attrs = {'response_charset':'UTF-16'} + request.__dict__.update(attrs) + response = self._callFUT(request, 'abc') + self.assertEqual(response.charset, 'UTF-16') + + def test_with_cache_for(self): + request = testing.DummyRequest() + attrs = {'response_cache_for':100} + request.__dict__.update(attrs) + response = self._callFUT(request, 'abc') + self.assertEqual(response.cache_control.max_age, 100) + + def test_with_alternate_response_factory(self): + from repoze.bfg.interfaces import IResponseFactory + class ResponseFactory(object): + def __init__(self, result): + self.result = result + self.config.registry.registerUtility(ResponseFactory, IResponseFactory) + request = testing.DummyRequest() + response = self._callFUT(request, 'abc') + self.assertEqual(response.__class__, ResponseFactory) + self.assertEqual(response.result, 'abc') + + def test_with_real_request(self): + # functional + from repoze.bfg.request import Request + request = Request({}) + attrs = {'response_status':'406 You Lose'} + request.__dict__.update(attrs) + response = self._callFUT(request, 'abc') + self.assertEqual(response.status, '406 You Lose') + self.assertEqual(response.body, 'abc') + +class Test__render(unittest.TestCase): + def setUp(self): + self.config = cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, renderer_name, request, values, system_values, renderer, + package): + from repoze.bfg.renderers import _render + return _render(renderer_name, request, values, system_values, renderer, + package) + + def test_explicit_renderer(self): + def renderer(*arg): + return arg + result = self._callFUT( + 'name', 'request', 'values', 'system_values', renderer, None) + self.assertEqual(result, ('values', 'system_values')) + + def test_request_has_registry(self): + request = Dummy() + class DummyRegistry(object): + def queryUtility(self, iface): + self.queried = True + return None + reg = DummyRegistry() + request.registry = reg + def renderer(*arg): + return arg + result = self._callFUT( + 'name', request, 'values', 'system_values', renderer, None) + self.assertEqual(result, ('values', 'system_values')) + self.failUnless(reg.queried) + + def test_renderer_is_None(self): + from repoze.bfg.interfaces import IRendererFactory + def renderer(values, system): + return rf + def rf(spec): + return renderer + self.config.registry.registerUtility(rf, IRendererFactory, name='name') + result = self._callFUT( + 'name', 'request', 'values', 'system_values', None, None) + self.assertEqual(result, rf) + + def test_system_values_is_None(self): + def renderer(*arg): + return arg + request = Dummy() + context = Dummy() + request.context = context + result = self._callFUT( + 'name', request, 'values', None, renderer, None) + system = {'request':request, 'context':context, + 'renderer_name':'name', 'view':None} + self.assertEqual(result, ('values', system)) + + def test_renderer_globals_factory_active(self): + from repoze.bfg.interfaces import IRendererGlobalsFactory + def rg(system): + return {'a':1} + self.config.registry.registerUtility(rg, IRendererGlobalsFactory) + def renderer(*arg): + return arg + result = self._callFUT( + 'name', 'request', 'values', {'a':2}, renderer, None) + self.assertEqual(result, ('values', {'a':1})) + +class Test__render_to_response(unittest.TestCase): + def setUp(self): + self.config = cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, renderer_name, request, values, system_values, renderer, + package): + from repoze.bfg.renderers import _render_to_response + return _render_to_response( + renderer_name, request, values, system_values, renderer, + package) + + def test_it(self): + def renderer(*arg): + return 'hello' + request = Dummy() + result = self._callFUT( + 'name', request, 'values', 'system_values', renderer, None) + self.assertEqual(result.body, 'hello') + +class Test_render(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, renderer_name, **kw): + from repoze.bfg.renderers import render + return render(renderer_name, **kw) + + def test_it_no_request(self): + renderer = self.config.testing_add_renderer( + 'repoze.bfg.tests:abc/def.pt') + renderer.string_response = 'abc' + result = self._callFUT('abc/def.pt', a=1) + self.assertEqual(result, 'abc') + renderer.assert_(a=1) + renderer.assert_(request=None) + + def test_it_with_request(self): + renderer = self.config.testing_add_renderer( + 'repoze.bfg.tests:abc/def.pt') + renderer.string_response = 'abc' + request = testing.DummyRequest() + result = self._callFUT('abc/def.pt', + a=1, request=request) + self.assertEqual(result, 'abc') + renderer.assert_(a=1) + renderer.assert_(request=request) + +class Test_render_to_response(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, renderer_name, **kw): + from repoze.bfg.renderers import render_to_response + return render_to_response(renderer_name, **kw) + + def test_it_no_request(self): + renderer = self.config.testing_add_renderer( + 'repoze.bfg.tests:abc/def.pt') + renderer.string_response = 'abc' + response = self._callFUT('abc/def.pt', a=1) + self.assertEqual(response.body, 'abc') + renderer.assert_(a=1) + renderer.assert_(request=None) + + def test_it_with_request(self): + renderer = self.config.testing_add_renderer( + 'repoze.bfg.tests:abc/def.pt') + renderer.string_response = 'abc' + request = testing.DummyRequest() + response = self._callFUT('abc/def.pt', + a=1, request=request) + self.assertEqual(response.body, 'abc') + renderer.assert_(a=1) + renderer.assert_(request=request) + +class Test_get_renderer(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _callFUT(self, renderer_name, **kw): + from repoze.bfg.renderers import get_renderer + return get_renderer(renderer_name) + + def test_it(self): + renderer = self.config.testing_add_renderer( + 'repoze.bfg.tests:abc/def.pt') + result = self._callFUT('abc/def.pt') + self.assertEqual(result, renderer) + +class Dummy: + pass + +class DummyResponse: + status = '200 OK' + headerlist = () + app_iter = () + body = '' + class DummyFactory: def __init__(self, renderer): self.renderer = renderer diff --git a/repoze/bfg/tests/test_testing.py b/repoze/bfg/tests/test_testing.py index 1b3d731fa..fc3bd7c9e 100644 --- a/repoze/bfg/tests/test_testing.py +++ b/repoze/bfg/tests/test_testing.py @@ -66,18 +66,18 @@ class Test_registerTemplateRenderer(TestBase): renderer = testing.registerTemplateRenderer('templates/foo') from repoze.bfg.testing import DummyTemplateRenderer self.failUnless(isinstance(renderer, DummyTemplateRenderer)) - from repoze.bfg.chameleon_zpt import render_template_to_response - render_template_to_response('templates/foo', foo=1, bar=2) - self.assertEqual(dict(foo=1, bar=2), renderer._received) + from repoze.bfg.renderers import render_to_response + render_to_response('templates/foo', foo=1, bar=2) + renderer.assert_(foo=1) + renderer.assert_(bar=2) def test_registerTemplateRenderer_explicitrenderer(self): from repoze.bfg import testing def renderer(kw, system): - raise ValueError + self.assertEqual(kw, {'foo':1, 'bar':2}) renderer = testing.registerTemplateRenderer('templates/foo', renderer) - from repoze.bfg.chameleon_zpt import render_template_to_response - self.assertRaises(ValueError, render_template_to_response, - 'templates/foo', foo=1, bar=2) + from repoze.bfg.renderers import render_to_response + render_to_response('templates/foo', foo=1, bar=2) class Test_registerEventListener(TestBase): def test_registerEventListener_single(self): @@ -526,8 +526,8 @@ class TestDummyTemplateRenderer(unittest.TestCase): renderer = self._makeOne() impl = renderer.implementation() impl(a=1, b=2) - self.assertEqual(renderer._received['a'], 1) - self.assertEqual(renderer._received['b'], 2) + self.assertEqual(renderer._implementation._received['a'], 1) + self.assertEqual(renderer._implementation._received['b'], 2) def test_getattr(self): renderer = self._makeOne() @@ -666,6 +666,59 @@ class Test_tearDown(unittest.TestCase): getSiteManager.reset() manager.clear() +class TestDummyRendererFactory(unittest.TestCase): + def _makeOne(self, name, factory): + from repoze.bfg.testing import DummyRendererFactory + return DummyRendererFactory(name, factory) + + def test_add_no_colon(self): + f = self._makeOne('name', None) + f.add('spec', 'renderer') + self.assertEqual(f.renderers['spec'], 'renderer') + + def test_add_with_colon(self): + f = self._makeOne('name', None) + f.add('spec:spec2', 'renderer') + self.assertEqual(f.renderers['spec:spec2'], 'renderer') + self.assertEqual(f.renderers['spec2'], 'renderer') + + def test_call(self): + f = self._makeOne('name', None) + f.renderers['spec'] = 'renderer' + self.assertEqual(f('spec'), 'renderer') + + def test_call2(self): + f = self._makeOne('name', None) + f.renderers['spec'] = 'renderer' + self.assertEqual(f('spec:spec'), 'renderer') + + def test_call3(self): + def factory(spec): + return 'renderer' + f = self._makeOne('name', factory) + self.assertEqual(f('spec'), 'renderer') + + def test_call_miss(self): + f = self._makeOne('name', None) + self.assertRaises(KeyError, f, 'spec') + +class TestMockTemplate(unittest.TestCase): + def _makeOne(self, response): + from repoze.bfg.testing import MockTemplate + return MockTemplate(response) + + def test_getattr(self): + template = self._makeOne(None) + self.assertEqual(template.foo, template) + + def test_getitem(self): + template = self._makeOne(None) + self.assertEqual(template['foo'], template) + + def test_call(self): + template = self._makeOne('123') + self.assertEqual(template(), '123') + from zope.interface import Interface from zope.interface import implements |
