summaryrefslogtreecommitdiff
path: root/repoze
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-07-26 00:26:10 +0000
committerChris McDonough <chrism@agendaless.com>2010-07-26 00:26:10 +0000
commit250c0218d0bd7dab6ea7e16c7051af71394f2a63 (patch)
treed57c38c27b72a483a3db9b1150d20553a93472d8 /repoze
parent2eb64f7a8bc7830667c3cb924bb5c13be3859b38 (diff)
downloadpyramid-250c0218d0bd7dab6ea7e16c7051af71394f2a63.tar.gz
pyramid-250c0218d0bd7dab6ea7e16c7051af71394f2a63.tar.bz2
pyramid-250c0218d0bd7dab6ea7e16c7051af71394f2a63.zip
merge generic_rendering branch
Diffstat (limited to 'repoze')
-rw-r--r--repoze/bfg/chameleon_text.py50
-rw-r--r--repoze/bfg/chameleon_zpt.py50
-rw-r--r--repoze/bfg/configuration.py273
-rw-r--r--repoze/bfg/interfaces.py9
-rw-r--r--repoze/bfg/renderers.py182
-rw-r--r--repoze/bfg/settings.py4
-rw-r--r--repoze/bfg/testing.py116
-rw-r--r--repoze/bfg/tests/test_chameleon_text.py87
-rw-r--r--repoze/bfg/tests/test_chameleon_zpt.py86
-rw-r--r--repoze/bfg/tests/test_configuration.py158
-rw-r--r--repoze/bfg/tests/test_renderers.py307
-rw-r--r--repoze/bfg/tests/test_testing.py71
12 files changed, 980 insertions, 413 deletions
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