From 01a6e567a20096f6033cc603667f4e900d2a44c3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 26 Sep 2008 06:42:53 +0000 Subject: Move to Chameleon. --- CHANGES.txt | 29 ++++ docs/api/template.rst | 63 ++++++++- docs/glossary.rst | 32 +++-- docs/narr/MyProject/myproject/views.py | 2 +- docs/narr/project.rst | 36 ++--- docs/narr/templates.rst | 42 +++--- docs/tutorials/cmf/skins.rst | 8 +- docs/tutorials/lxmlgraph/step03.rst | 17 +-- docs/tutorials/lxmlgraph/step03/myapp/views.py | 4 +- docs/tutorials/lxmlgraph/step04/myapp/views.py | 2 +- repoze/bfg/chameleon_genshi.py | 82 +++++++++++ repoze/bfg/chameleon_zpt.py | 81 +++++++++++ repoze/bfg/configure.zcml | 4 +- repoze/bfg/includes/configure.zcml | 4 +- repoze/bfg/paster_template/+package+/views.py_tmpl | 2 +- repoze/bfg/push.py | 6 +- repoze/bfg/template.py | 151 ++------------------- repoze/bfg/tests/fixtures/minimal.genshi | 3 + repoze/bfg/tests/test_chameleon_genshi.py | 134 ++++++++++++++++++ repoze/bfg/tests/test_chameleon_zpt.py | 139 +++++++++++++++++++ repoze/bfg/tests/test_push.py | 2 +- repoze/bfg/tests/test_template.py | 61 +-------- repoze/bfg/tests/test_xslt.py | 14 +- repoze/bfg/xslt.py | 85 ++++++++++++ repoze/bfg/zcml.py | 2 +- setup.py | 44 +++--- 26 files changed, 753 insertions(+), 296 deletions(-) create mode 100644 repoze/bfg/chameleon_genshi.py create mode 100644 repoze/bfg/chameleon_zpt.py create mode 100644 repoze/bfg/tests/fixtures/minimal.genshi create mode 100644 repoze/bfg/tests/test_chameleon_genshi.py create mode 100644 repoze/bfg/tests/test_chameleon_zpt.py create mode 100644 repoze/bfg/xslt.py diff --git a/CHANGES.txt b/CHANGES.txt index 4378fba85..2a0160998 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,35 @@ Next release - Add ``principals_allowed_by_permission`` API to security module. + - Replace ``z3c.pt`` support with support for ``chameleon.zpt``. + Chameleon is the new name for the package that used to be named + ``z3c.pt``. NOTE: If you update a ``repoze.bfg`` SVN checkout + that you're using for development, you will need to run "setup.py + install" or "setup.py develop" again in order to obtain the + proper Chameleon packages. ``z3c.pt`` is no longer supported by + ``repoze.bfg``. All API functions that used to render ``z3c.pt`` + templates will work fine with the new packages, and your + templates should render almost identically. + + - Add a ``repoze.bfg.chameleon_zpt`` module. This module provides + Chameleon ZPT support. + + - Add a ``repoze.bfg.xslt`` module. This module provides XSLT + support. + + - Add a ``repoze.bfg.chameleon_genshi`` module. This provides + direct Genshi support, which did not exist previously. + + Deprecations + + - Importing API functions directly from ``repoze.bfg.template`` is + now deprecated. The ``get_template``, ``render_template``, + ``render_template_to_response`` functions should now be imported + from ``repoze.chameleon_zpt``. The ``render_transform``, and + ``render_transform_to_response`` functions should now be imported + from ``repoze.bfg.xslt``. The ``repoze.bfg.template`` module + will remain around "forever" to support backwards compatibility. + 0.3.7 (09/09/2008) Features diff --git a/docs/api/template.rst b/docs/api/template.rst index 64e25a37c..8cf63c91b 100644 --- a/docs/api/template.rst +++ b/docs/api/template.rst @@ -1,9 +1,56 @@ .. _template_module: -:mod:`repoze.bfg.template` --------------------------- +:mod:`repoze.bfg` Built-in Templating Facilties +=============================================== -.. automodule:: repoze.bfg.template +Three templating facilities are provided by :mod:`repoze.bfg` "out of +the box": :term:`ZPT` -style, :term:`Genshi` -style, and :term:`XSLT` +templating. + +ZPT-style and Genshi-style templates are in :mod:`repoze.bfg` are +supported by the :term:`Chameleon` (nee :term:`z3c.pt`) templating +engine, which contains alternate implementations of both the ZPT and +Genshi language specifications. + +XSLT templating is supported by the use of :term:`lxml`. + +Below is API documentation for each of those facilities. Each +facility is similar to the other, but to use a particular facility, +you must import the API function from a specific module. For +instance, to render a ZPT-style template to a response, you would +import the ``render_template_to_response`` function from +``repoze.bfg.chameleon_zpt`` while you would import +``render_template_to_response`` from ``repoze.bfg.chameleon_genshi`` +in order to render a Genshi-style template to a response. While these +functions have the same name, each will only operate on template files +that match the style in which the template file itself is written. If +you need to import API functions from two templating facilities within +the same module, use the ``as`` feature of the Python import +statement, e.g.: + +.. code-block:: python + + from repoze.chameleon_zpt import render_template as zpt_render + from repoze.chameleon_genshi import render_template as genshi_render + +:mod:`repoze.bfg.chameleon_zpt` +------------------------------- + +.. automodule:: repoze.bfg.chameleon_zpt + + .. autofunction:: get_template + + .. autofunction:: render_template + + .. autofunction:: render_template_to_response + +.. note:: For backwards compatibility purposes, these functions may + also be imported from ``repoze.bfg.template``. + +:mod:`repoze.bfg.chameleon_genshi` +---------------------------------- + +.. automodule:: repoze.bfg.chameleon_genshi .. autofunction:: get_template @@ -11,7 +58,17 @@ .. autofunction:: render_template_to_response +:mod:`repoze.bfg.xslt` +---------------------- + +.. automodule:: repoze.bfg.xslt + + .. autofunction:: get_transform + .. autofunction:: render_transform .. autofunction:: render_transform_to_response +.. note:: For backwards compatibility purposes, these functions may + also be imported from ``repoze.bfg.template``. + diff --git a/docs/glossary.rst b/docs/glossary.rst index 51eb8c2fa..6d335ec51 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -199,22 +199,36 @@ Glossary XSLT `XSL Transformations `_. A language for transforming XML documents into other XML documents. + Chameleon + `chameleon `_ is an + attribute language template compiler which supports both the + :term:`ZPT` and :term:`Genshi` templating specifications. It is + written and maintained by Malthe Borch. It has serveral + extensions, such as the ability to use bracketed (Genshi-style) + ``${name}`` syntax, even within ZPT. It is also much faster than + the reference implementations of both ZPT and Genshi. + :mod:`repoze.bfg` offers Chameleon templating out of the box in + both ZPT and Genshi "flavors". + chameleon.zpt + ``chameleon.zpt`` is the package which provides :term:`ZPT` + templating support under the :term:`Chameleon` templating engine. + chameleon.genshi + ``chameleon.genshi`` is the package which provides :term:`Genshi` + templating support under the :term:`Chameleon` templating engine. z3c.pt - `z3c.pt `_ is an - implementation of :term:`ZPT` by Malthe Borch. It has serveral - extensions, such as the ability to use bracketed- ``${name}`` - syntax. It is also much faster than the reference implementation - of ZPT. :mod:`repoze.bfg` offers z3c.pt templating out of the - box. + This was the previous name for :term:`Chameleon`, and is now a + Zope 3 compatibility package for Chameleon. ZPT The `Zope Page Template `_ templating language. + Genshi + `Genshi `_ is an attribute-based XML + templating language similar to ZPT. Its syntax is supported + within :mod:`repoze.bfg` via :term:`Chameleon`. METAL `Macro Expansion for TAL `_, a part of :term:`ZPT` which makes it possible to share common look - and feel between templates. :term:`z3c.pt`, the implementation of - ZPT that :mod:`repoze.bfg` ships with does not implement the METAL - specification. + and feel between templates. Routes A `system by Ben Bangert `_ which parses URLs and compares them against a number of user defined diff --git a/docs/narr/MyProject/myproject/views.py b/docs/narr/MyProject/myproject/views.py index 6e1be6190..67dbd00b6 100644 --- a/docs/narr/MyProject/myproject/views.py +++ b/docs/narr/MyProject/myproject/views.py @@ -1,4 +1,4 @@ -from repoze.bfg.template import render_template_to_response +from repoze.bfg.chameleon_zpt import render_template_to_response def my_view(context, request): return render_template_to_response('templates/mytemplate.pt', diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 7360053dd..3bca363e8 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -299,7 +299,7 @@ your application by requiring more settings in this section. The ``reload_templates`` setting in the ``[app:main]`` section is a :mod:`repoze.bfg`-specific setting which is passed into the framework. -If it exists, and is ``true``, :term:`z3c.pt` and XSLT template +If it exists, and is ``true``, :term:`Chameleon` and XSLT template changes will not require an application restart to be detected. .. warning:: The ``reload_templates`` option should be turned off for @@ -418,8 +418,8 @@ The ``myproject`` :term:`package` lives inside the ``MyProject`` #. A ``run.py`` module, which contains code that helps users run the application. -#. A ``templates`` directory, which is full of :term:`z3c.pt` and/or - :term:`XSLT` templates. +#. A ``templates`` directory, which contains :term:`Chameleon` (or + other types of) templates. #. A ``tests.py`` module, which contains unit test code for the application. @@ -485,16 +485,17 @@ in the model, and the HTML given back to the browser. dispatch`). The *request* is an instance of the :term:`WebOb` ``Request`` class representing the browser's request to our server. -#. The view renders a :term:`template` and returns the result as the - :term:`response`. Note that because our ``MyProject.ini`` has a - ``reload_templates = true`` directive indicating that templates - should be reloaded when they change, you won't need to restart the - application server to see changes you make to templates. During - development, this is handy. If this directive had been ``false`` - (or if the directive did not exist), you would need to restart the - application server for each template change. For production - applications, you should set your project's ``reload_templates`` to - ``false`` to increase the speed at which templates may be rendered. +#. The view renders a :term:`Chameleon` template and returns the + result as the :term:`response`. Note that because our + ``MyProject.ini`` has a ``reload_templates = true`` directive + indicating that templates should be reloaded when they change, you + won't need to restart the application server to see changes you + make to templates. During development, this is handy. If this + directive had been ``false`` (or if the directive did not exist), + you would need to restart the application server for each template + change. For production applications, you should set your project's + ``reload_templates`` to ``false`` to increase the speed at which + templates may be rendered. .. note:: @@ -569,16 +570,15 @@ without the PasteDeploy configuration file: ``templates/mytemplate.pt`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The single :term:`template` in the project looks like so: +The single :term:`Chameleon` template in the project looks like so: .. literalinclude:: MyProject/myproject/templates/mytemplate.pt :linenos: :language: xml -This is a :term:`z3c.pt` template. It displays the current project -name when it is rendered. It is referenced by the ``my_view`` -function in the ``views.py`` module. Templates are accessed and used -by view functions. +It displays the current project name when it is rendered. It is +referenced by the ``my_view`` function in the ``views.py`` module. +Templates are accessed and used by view functions. ``tests.py`` ~~~~~~~~~~~~ diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 80b530a8c..dce66ae3d 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -5,25 +5,35 @@ A :term:`template` is a usually file on disk which can be used to render data provided by a :term:`view`, surrounded by more static information. -Templating With :term:`z3c.pt` (ZPT) Page Templates ---------------------------------------------------- +Templating With :term:`Chameleon` (:term:`chameleon.zpt`) Page Templates +------------------------------------------------------------------------ Like Zope, :mod:`repoze.bfg` uses Zope Page Templates (:term:`ZPT`) as -its default templating language. However, :mod:`repoze.bfg` uses a -different implementation of the :term:`ZPT` specification than Zope -does: the :term:`z3c.pt` templating engine. This templating engine +its default and best-supported templating language. However, +:mod:`repoze.bfg` uses a different implementation of the :term:`ZPT` +specification than Zope does: the :term:`Chameleon` +:term:`chameleon.zpt` templating engine. This templating engine complies with the `Zope Page Template `_ template specification and is significantly faster. -Given that there is a :term:`z3c.pt` template named ``foo.html`` in a -directory in your application named ``templates``, you can render the -template from a view like so: +.. note:: :mod:`repoze.bfg` can also allow for the use of Genshi-style + templates via the ``chameleon.genshi`` package, support for which + is built-in to :mod:`repoze.bfg`. The :mod:`repoze.bfg` API + functions for getting and rendering Chameleon Genshi-style + templates mirrors the Chameleon ZPT-style API completely; only the + template files themselves must differ. See :ref:`template_module` + for more information about using Genshi-style templates within + :mod:`repoze.bfg`. + +Given that there is a :term:`chameleon.zpt` template named +``foo.html`` in a directory in your application named ``templates``, +you can render the template from a view like so: .. code-block:: python :linenos: - from repoze.bfg.template import render_template_to_response + from repoze.bfg.chameleon_zpt import render_template_to_response def sample_view(context, request): return render_template_to_response('templates/foo.html', foo=1, bar=2) @@ -35,18 +45,18 @@ Relative to the directory in which the ``views.py`` file which names it lives, which is usually the :mod:`repoze.bfg` application's :term:`package` directory. -``render_template_to_response`` always renders a :term:`z3c.pt` +``render_template_to_response`` always renders a :term:`chameleon.zpt` template, and always returns a Response object which has a *status code* of ``200 OK`` and a *content-type* of ``text-html``. If you need more control over the status code and content-type, use the -``render_template`` function instead, which also renders a z3c.pt -template but returns a string instead of a Response. You can use -the string manually as a response body: +``render_template`` function instead, which also renders a ZPT +template but returns a string instead of a Response. You can use the +string manually as a response body: .. code-block:: python :linenos: - from repoze.bfg.template import render_template + from repoze.bfg.chameleon_zpt import render_template from webob import Response def sample_view(context, request): result = render_template('templates/foo.html', foo=1, bar=2) @@ -71,7 +81,7 @@ an XSLT as follows: .. code-block:: python :linenos: - from repoze.bfg.template import render_transform_to_response + from repoze.bfg.xslt import render_transform_to_response from lxml import etree node = etree.Element("root") return render_transform_to_response('templates/foo.xsl', node) @@ -85,7 +95,7 @@ You can also pass XSLT parameters in as keyword arguments: .. code-block:: python :linenos: - from repoze.bfg.template import render_transform_to_response + from repoze.bfg.xslt import render_transform_to_response from lxml import etree node = etree.Element("root") value1 = "'app1'" diff --git a/docs/tutorials/cmf/skins.rst b/docs/tutorials/cmf/skins.rst index 2e444c4db..cbc28bfb5 100644 --- a/docs/tutorials/cmf/skins.rst +++ b/docs/tutorials/cmf/skins.rst @@ -14,9 +14,9 @@ particular skin to provide the site with additional features. :mod:`repoze.bfg` itself has no such concept, and no package provides a direct replacement, but bfg :term:`view` code combined with differing :term:`request type` attributes can provide a good deal of -the same sort of behavior. The `vudo.bfg `_ -package is an attempt to allow directories on disk to represent -collections of templates, each of which can be thought of as a minimal -skin. +the same sort of behavior. The `repoze.skins +`_ package is an attempt to allow +directories on disk to represent collections of templates, each of +which can be thought of as a minimal skin. diff --git a/docs/tutorials/lxmlgraph/step03.rst b/docs/tutorials/lxmlgraph/step03.rst index 9edc77af3..0f54f8761 100644 --- a/docs/tutorials/lxmlgraph/step03.rst +++ b/docs/tutorials/lxmlgraph/step03.rst @@ -52,7 +52,7 @@ Also add a function in ``views.py`` that looks like the following: .. code-block:: python :linenos: - from repoze.bfg.template import render_template_to_response + from repoze.bfg.chameleon_zpt import render_template_to_response def zpt_view(context, request): return render_template_to_response('templates/default.pt', name=context.__name__, @@ -61,8 +61,8 @@ Also add a function in ``views.py`` that looks like the following: This function is relatively simple: #. Line 1 imports a :mod:`repoze.bfg` function that renders ZPT - templates to a response. :mod:`repoze.bfg` uses the ``z3c.pt`` ZPT - engine. + templates to a response. :mod:`repoze.bfg` uses the + :term:`chameleon.zpt` ZPT engine. #. Line 2, like our other view functions, gets passed a ``context`` (the current hop in the URL) and WebOb ``request`` object. @@ -92,10 +92,11 @@ Life is better with templating: ``render_template_to_response``. #. Line 6 looks interesting. It uses the ``node`` that we passed in - via ``render_template_to_response``. Since ``z3c.pt`` uses Python - as its expession language, we can put anything Python-legal between - the braces. And since ``node`` is an ``lxml`` ``Element`` object, - we just ask for its ``.tag``, like regular Python ``lxml`` code. + via ``render_template_to_response``. Since :term:`chameleon.zpt` + uses Python as its expession language, we can put anything + Python-legal between the braces. And since ``node`` is an ``lxml`` + ``Element`` object, we just ask for its ``.tag``, like regular + Python ``lxml`` code. Viewing the ZPT ------------------ @@ -119,7 +120,7 @@ model using the ZPT templating language. XSLT Templates -==================== +============== So that's the ZPT way of rendering HTML for an XML document. We can additonally use XSLT to do templating. How might XSLT look? diff --git a/docs/tutorials/lxmlgraph/step03/myapp/views.py b/docs/tutorials/lxmlgraph/step03/myapp/views.py index 0ac33ba83..6eb4e376c 100644 --- a/docs/tutorials/lxmlgraph/step03/myapp/views.py +++ b/docs/tutorials/lxmlgraph/step03/myapp/views.py @@ -1,5 +1,5 @@ -from repoze.bfg.template import render_template_to_response -from repoze.bfg.template import render_transform_to_response +from repoze.bfg.chameleon_zpt import render_template_to_response +from repoze.bfg.chameleon_zpt import render_transform_to_response def zpt_view(context, request): return render_template_to_response("templates/default.pt", diff --git a/docs/tutorials/lxmlgraph/step04/myapp/views.py b/docs/tutorials/lxmlgraph/step04/myapp/views.py index fd8650e14..f079cea8c 100644 --- a/docs/tutorials/lxmlgraph/step04/myapp/views.py +++ b/docs/tutorials/lxmlgraph/step04/myapp/views.py @@ -1,4 +1,4 @@ -from repoze.bfg.template import render_transform_to_response +from repoze.bfg.xslt import render_transform_to_response # Some constants XML_NAMESPACE='http://www.w3.org/XML/1998/namespace' diff --git a/repoze/bfg/chameleon_genshi.py b/repoze/bfg/chameleon_genshi.py new file mode 100644 index 000000000..fbcc519e6 --- /dev/null +++ b/repoze/bfg/chameleon_genshi.py @@ -0,0 +1,82 @@ +import os + +from webob import Response + +from zope.component import queryUtility +from zope.component.interfaces import ComponentLookupError +from zope.component import getSiteManager + +from zope.interface import classProvides +from zope.interface import implements + +from repoze.bfg.path import caller_path +from repoze.bfg.interfaces import ITemplateFactory +from repoze.bfg.interfaces import ITemplate +from repoze.bfg.interfaces import ISettings + +from chameleon.genshi.template import GenshiTemplateFile + +class GenshiTemplateFactory(object): + classProvides(ITemplateFactory) + implements(ITemplate) + + def __init__(self, path, auto_reload=False): + try: + self.template = GenshiTemplateFile(path, auto_reload=auto_reload) + except ImportError, why: + why = str(why) + if 'z3c.pt' in why: + # unpickling error due to move from z3c.pt -> chameleon + cachefile = '%s.cache' % path + if os.path.isfile(cachefile): + os.remove(cachefile) + self.template = GenshiTemplateFile(path, + auto_reload=auto_reload) + else: + raise + + def __call__(self, **kw): + result = self.template.render(**kw) + return result + +def _get_template(path, **kw): + # XXX use pkg_resources + template = queryUtility(ITemplate, path) + + if template is None: + if not os.path.exists(path): + raise ValueError('Missing template file: %s' % path) + settings = queryUtility(ISettings) + auto_reload = settings and settings.reload_templates + template = GenshiTemplateFactory(path, auto_reload) + try: + sm = getSiteManager() + except ComponentLookupError: + pass + else: + sm.registerUtility(template, ITemplate, name=path) + + return template + +def get_template(path): + """ Return a ``chameleon.genshi`` template object at the + package-relative path (may also be absolute)""" + path = caller_path(path) + return _get_template(path).template + +def render_template(path, **kw): + """ Render a ``chameleon.genshi`` template at the package-relative + path (may also be absolute) using the kwargs in ``*kw`` as + top-level names and return a string.""" + path = caller_path(path) + template = get_template(path) + return template(**kw) + +def render_template_to_response(path, **kw): + """ Render a ``chameleon.genshi`` template at the package-relative + path (may also be absolute) using the kwargs in ``*kw`` as + top-level names and return a Response object.""" + path = caller_path(path) + result = render_template(path, **kw) + return Response(result) + diff --git a/repoze/bfg/chameleon_zpt.py b/repoze/bfg/chameleon_zpt.py new file mode 100644 index 000000000..80669c009 --- /dev/null +++ b/repoze/bfg/chameleon_zpt.py @@ -0,0 +1,81 @@ +import os + +from webob import Response + +from zope.component import queryUtility +from zope.component.interfaces import ComponentLookupError +from zope.component import getSiteManager + +from zope.interface import classProvides +from zope.interface import implements + +from repoze.bfg.path import caller_path +from repoze.bfg.interfaces import ITemplateFactory +from repoze.bfg.interfaces import ITemplate +from repoze.bfg.interfaces import ISettings + +from chameleon.zpt.template import PageTemplateFile + +class ZPTTemplateFactory(object): + classProvides(ITemplateFactory) + implements(ITemplate) + + def __init__(self, path, auto_reload=False): + try: + self.template = PageTemplateFile(path, auto_reload=auto_reload) + except ImportError, why: + why = str(why) + if 'z3c.pt' in why: + # unpickling error due to move from z3c.pt -> chameleon + cachefile = '%s.cache' % path + if os.path.isfile(cachefile): + os.remove(cachefile) + self.template = PageTemplateFile(path, auto_reload=auto_reload) + else: + raise + + def __call__(self, **kw): + result = self.template.render(**kw) + return result + +def _get_template(path, **kw): + # XXX use pkg_resources + template = queryUtility(ITemplate, path) + + if template is None: + if not os.path.exists(path): + raise ValueError('Missing template file: %s' % path) + settings = queryUtility(ISettings) + auto_reload = settings and settings.reload_templates + template = ZPTTemplateFactory(path, auto_reload) + try: + sm = getSiteManager() + except ComponentLookupError: + pass + else: + sm.registerUtility(template, ITemplate, name=path) + + return template + +def get_template(path): + """ Return a ``chameleon.zpt`` template object at the + package-relative path (may also be absolute)""" + path = caller_path(path) + return _get_template(path).template + +def render_template(path, **kw): + """ Render a ``chameleon.zpt`` template at the package-relative + path (may also be absolute) using the kwargs in ``*kw`` as + top-level names and return a string.""" + path = caller_path(path) + template = get_template(path) + return template(**kw) + +def render_template_to_response(path, **kw): + """ Render a ``chameleon.zpt`` template at the package-relative + path (may also be absolute) using the kwargs in ``*kw`` as + top-level names and return a Response object.""" + path = caller_path(path) + result = render_template(path, **kw) + return Response(result) + diff --git a/repoze/bfg/configure.zcml b/repoze/bfg/configure.zcml index 355795b7c..517b5fd54 100644 --- a/repoze/bfg/configure.zcml +++ b/repoze/bfg/configure.zcml @@ -3,7 +3,9 @@ - + + + - + + + + diff --git a/repoze/bfg/tests/test_chameleon_genshi.py b/repoze/bfg/tests/test_chameleon_genshi.py new file mode 100644 index 000000000..0f0fc88a1 --- /dev/null +++ b/repoze/bfg/tests/test_chameleon_genshi.py @@ -0,0 +1,134 @@ +import unittest + +from zope.component.testing import PlacelessSetup + +class Base(PlacelessSetup): + def setUp(self): + PlacelessSetup.setUp(self) + + def tearDown(self): + PlacelessSetup.tearDown(self) + + def _zcmlConfigure(self): + import repoze.bfg + import zope.configuration.xmlconfig + zope.configuration.xmlconfig.file('configure.zcml', package=repoze.bfg) + + def _getTemplatePath(self, name): + import os + here = os.path.abspath(os.path.dirname(__file__)) + return os.path.join(here, 'fixtures', name) + +class GenshiTemplateFactoryTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getTargetClass(self): + from repoze.bfg.chameleon_genshi import GenshiTemplateFactory + return GenshiTemplateFactory + + def _makeOne(self, *arg, **kw): + klass = self._getTargetClass() + return klass(*arg, **kw) + + def test_instance_implements_ITemplate(self): + from zope.interface.verify import verifyObject + from repoze.bfg.interfaces import ITemplate + path = self._getTemplatePath('minimal.genshi') + verifyObject(ITemplate, self._makeOne(path)) + + def test_class_implements_ITemplate(self): + from zope.interface.verify import verifyClass + from repoze.bfg.interfaces import ITemplate + verifyClass(ITemplate, self._getTargetClass()) + + def test_call(self): + self._zcmlConfigure() + minimal = self._getTemplatePath('minimal.genshi') + instance = self._makeOne(minimal) + result = instance() + self.failUnless(isinstance(result, str)) + self.assertEqual(result, '
\n
\n') + +class RenderTemplateTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getFUT(self): + from repoze.bfg.chameleon_genshi import render_template + return render_template + + def test_it(self): + minimal = self._getTemplatePath('minimal.genshi') + render = self._getFUT() + result = render(minimal) + self.failUnless(isinstance(result, str)) + self.assertEqual(result, '
\n
\n') + +class RenderTemplateToResponseTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getFUT(self): + from repoze.bfg.chameleon_genshi import render_template_to_response + return render_template_to_response + + def test_it(self): + minimal = self._getTemplatePath('minimal.genshi') + render = self._getFUT() + result = render(minimal) + from webob import Response + self.failUnless(isinstance(result, Response)) + self.assertEqual(result.app_iter, ['
\n
\n']) + self.assertEqual(result.status, '200 OK') + self.assertEqual(len(result.headerlist), 2) + +class GetTemplateTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getFUT(self): + from repoze.bfg.chameleon_genshi import get_template + return get_template + + def test_nonabs_registered(self): + from zope.component import getGlobalSiteManager + from zope.component import queryUtility + from repoze.bfg.chameleon_genshi import GenshiTemplateFactory + from repoze.bfg.interfaces import ITemplate + minimal = self._getTemplatePath('minimal.genshi') + utility = GenshiTemplateFactory(minimal) + gsm = getGlobalSiteManager() + gsm.registerUtility(utility, ITemplate, name=minimal) + get = self._getFUT() + result = get(minimal) + self.assertEqual(result.filename, minimal) + self.assertEqual(queryUtility(ITemplate, minimal), utility) + + def test_nonabs_unregistered(self): + from zope.component import getGlobalSiteManager + from zope.component import queryUtility + from repoze.bfg.chameleon_genshi import GenshiTemplateFactory + from repoze.bfg.interfaces import ITemplate + minimal = self._getTemplatePath('minimal.genshi') + self.assertEqual(queryUtility(ITemplate, minimal), None) + utility = GenshiTemplateFactory(minimal) + gsm = getGlobalSiteManager() + gsm.registerUtility(utility, ITemplate, name=minimal) + get = self._getFUT() + result = get(minimal) + self.assertEqual(result.filename, minimal) + self.assertEqual(queryUtility(ITemplate, minimal), utility) + diff --git a/repoze/bfg/tests/test_chameleon_zpt.py b/repoze/bfg/tests/test_chameleon_zpt.py new file mode 100644 index 000000000..3b977bcbf --- /dev/null +++ b/repoze/bfg/tests/test_chameleon_zpt.py @@ -0,0 +1,139 @@ +import unittest + +from zope.component.testing import PlacelessSetup + +class Base(PlacelessSetup): + def setUp(self): + PlacelessSetup.setUp(self) + + def tearDown(self): + PlacelessSetup.tearDown(self) + + def _zcmlConfigure(self): + import repoze.bfg + import zope.configuration.xmlconfig + zope.configuration.xmlconfig.file('configure.zcml', package=repoze.bfg) + + def _getTemplatePath(self, name): + import os + here = os.path.abspath(os.path.dirname(__file__)) + return os.path.join(here, 'fixtures', name) + +class ZPTTemplateFactoryTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getTargetClass(self): + from repoze.bfg.chameleon_zpt import ZPTTemplateFactory + return ZPTTemplateFactory + + def _makeOne(self, *arg, **kw): + klass = self._getTargetClass() + return klass(*arg, **kw) + + def test_instance_implements_ITemplate(self): + from zope.interface.verify import verifyObject + from repoze.bfg.interfaces import ITemplate + path = self._getTemplatePath('minimal.pt') + verifyObject(ITemplate, self._makeOne(path)) + + def test_class_implements_ITemplate(self): + from zope.interface.verify import verifyClass + from repoze.bfg.interfaces import ITemplate + verifyClass(ITemplate, self._getTargetClass()) + + def test_call(self): + self._zcmlConfigure() + minimal = self._getTemplatePath('minimal.pt') + instance = self._makeOne(minimal) + result = instance() + self.failUnless(isinstance(result, str)) + self.assertEqual(result, '
\n
\n') + +class RenderTemplateTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getFUT(self): + from repoze.bfg.chameleon_zpt import render_template + return render_template + + def test_it(self): + self._zcmlConfigure() + minimal = self._getTemplatePath('minimal.pt') + render = self._getFUT() + result = render(minimal) + self.failUnless(isinstance(result, str)) + self.assertEqual(result, '
\n
\n') + +class RenderTemplateToResponseTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getFUT(self): + from repoze.bfg.chameleon_zpt import render_template_to_response + return render_template_to_response + + def test_it(self): + self._zcmlConfigure() + minimal = self._getTemplatePath('minimal.pt') + render = self._getFUT() + result = render(minimal) + from webob import Response + self.failUnless(isinstance(result, Response)) + self.assertEqual(result.app_iter, ['
\n
\n']) + self.assertEqual(result.status, '200 OK') + self.assertEqual(len(result.headerlist), 2) + +class GetTemplateTests(unittest.TestCase, Base): + def setUp(self): + Base.setUp(self) + + def tearDown(self): + Base.tearDown(self) + + def _getFUT(self): + from repoze.bfg.chameleon_zpt import get_template + return get_template + + def test_nonabs_registered(self): + self._zcmlConfigure() + from zope.component import getGlobalSiteManager + from zope.component import queryUtility + from repoze.bfg.chameleon_zpt import ZPTTemplateFactory + from repoze.bfg.interfaces import ITemplate + minimal = self._getTemplatePath('minimal.pt') + utility = ZPTTemplateFactory(minimal) + gsm = getGlobalSiteManager() + gsm.registerUtility(utility, ITemplate, name=minimal) + get = self._getFUT() + result = get(minimal) + self.assertEqual(result.filename, minimal) + self.assertEqual(queryUtility(ITemplate, minimal), utility) + + def test_nonabs_unregistered(self): + self._zcmlConfigure() + from zope.component import getGlobalSiteManager + from zope.component import queryUtility + from repoze.bfg.chameleon_zpt import ZPTTemplateFactory + from repoze.bfg.interfaces import ITemplate + minimal = self._getTemplatePath('minimal.pt') + self.assertEqual(queryUtility(ITemplate, minimal), None) + utility = ZPTTemplateFactory(minimal) + gsm = getGlobalSiteManager() + gsm.registerUtility(utility, ITemplate, name=minimal) + get = self._getFUT() + result = get(minimal) + self.assertEqual(result.filename, minimal) + self.assertEqual(queryUtility(ITemplate, minimal), utility) + + diff --git a/repoze/bfg/tests/test_push.py b/repoze/bfg/tests/test_push.py index 907503983..3dc456b58 100644 --- a/repoze/bfg/tests/test_push.py +++ b/repoze/bfg/tests/test_push.py @@ -31,7 +31,7 @@ class Test_pushpage(unittest.TestCase, PlacelessSetup): pp = self._makeOne('pp.pt') wrapped = pp(to_wrap) response = wrapped(object(), object()) - self.assertEqual(response.body, '

