[Checkins] SVN: z3c.jbot/trunk/ Fixed issue with multiple layers; refactor, added tests.
Malthe Borch
mborch at gmail.com
Sat Sep 26 11:27:32 EDT 2009
Log message for revision 104565:
Fixed issue with multiple layers; refactor, added tests.
Changed:
A z3c.jbot/trunk/CHANGES.txt
U z3c.jbot/trunk/README.txt
U z3c.jbot/trunk/setup.py
U z3c.jbot/trunk/z3c/jbot/README.txt
U z3c.jbot/trunk/z3c/jbot/__init__.py
U z3c.jbot/trunk/z3c/jbot/manager.py
U z3c.jbot/trunk/z3c/jbot/metaconfigure.py
A z3c.jbot/trunk/z3c/jbot/tests/overrides/
A z3c.jbot/trunk/z3c/jbot/tests/overrides/http/
A z3c.jbot/trunk/z3c/jbot/tests/overrides/http/z3c.jbot.tests.templates.example.pt
A z3c.jbot/trunk/z3c/jbot/tests/overrides/https/
A z3c.jbot/trunk/z3c/jbot/tests/overrides/https/z3c.jbot.tests.templates.example.pt
A z3c.jbot/trunk/z3c/jbot/tests/overrides/interface/
A z3c.jbot/trunk/z3c/jbot/tests/overrides/interface/z3c.jbot.tests.templates.example.pt
A z3c.jbot/trunk/z3c/jbot/tests/overrides/request/
A z3c.jbot/trunk/z3c/jbot/tests/overrides/request/z3c.jbot.tests.templates.example.pt
D z3c.jbot/trunk/z3c/jbot/tests/templates/z3c.jbot.tests.templates.example.pt
U z3c.jbot/trunk/z3c/jbot/utility.py
-=-
Added: z3c.jbot/trunk/CHANGES.txt
===================================================================
--- z3c.jbot/trunk/CHANGES.txt (rev 0)
+++ z3c.jbot/trunk/CHANGES.txt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -0,0 +1,20 @@
+Changes
+=======
+
+In next release...
+
+- Improved test coverage.
+
+- Refactored code, improving performance.
+
+- Fixed issue with multiple layers.
+
+0.2 (2008-07-14)
+----------------
+
+- Added layer support.
+
+0.1 (2007-11-27)
+----------------
+
+- Initial public release.
Modified: z3c.jbot/trunk/README.txt
===================================================================
--- z3c.jbot/trunk/README.txt 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/README.txt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -1,5 +1,5 @@
Overview
---------
+========
The z3c.jbot (or "Just a bunch of templates") package allows drop-in
page template overrides.
@@ -11,7 +11,6 @@
Overrides may be registered for a specific layer or any layer.
-
Canonical filename
------------------
@@ -24,28 +23,24 @@
Suppose you want to override: /plone/app/layout/viewlets/logo.pt
You would use the filename: plone.app.layout.viewlets.logo.pt
-
Registering a on overrides directory
------------------------------------
A Zope component configuration directive is available to configure
-overrides.
+overrides::
<include package="z3c.jbot" file="meta.zcml" />
-
+
<browser:templateOverrides
directory="<directory>"
layer="<layer>" />
-
Performance considerations
--------------------------
The use of jbot adds to the general page load time. On a site with
-many templates this may be as much as 20 ms per request (a 7% increase
-on my machine).
+many templates this may be as much as 10 ms per request.
-
Author
------
Modified: z3c.jbot/trunk/setup.py
===================================================================
--- z3c.jbot/trunk/setup.py 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/setup.py 2009-09-26 15:27:31 UTC (rev 104565)
@@ -1,12 +1,12 @@
from setuptools import setup, find_packages
import sys, os
-version = '0.2dev'
+version = '0.3dev'
setup(name='z3c.jbot',
version=version,
description="Drop-in template overrides.",
- long_description=open("README.txt").read(),
+ long_description=open("README.txt").read() + open("CHANGES.txt").read(),
classifiers=[
"Framework :: Zope2",
"Framework :: Zope3",
Modified: z3c.jbot/trunk/z3c/jbot/README.txt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/README.txt 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/z3c/jbot/README.txt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -11,7 +11,7 @@
>>> from zope.pagetemplate.pagetemplatefile import PageTemplateFile
>>> template = PageTemplateFile("tests/templates/example.pt")
-
+
A call to the template will render it.
>>> template()
@@ -30,41 +30,23 @@
>>> import z3c.jbot.tests
>>> directory = z3c.jbot.tests.__path__[0]
-Register template manager factory. We'll register it for
-``zope.interface.Interface`` which makes it available on all layers.
-
- >>> import z3c.jbot.manager
- >>> import z3c.jbot.interfaces
- >>> factory = z3c.jbot.manager.TemplateManagerFactory()
- >>> component.provideAdapter(
- ... factory, (interface.Interface,),
- ... z3c.jbot.interfaces.ITemplateManager)
+Register overrides directory (by default for any request); we confirm
+that it's registered for the same template manager.
-Register overrides directory.
-
- >>> manager = factory.manager
- >>> manager.registerDirectory("%s/templates" % directory)
+ >>> from z3c.jbot.metaconfigure import handler
+ >>> manager = handler("%s/overrides/interface" % directory, interface.Interface)
-Verify that we've registered the contents of the directory:
-
- >>> manager.paths
- {'z3c.jbot.tests.templates.example.pt': '.../z3c.jbot.tests.templates.example.pt',
- 'example.pt': '.../example.pt'}
-
-Notice that the file "z3c.jbot.tests.templates.example.pt" is the
-dotted name for the original example page template file.
-
We should now see that the new filename will be used for rendering:
>>> template()
- u'This template will override the example template.\n'
+ u'Override from ./interface.\n'
Before we proceed we'll clean up.
- >>> manager.unregisterDirectory("%s/templates" % directory)
+ >>> manager.unregisterAllDirectories()
The template does indeed render the original template.
-
+
>>> template()
u'This is an example page template.\n'
@@ -74,73 +56,112 @@
>>> template.filename
'.../z3c.jbot/z3c/jbot/tests/templates/example.pt'
-Overrides can be registered for a specific layer. Let's re-register an
-override template factory for the HTTP-request layer.
+Overrides can be registered for a specific layer. Let's register three
+more overrides, one for the general-purpose ``IRequest`` layer, one
+for the ``IHTTPRequest`` layer and one for a made-up ``IHTTPSRequest``
+layer.
- >>> from zope.publisher.interfaces.browser import IHTTPRequest
- >>> factory = z3c.jbot.manager.TemplateManagerFactory()
- >>> component.provideAdapter(
- ... factory, (IHTTPRequest,),
- ... z3c.jbot.interfaces.ITemplateManager, name='http')
+ >>> from zope.publisher.interfaces import IRequest
+ >>> from zope.publisher.interfaces.http import IHTTPRequest
+ >>> class IHTTPSRequest(IRequest):
+ ... """An HTTPS request."""
-Register overrides directory.
-
- >>> manager = factory.manager
- >>> manager.registerDirectory("%s/templates" % directory)
+Next we register an overrides directory for the ``IRequest`` layer.
-Let's set up an interaction with a base request.
+ >>> general = handler("%s/overrides/request" % directory, IRequest)
+Let's set up an interaction with a trivial participation.
+
+ >>> class Participation:
+ ... interaction = None
+
+ >>> participation = Participation()
>>> import zope.security.management
- >>> import zope.publisher.base
- >>> request = zope.publisher.base.BaseRequest("", {})
- >>> IHTTPRequest.providedBy(request)
+ >>> zope.security.management.newInteraction(participation)
+
+This participation does not provide even the basic request interface.
+
+ >>> IRequest.providedBy(participation)
False
- >>> zope.security.management.newInteraction(request)
-Since this request is not an HTTP-request, we don't expect the
-override to be enabled.
+We don't expect the template to be overriden for this interaction.
>>> template()
u'This is an example page template.\n'
-Let's now engage in an interaction with an HTTP-request.
-
- >>> interface.alsoProvides(request, IHTTPRequest)
+Let's upgrade it.
+
+ >>> request = participation
+ >>> interface.alsoProvides(request, IRequest)
+
>>> template()
- u'This template will override the example template.\n'
+ u'Override from ./request.\n'
>>> template._v_cooked
1
-
+
Going back to a basic request.
- >>> interface.noLongerProvides(request, IHTTPRequest)
- >>> IHTTPRequest.providedBy(request)
- False
-
+ >>> interface.noLongerProvides(request, IRequest)
>>> template()
u'This is an example page template.\n'
Let's verify that we only cook once per template source.
- >>> import z3c.jbot.utility
>>> output = template()
>>> template._v_last_read and template._v_cooked
1
- >>> interface.alsoProvides(request, IHTTPRequest)
+ >>> interface.alsoProvides(request, IRequest)
>>> output = template()
>>> template._v_last_read and template._v_cooked
1
>>> template()
- u'This template will override the example template.\n'
+ u'Override from ./request.\n'
- >>> for manager in z3c.jbot.utility.getManagers():
- ... manager.unregisterDirectory("%s/templates" % directory)
-
+Now, if we switch to the HTTP-layer.
+
+ >>> interface.noLongerProvides(request, IRequest)
+ >>> interface.alsoProvides(request, IHTTPRequest)
+
+ >>> template()
+ u'Override from ./request.\n'
+
+ >>> general.unregisterAllDirectories()
+
+ >>> template()
+ u'This is an example page template.\n'
+
+ >>> http = handler("%s/overrides/http" % directory, IHTTPRequest)
+ >>> https = handler("%s/overrides/https" % directory, IHTTPSRequest)
+
+ >>> template()
+ u'Override from ./http.\n'
+
+Switching to HTTPS.
+
>>> interface.noLongerProvides(request, IHTTPRequest)
-
+ >>> interface.alsoProvides(request, IHTTPSRequest)
+
+ >>> template()
+ u'Override from ./https.\n'
+
+ >>> interface.noLongerProvides(request, IHTTPSRequest)
+
+Unregister all directories (cleanup).
+
+ >>> for manager, layer in ((http, IHTTPRequest), (https, IHTTPSRequest)):
+ ... interface.alsoProvides(request, layer)
+ ... _ = template()
+ ... manager.unregisterAllDirectories()
+ ... interface.noLongerProvides(request, layer)
+
+The override is no longer in effect.
+
+ >>> template()
+ u'This is an example page template.\n'
+
Configuring template override directories in ZCML
-------------------------------------------------
@@ -155,42 +176,42 @@
>>> xmlconfig.xmlconfig(StringIO("""
... <configure xmlns="http://namespaces.zope.org/browser">
- ... <templateOverrides directory="%s/templates" />
+ ... <templateOverrides directory="%s/overrides/interface" />
... </configure>
... """ % directory))
Once again, the override will be in effect.
-
+
>>> template()
- u'This template will override the example template.\n'
+ u'Override from ./interface.\n'
Providing the HTTP-request layer does not change this.
>>> interface.alsoProvides(request, IHTTPRequest)
>>> template()
- u'This template will override the example template.\n'
+ u'Override from ./interface.\n'
Unregister overrides.
-
- >>> manager = tuple(z3c.jbot.utility.getManagers())[0]
- >>> manager.unregisterDirectory("%s/templates" % directory)
-
+
+ >>> for manager in z3c.jbot.utility.getManagers():
+ ... manager.unregisterAllDirectories()
+
>>> template()
u'This is an example page template.\n'
-
+
Let's register overrides for the HTTP-request layer.
>>> xmlconfig.xmlconfig(StringIO("""
... <configure xmlns="http://namespaces.zope.org/browser">
... <templateOverrides
- ... directory="%s/templates"
+ ... directory="%s/overrides/http"
... layer="zope.publisher.interfaces.browser.IHTTPRequest" />
... </configure>
... """ % directory))
-If we now provide the HTTP-request layer, the override becomes active.
+Since we now provide the HTTP-request layer, the override is used.
>>> template()
- u'This template will override the example template.\n'
+ u'Override from ./http.\n'
Modified: z3c.jbot/trunk/z3c/jbot/__init__.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/__init__.py 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/z3c/jbot/__init__.py 2009-09-26 15:27:31 UTC (rev 104565)
@@ -1,4 +1,3 @@
-from zope import interface
from zope.pagetemplate.pagetemplatefile import PageTemplateFile
import utility
@@ -15,6 +14,8 @@
except:
pass
+_v_cache = threading.local()
+
class LayerProperty(property):
"""Layer-specific property class.
@@ -30,46 +31,59 @@
This pattern takes into account that attributes may be set before
the property is defined on the class.
"""
-
+
def __init__(self, cls, name):
self.name = name
self.default = getattr(cls, name, None)
property.__init__(self, self._get, self._set)
-
+
def _get(self, template):
key = self.name
- layer = getattr(template._v_cache, 'layer', None)
- attrs = getattr(template, '_v_attrs', template.__dict__)
- if (layer, key) in attrs:
+ __dict__ = template.__dict__
+
+ # note: exceptions should not arise during normal service
+ try:
+ layer = _v_cache.layer
+ except AttributeError:
+ layer = None
+
+ try:
+ # any of these could raise a key-error
+ attrs = __dict__['_v_attrs']
return attrs[layer, key]
- return attrs.get(key) or template.__dict__.get(key) or self.default
-
+ except KeyError:
+ return __dict__.get(key) or self.default
+
def _set(self, template, value):
key = self.name
- layer = getattr(template._v_cache, 'layer', None)
- attrs = template.__dict__.get('_v_attrs')
- if attrs is None:
- attrs = template._v_attrs = {}
+ __dict__ = template.__dict__
- # set value
+ # note: exceptions should not arise during normal service
+ try:
+ layer = _v_cache.layer
+ except AttributeError:
+ layer = None
+
+ try:
+ attrs = __dict__['_v_attrs']
+ except KeyError:
+ attrs = __dict__['_v_attrs'] = {}
+
attrs[layer, key] = value
-
- # set default value
- attrs.setdefault(key, value)
-
+ __dict__.setdefault(key, value)
+
# registration hook to template manager
def jbot(func):
def patch(self, *args, **kwargs):
- # set layer
- self._v_cache.layer = utility.getLayer()
+ _v_cache.layer = utility.getLayer()
for manager in utility.getManagers():
# register template; this call returns ``True`` if
# template was invalidated
if manager.registerTemplate(self):
break
-
- return func(self, *args, **kwargs)
+
+ return func(self, *args, **kwargs)
return patch
logger.info("Patching page template classes for use with z3c.jbot...")
@@ -82,7 +96,5 @@
for pt_class in PT_CLASSES:
for name in ('_v_macros', '_v_program', '_v_cooked', '_v_errors',
'_v_last_read', '_v_warning', '_text_',
- 'filename', 'content_type', 'is_html'):
+ 'filename', '_filename', 'content_type', 'is_html'):
setattr(pt_class, name, LayerProperty(pt_class, name))
-
- setattr(pt_class, '_v_cache', threading.local())
Modified: z3c.jbot/trunk/z3c/jbot/manager.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/manager.py 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/z3c/jbot/manager.py 2009-09-26 15:27:31 UTC (rev 104565)
@@ -7,6 +7,7 @@
import interfaces
IGNORE = object()
+DELETE = object()
def root_length(a, b):
if b.startswith(a):
@@ -21,7 +22,7 @@
def find_zope2_product(path):
"""Check the Zope2 magic Products semi-namespace to see if the
path is part of a Product."""
-
+
_syspaths = sort_by_path(path, sys.modules["Products"].__path__)
syspath = _syspaths[0]
@@ -39,37 +40,41 @@
_syspaths = sort_by_path(path, syspaths)
syspath = _syspaths[0]
-
+
path = os.path.normpath(path)
if not path.startswith(syspath):
if utility.ZOPE_2:
return find_zope2_product(path)
return None
-
+
path = path[len(syspath):]
-
+
# convert path to dotted filename
if path.startswith(os.path.sep):
path = path[1:]
-
+
return path
class TemplateManagerFactory(object):
- def __init__(self):
- self.manager = TemplateManager()
+ def __init__(self, name):
+ self.manager = TemplateManager(name)
def __call__(self, layer):
return self.manager
-
+
class TemplateManager(object):
interface.implements(interfaces.ITemplateManager)
-
- def __init__(self):
+
+ def __init__(self, name):
self.syspaths = tuple(sys.path)
self.templates = {}
self.paths = {}
+ self.directories = set()
+ self.name = name
def registerDirectory(self, directory):
+ self.directories.add(directory)
+
for filename in os.listdir(directory):
if filename.endswith('.pt'):
self.paths[filename] = "%s/%s" % (directory, filename)
@@ -79,8 +84,10 @@
del self.templates[template]
def unregisterDirectory(self, directory):
+ self.directories.remove(directory)
+
templates = []
-
+
for template, filename in self.templates.items():
if filename in self.paths:
templates.append(template)
@@ -92,12 +99,18 @@
for template in templates:
self.registerTemplate(template)
del self.templates[template]
-
+ template.filename = template._filename
+ template._v_last_read = False
+
+ def unregisterAllDirectories(self):
+ for directory in tuple(self.directories):
+ self.unregisterDirectory(directory)
+
def registerTemplate(self, template):
# only register templates that have a filename attribute
if not hasattr(template, 'filename'):
return
-
+
# assert that the template is not already registered
filename = self.templates.get(template)
if filename is IGNORE:
@@ -110,18 +123,16 @@
# verify that override has not been unregistered
if filename is not None and filename not in paths:
- # restore original template
template.filename = template._filename
- delattr(template, '_filename')
del self.templates[template]
-
+
# check if an override exists
path = find_package(self.syspaths, template.filename)
if path is None:
# permanently ignore template
self.templates[template] = IGNORE
return
-
+
filename = path.replace(os.path.sep, '.')
if filename in paths:
path = paths[filename]
Modified: z3c.jbot/trunk/z3c/jbot/metaconfigure.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/metaconfigure.py 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/z3c/jbot/metaconfigure.py 2009-09-26 15:27:31 UTC (rev 104565)
@@ -5,10 +5,10 @@
import interfaces
def handler(directory, layer):
- gsm = component.getGlobalSiteManager()
+ lookup_all = component.getGlobalSiteManager().adapters.lookupAll
# check if a template manager already exists
- factories = set(factory for name, factory in gsm.adapters.lookupAll(
+ factories = set(factory for name, factory in lookup_all(
(layer,), interfaces.ITemplateManager))
# if factory is available on the interface bases of the layer we
@@ -16,18 +16,23 @@
if layer is interface.Interface:
base_factories = set()
else:
- base_factories = set(factory for name, factory in gsm.adapters.lookupAll(
- (interface.implementedBy(layer.__bases__),), interfaces.ITemplateManager))
+ base_factories = set()
+ for base in layer.__bases__:
+ for name, factory in lookup_all((base,), interfaces.ITemplateManager):
+ base_factories.add(factory)
try:
factory = factories.difference(base_factories).pop()
except KeyError:
- factory = manager.TemplateManagerFactory()
+ name = directory
+ factory = manager.TemplateManagerFactory(name)
component.provideAdapter(
- factory, (layer,), interfaces.ITemplateManager, name=directory)
+ factory, (layer,), interfaces.ITemplateManager, name=name)
factory(layer).registerDirectory(directory)
-
+
+ return factory(layer)
+
def templateOverridesDirective(_context, directory, layer=interface.Interface):
_context.action(
discriminator = ('override', directory, layer),
Added: z3c.jbot/trunk/z3c/jbot/tests/overrides/http/z3c.jbot.tests.templates.example.pt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/tests/overrides/http/z3c.jbot.tests.templates.example.pt (rev 0)
+++ z3c.jbot/trunk/z3c/jbot/tests/overrides/http/z3c.jbot.tests.templates.example.pt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -0,0 +1 @@
+Override from ./http.
Added: z3c.jbot/trunk/z3c/jbot/tests/overrides/https/z3c.jbot.tests.templates.example.pt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/tests/overrides/https/z3c.jbot.tests.templates.example.pt (rev 0)
+++ z3c.jbot/trunk/z3c/jbot/tests/overrides/https/z3c.jbot.tests.templates.example.pt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -0,0 +1 @@
+Override from ./https.
Added: z3c.jbot/trunk/z3c/jbot/tests/overrides/interface/z3c.jbot.tests.templates.example.pt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/tests/overrides/interface/z3c.jbot.tests.templates.example.pt (rev 0)
+++ z3c.jbot/trunk/z3c/jbot/tests/overrides/interface/z3c.jbot.tests.templates.example.pt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -0,0 +1 @@
+Override from ./interface.
Copied: z3c.jbot/trunk/z3c/jbot/tests/overrides/request/z3c.jbot.tests.templates.example.pt (from rev 104480, z3c.jbot/trunk/z3c/jbot/tests/templates/z3c.jbot.tests.templates.example.pt)
===================================================================
--- z3c.jbot/trunk/z3c/jbot/tests/overrides/request/z3c.jbot.tests.templates.example.pt (rev 0)
+++ z3c.jbot/trunk/z3c/jbot/tests/overrides/request/z3c.jbot.tests.templates.example.pt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -0,0 +1 @@
+Override from ./request.
Deleted: z3c.jbot/trunk/z3c/jbot/tests/templates/z3c.jbot.tests.templates.example.pt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/tests/templates/z3c.jbot.tests.templates.example.pt 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/z3c/jbot/tests/templates/z3c.jbot.tests.templates.example.pt 2009-09-26 15:27:31 UTC (rev 104565)
@@ -1 +0,0 @@
-This template will override the example template.
Modified: z3c.jbot/trunk/z3c/jbot/utility.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/utility.py 2009-09-26 06:24:52 UTC (rev 104564)
+++ z3c.jbot/trunk/z3c/jbot/utility.py 2009-09-26 15:27:31 UTC (rev 104565)
@@ -24,7 +24,7 @@
return site.request
except AttributeError:
return site.REQUEST
-
+
try:
i = zope.security.management.getInteraction()
except zope.security.interfaces.NoInteraction:
@@ -45,6 +45,6 @@
def getManagers():
layer = getLayer()
gsm = component.getGlobalSiteManager()
-
+
for name, factory in gsm.adapters.lookupAll((layer,), interfaces.ITemplateManager):
yield factory(layer)
More information about the checkins
mailing list