WRAPPED

') + self.assertEqual(response.body, '

WRAPPED

\n') def to_wrap(context, request): return {'wrapped': 'WRAPPED'} diff --git a/repoze/bfg/tests/test_template.py b/repoze/bfg/tests/test_template.py index ba3745132..42baa6492 100644 --- a/repoze/bfg/tests/test_template.py +++ b/repoze/bfg/tests/test_template.py @@ -19,40 +19,6 @@ class Base(PlacelessSetup): here = os.path.abspath(os.path.dirname(__file__)) return os.path.join(here, 'fixtures', name) -class Z3CPTTemplateFactoryTests(unittest.TestCase, Base): - def setUp(self): - Base.setUp(self) - - def tearDown(self): - Base.tearDown(self) - - def _getTargetClass(self): - from repoze.bfg.template import Z3CPTTemplateFactory - return Z3CPTTemplateFactory - - def _makeOne(self, *arg, **kw): - klass = self._getTargetClass() - return klass(*arg, **kw) - - def test_instance_implements_ITemplate(self): - from zope.interface.verify import verifyObject - from repoze.bfg.interfaces import ITemplate - path = self._getTemplatePath('minimal.pt') - verifyObject(ITemplate, self._makeOne(path)) - - def test_class_implements_ITemplate(self): - from zope.interface.verify import verifyClass - from repoze.bfg.interfaces import ITemplate - verifyClass(ITemplate, self._getTargetClass()) - - def test_call(self): - self._zcmlConfigure() - minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) - result = instance() - self.failUnless(isinstance(result, str)) - self.assertEqual(result, '
\n
') - class RenderTemplateTests(unittest.TestCase, Base): def setUp(self): Base.setUp(self) @@ -70,7 +36,7 @@ class RenderTemplateTests(unittest.TestCase, Base): render = self._getFUT() result = render(minimal) self.failUnless(isinstance(result, str)) - self.assertEqual(result, '
\n
') + self.assertEqual(result, '
\n
\n') class RenderTemplateToResponseTests(unittest.TestCase, Base): def setUp(self): @@ -90,7 +56,7 @@ class RenderTemplateToResponseTests(unittest.TestCase, Base): result = render(minimal) from webob import Response self.failUnless(isinstance(result, Response)) - self.assertEqual(result.app_iter, ['
\n
']) + self.assertEqual(result.app_iter, ['
\n
\n']) self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) @@ -109,10 +75,10 @@ class GetTemplateTests(unittest.TestCase, Base): self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility - from repoze.bfg.template import Z3CPTTemplateFactory + from repoze.bfg.chameleon_zpt import ZPTTemplateFactory from repoze.bfg.interfaces import ITemplate minimal = self._getTemplatePath('minimal.pt') - utility = Z3CPTTemplateFactory(minimal) + utility = ZPTTemplateFactory(minimal) gsm = getGlobalSiteManager() gsm.registerUtility(utility, ITemplate, name=minimal) get = self._getFUT() @@ -124,11 +90,11 @@ class GetTemplateTests(unittest.TestCase, Base): self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility - from repoze.bfg.template import Z3CPTTemplateFactory + from repoze.bfg.chameleon_zpt import ZPTTemplateFactory from repoze.bfg.interfaces import ITemplate minimal = self._getTemplatePath('minimal.pt') self.assertEqual(queryUtility(ITemplate, minimal), None) - utility = Z3CPTTemplateFactory(minimal) + utility = ZPTTemplateFactory(minimal) gsm = getGlobalSiteManager() gsm.registerUtility(utility, ITemplate, name=minimal) get = self._getFUT() @@ -136,18 +102,3 @@ class GetTemplateTests(unittest.TestCase, Base): self.assertEqual(result.filename, minimal) self.assertEqual(queryUtility(ITemplate, minimal), utility) - def test_nonabs_registered(self): - self._zcmlConfigure() - from zope.component import getGlobalSiteManager - from zope.component import queryUtility - from repoze.bfg.template import Z3CPTTemplateFactory - from repoze.bfg.interfaces import ITemplate - minimal = self._getTemplatePath('minimal.pt') - utility = Z3CPTTemplateFactory(minimal) - gsm = getGlobalSiteManager() - gsm.registerUtility(utility, ITemplate, name=minimal) - get = self._getFUT() - result = get(minimal) - self.assertEqual(result.filename, minimal) - self.assertEqual(queryUtility(ITemplate, minimal), utility) - diff --git a/repoze/bfg/tests/test_xslt.py b/repoze/bfg/tests/test_xslt.py index 01e5befba..0a858e08b 100644 --- a/repoze/bfg/tests/test_xslt.py +++ b/repoze/bfg/tests/test_xslt.py @@ -27,7 +27,7 @@ class XSLTemplateFactoryTests(unittest.TestCase, Base): Base.tearDown(self) def _getTargetClass(self): - from repoze.bfg.template import XSLTemplateFactory + from repoze.bfg.xslt import XSLTemplateFactory return XSLTemplateFactory def _makeOne(self, *arg, **kw): @@ -64,7 +64,7 @@ class RenderTransformToResponseTests(unittest.TestCase, Base): Base.tearDown(self) def _getFUT(self): - from repoze.bfg.template import render_transform_to_response + from repoze.bfg.xslt import render_transform_to_response return render_transform_to_response def test_nonabs_unregistered(self): @@ -83,7 +83,7 @@ class RenderTransformToResponseTests(unittest.TestCase, Base): self.assertEqual(result.app_iter, [resultstr]) self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) - from repoze.bfg.template import XSLTemplateFactory + from repoze.bfg.xslt import XSLTemplateFactory self.failUnless(isinstance(queryUtility(INodeTemplate, minimal), XSLTemplateFactory)) @@ -91,7 +91,7 @@ class RenderTransformToResponseTests(unittest.TestCase, Base): self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility - from repoze.bfg.template import XSLTemplateFactory + from repoze.bfg.xslt import XSLTemplateFactory from repoze.bfg.interfaces import INodeTemplate minimal = self._getTemplatePath('minimal.xsl') utility = XSLTemplateFactory(minimal) @@ -117,7 +117,7 @@ class RenderTransformTests(unittest.TestCase, Base): Base.tearDown(self) def _getFUT(self): - from repoze.bfg.template import render_transform + from repoze.bfg.xslt import render_transform return render_transform def test_nonabs_unregistered(self): @@ -133,7 +133,7 @@ class RenderTransformTests(unittest.TestCase, Base): self.failUnless(isinstance(result, str)) resultstr = """\n
\n""" self.assertEqual(result, resultstr) - from repoze.bfg.template import XSLTemplateFactory + from repoze.bfg.xslt import XSLTemplateFactory self.failUnless(isinstance(queryUtility(INodeTemplate, minimal), XSLTemplateFactory)) @@ -141,7 +141,7 @@ class RenderTransformTests(unittest.TestCase, Base): self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility - from repoze.bfg.template import XSLTemplateFactory + from repoze.bfg.xslt import XSLTemplateFactory from repoze.bfg.interfaces import INodeTemplate minimal = self._getTemplatePath('minimal.xsl') utility = XSLTemplateFactory(minimal) diff --git a/repoze/bfg/xslt.py b/repoze/bfg/xslt.py new file mode 100644 index 000000000..949bde6da --- /dev/null +++ b/repoze/bfg/xslt.py @@ -0,0 +1,85 @@ +import os + +from webob import Response + +from zope.component import queryUtility +from zope.component import getSiteManager +from zope.component.interfaces import ComponentLookupError + +from zope.interface import classProvides +from zope.interface import implements + +from repoze.bfg.path import caller_path + +from repoze.bfg.interfaces import INodeTemplate +from repoze.bfg.interfaces import ITemplateFactory + +def get_transform(path, node): + """ Return a callable transform object. When called, the + transform will return a string. The ``path`` argument should be a + package-relative path (also may be absolute) to an XSLT file. + When called, the transform will use the kwargs in ``*kw`` as top + level names and the lxml node at ``node``.""" + # Render using XSLT + path = caller_path(path) + + template = queryUtility(INodeTemplate, path) + if template is None: + if not os.path.exists(path): + raise ValueError('Missing template file: %s' % path) + template = XSLTemplateFactory(path) + try: + sm = getSiteManager() + except ComponentLookupError: + pass + else: + sm.registerUtility(template, INodeTemplate, name=path) + return template + +def render_transform(path, node, **kw): + """ Render a XSL template at the package-relative path (may also + be absolute) using the kwargs in ``*kw`` as top-level names and + the lxml node at ``node`` and return a string.""" + path = caller_path(path) + template = get_transform(path, node) + return template(node, **kw) + +def render_transform_to_response(path, node, **kw): + """ Render a XSL template at the package-relative path (may also + be absolute) using the kwargs in ``*kw`` as top-level names and + the lxml node at ``node`` and return a Response object.""" + path = caller_path(path) + result = render_transform(path, node, **kw) + return Response(result) + +class XSLTemplateFactory(object): + classProvides(ITemplateFactory) + implements(INodeTemplate) + + def __init__(self, path, auto_reload=False): + self.path = path + self.auto_reload = auto_reload + + def __call__(self, node, **kw): + processor = get_processor(self.path, self.auto_reload) + result = str(processor(node, **kw)) + return result + +# Manage XSLT processors on a per-thread basis +import threading +from lxml import etree +xslt_pool = threading.local() +def get_processor(xslt_fn, auto_reload=False): + if not auto_reload: + try: + return xslt_pool.processors[xslt_fn] + except AttributeError: + xslt_pool.processors = {} + except KeyError: + pass + + # Make a processor and add it to the pool + source = etree.ElementTree(file=xslt_fn) + proc = etree.XSLT(source) + xslt_pool.processors[xslt_fn] = proc + return proc diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index a2a063603..b5de0584a 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -118,7 +118,7 @@ def zcml_configure(name, package, load=cPickle.load): try: vers, ptime, actions = load(open(pckname, 'rb')) except (IOError, cPickle.UnpicklingError, EOFError, TypeError, ValueError, - AttributeError, NameError): + AttributeError, NameError, ImportError): return file_configure(name, package) if vers != PVERSION: diff --git a/setup.py b/setup.py index 6ee7cbb8e..3aa1aa78f 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # ############################################################################## -__version__ = '0.3.7' +__version__ = '0.3.8' import os @@ -25,6 +25,21 @@ here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() +install_requires=[ + 'zope.interface', + 'zope.component', + 'zope.testing', + 'zope.hookable', + 'zope.event', + 'WebOb', + 'PasteScript', + 'chameleon.core [lxml]', + 'chameleon.zpt', + 'chameleon.genshi', + 'Routes', + 'setuptools', + ] + setup(name='repoze.bfg', version=__version__, description='A web framework for WSGI', @@ -47,31 +62,8 @@ setup(name='repoze.bfg', include_package_data=True, namespace_packages=['repoze', 'repoze.bfg'], zip_safe=False, - install_requires=[ - 'zope.interface', - 'zope.component', - 'zope.testing', - 'zope.hookable', - 'zope.event', - 'WebOb', - 'PasteScript', - 'z3c.pt [lxml]', - 'Routes', - 'setuptools', - ], - tests_require=[ - 'zope.interface', - 'zope.component', - 'zope.testing', - 'zope.hookable', - 'zope.event', - 'WebOb', - 'z3c.pt [lxml]', - 'Paste', - 'Routes', - 'Sphinx', - 'docutils', - ], + install_requires = install_requires, + tests_require= install_requires + ['Sphinx', 'docutils'], test_suite="repoze.bfg.tests", entry_points = """\ [paste.paster_create_template] -- cgit v1.2.3