[z3-checkins] r52153 - in z3/deliverance/trunk/deliverance: . test-data/urienv

tseaver at codespeak.net tseaver at codespeak.net
Tue Mar 4 17:54:11 CET 2008


Author: tseaver
Date: Tue Mar  4 17:54:10 2008
New Revision: 52153

Added:
   z3/deliverance/trunk/deliverance/test-data/urienv/
      - copied from r52150, z3/deliverance/branches/urienv/deliverance/test-data/urienv/
Modified:
   z3/deliverance/trunk/deliverance/test_wsgi.py
   z3/deliverance/trunk/deliverance/utils.py
   z3/deliverance/trunk/deliverance/wsgimiddleware.py
Log:
Merge urienv branch:

- (wsgimiddleware): Wrap long lines per PEP 8.

- (wsgimiddleware) Expose all middleware constructor arguments via
  Paste config.

- (wsgimiddleware) Check for environmental overrides of theme URI, rule
  URI, serializer, and apply.

- (utils) Added APIs for parsing non-string config values 
  ('resolve_callable', 'resolve_dotted_or_egg', 'bool_from_string').

- (utils) Added APIs for getting / setting environmental overrides of
  theme URI, rule URI, serializer.


Modified: z3/deliverance/trunk/deliverance/test_wsgi.py
==============================================================================
--- z3/deliverance/trunk/deliverance/test_wsgi.py	(original)
+++ z3/deliverance/trunk/deliverance/test_wsgi.py	Tue Mar  4 17:54:10 2008
@@ -23,6 +23,7 @@
 aggregate_data = os.path.join(os.path.dirname(__file__), 'test-data', 'aggregate')
 aggregate2_data = os.path.join(os.path.dirname(__file__), 'test-data', 'aggregate2')
 ignore_data = os.path.join(os.path.dirname(__file__), 'test-data', 'ignore')
+urienv_data = os.path.join(os.path.dirname(__file__), 'test-data', 'urienv')
 
 static_app = StaticURLParser(static_data)
 tasktracker_app = StaticURLParser(tasktracker_data)
@@ -34,6 +35,7 @@
 aggregate_app = StaticURLParser(aggregate_data)
 aggregate2_app = StaticURLParser(aggregate2_data)
 ignore_app = StaticURLParser(ignore_data)
+urienv_app = StaticURLParser(urienv_data)
 
 def html_string_compare(astr, bstr):
     """
@@ -313,11 +315,51 @@
                   headers={'If-Modified-Since': formatdate(then-15)})
     status = res.status
     assert(status == 200)
+
+def do_rule_uri_environ(renderer_type, name):
+    from deliverance.utils import set_rule_uri
+    wsgi_app = DeliveranceMiddleware(urienv_app, 'theme.html', 'rules.xml',
+                                     renderer_type)
+    environ = {}
+    set_rule_uri(environ, 'rules2.xml')
+    app = TestApp(wsgi_app, extra_environ=environ)
+    res = app.get('/example.html')
+    res2 = app.get('/example_expected_rule_uri_environ.html?notheme')
+    html_string_compare(res.body, res2.body)
+
+def do_theme_uri_environ(renderer_type, name):
+    from deliverance.utils import set_theme_uri
+    wsgi_app = DeliveranceMiddleware(urienv_app, 'theme.html', 'rules.xml',
+                                     renderer_type)
+    environ = {}
+    set_theme_uri(environ, 'theme2.html')
+    app = TestApp(wsgi_app, extra_environ=environ)
+    res = app.get('/example.html')
+    res2 = app.get('/example_expected_theme_uri_environ.html?notheme')
+    html_string_compare(res.body, res2.body)
     
+def do_serializer_environ(renderer_type, name):
+    from deliverance.utils import set_serializer
+    wsgi_app = DeliveranceMiddleware(urienv_app, 'theme.html', 'rules.xml',
+                                     renderer_type)
+    environ = {}
+    set_serializer(environ, 'deliverance.test_wsgi:_uppercaseTextNodes')
+    app = TestApp(wsgi_app, extra_environ=environ)
+    res = app.get('/example.html')
+    res2 = app.get('/example_expected_uppercase.html?notheme')
+    html_string_compare(res.body, res2.body)
+
 
+def _uppercaseTextNodes(content):
+    from htmlserialize import tostring
+    return tostring(content,
+                    doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN",
+                                  "http://www.w3.org/TR/html4/loose.dtd")
+                   ).upper()
 
 RENDERER_TYPES = ['py', 'xslt']
-TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate, do_aggregate2, do_cache, do_ignore, do_ignore_header ] 
+TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate, do_aggregate2, do_cache, do_ignore, do_ignore_header, do_rule_uri_environ, do_theme_uri_environ, do_serializer_environ ] 
+
 def test_all():
     for renderer_type in RENDERER_TYPES:
         for test_func in TEST_FUNCS: 

Modified: z3/deliverance/trunk/deliverance/utils.py
==============================================================================
--- z3/deliverance/trunk/deliverance/utils.py	(original)
+++ z3/deliverance/trunk/deliverance/utils.py	Tue Mar  4 17:54:10 2008
@@ -535,3 +535,44 @@
         text = etree.tostring(rule)
         text = text.replace(' xmlns="http://www.plone.org/deliverance"', '')
         return text
+
+# API for sharing overridden theme / rule URIs with other middleware layers.
+
+_THEME_URI_KEY = 'deliverance.theme_uri'
+
+def set_theme_uri(environ, uri):
+    environ[_THEME_URI_KEY] = uri
+
+def get_theme_uri(environ, default=None):
+    return environ.get(_THEME_URI_KEY, default)
+
+_RULE_URI_KEY = 'deliverance.rule_uri'
+
+def set_rule_uri(environ, uri):
+    environ[_RULE_URI_KEY] = uri
+
+def get_rule_uri(environ, default=None):
+    return environ.get(_RULE_URI_KEY, default)
+
+_SERIALIZER_KEY = 'deliverance.serializer'
+
+def set_serializer(environ, dotted_or_egg):
+    environ[_SERIALIZER_KEY] = dotted_or_egg
+
+def get_serializer(environ, default=None):
+    return resolve_callable(environ.get(_SERIALIZER_KEY, default))
+
+def resolve_callable(dotted_or_egg):
+    if isinstance(dotted_or_egg, basestring):
+        return resolve_dotted_or_egg(dotted_or_egg)
+    return dotted_or_egg
+
+def resolve_dotted_or_egg(dotted_or_egg):
+    from pkg_resources import EntryPoint
+    return EntryPoint.parse('x=%s' % dotted_or_egg).load(False)
+
+def bool_from_string(value):
+    if isinstance(value, basestring):
+        if value.lower() in ('false', 'no'):
+            return False
+    return bool(value)

Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py
==============================================================================
--- z3/deliverance/trunk/deliverance/wsgimiddleware.py	(original)
+++ z3/deliverance/trunk/deliverance/wsgimiddleware.py	Tue Mar  4 17:54:10 2008
@@ -13,9 +13,16 @@
 from paste.request import construct_url
 from paste.response import header_value, replace_header
 from htmlserialize import tostring
+from deliverance.utils import bool_from_string
 from deliverance.utils import DeliveranceError
 from deliverance.utils import DELIVERANCE_ERROR_PAGE
-from deliverance.resource_fetcher import InternalResourceFetcher, FileResourceFetcher, ExternalResourceFetcher
+from deliverance.utils import get_theme_uri
+from deliverance.utils import get_rule_uri
+from deliverance.utils import get_serializer
+from deliverance.utils import resolve_callable
+from deliverance.resource_fetcher import InternalResourceFetcher
+from deliverance.resource_fetcher import FileResourceFetcher
+from deliverance.resource_fetcher import ExternalResourceFetcher
 from deliverance import cache_utils
 import sys 
 import datetime
@@ -27,12 +34,19 @@
 DELIVERANCE_BASE_URL = 'deliverance.base-url'
 DELIVERANCE_CACHE = 'deliverance.cache'
 
-IGNORE_EXTENSIONS = ['js','css','gif','jpg','jpeg','pdf','ps','doc','png','ico','mov','mpg','mpeg', 'mp3','m4a', 
-                     'txt','rtf', 'swf', 'wav', 'zip', 'wmv', 'ppt', 'gz', 'tgz', 'jar', 'xls', 'bmp', 'tif', 'tga', 
-                     'hqx', 'avi']
+IGNORE_EXTENSIONS = ['js', 'css', 'gif', 'jpg', 'jpeg', 'pdf', 'ps', 'doc',
+                     'png', 'ico', 'mov', 'mpg', 'mpeg', 'mp3', 'm4a', 'txt',
+                     'rtf', 'swf', 'wav', 'zip', 'wmv', 'ppt', 'gz', 'tgz',
+                     'jar', 'xls', 'bmp', 'tif', 'tga', 'hqx', 'avi',
+                    ]
 
 IGNORE_URL_PATTERN = re.compile("^.*\.(%s)$" % '|'.join(IGNORE_EXTENSIONS))
 
+def _toHTML(content):
+    return tostring(content,
+                    doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN",
+                                  "http://www.w3.org/TR/html4/loose.dtd"))
+
 class DeliveranceMiddleware(object):
     """
     a DeliveranceMiddleware object exposes a single deliverance 
@@ -41,7 +55,7 @@
 
     def __init__(self, app, theme_uri, rule_uri,
                  renderer='py', merge_cache_control=False,
-                 is_internal_uri=None):
+                 is_internal_uri=None, serializer=None):
         """
         initializer
         
@@ -60,11 +74,14 @@
           should be considered 'internal'(passed to the
           subapplication) and false if the requestshould be send
           over the network. 
+        serializer:  dotted name or entry point indicdating a callable used
+          to post-process rendered output.  Defaults to the '_toHTML' function
+          above.
         """
         self.app = app
         self.theme_uri = theme_uri
         self.rule_uri = rule_uri
-        self.merge_cache_control = merge_cache_control
+        self.merge_cache_control = bool_from_string(merge_cache_control)
 
         if renderer == 'py':
             import interpreter
@@ -73,11 +90,15 @@
             import xslt
             self._rendererType = xslt.Renderer
         elif renderer is None or isinstance(renderer, basestring):
-            raise ValueError("Unknown Renderer: %s - Expecting 'py' or 'xslt'" % renderer)
+            raise ValueError("Unknown Renderer: %s - Expecting 'py' or 'xslt'"
+                               % renderer)
         else:
             self._rendererType = renderer
 
-        self._is_internal_uri = is_internal_uri
+        self._is_internal_uri = resolve_callable(is_internal_uri)
+        if serializer is None:
+            serializer = _toHTML
+        self.serializer = serializer
 
     def get_renderer(self, environ):
         return self.create_renderer(environ)
@@ -88,11 +109,11 @@
         information passed to the initializer.  A new copy 
         of the theme and rules is retrieved. 
         """
-        theme = self.theme(environ)
-        rule = self.rule(environ)
+        theme, theme_uri = self.theme(environ)
+        rule, rule_uri = self.rule(environ)
         full_theme_uri = urlparse.urljoin(
             construct_url(environ, with_path_info=False),
-            self.theme_uri)
+            theme_uri)
 
         def reference_resolver(href, parse, encoding=None):
             text = self.get_resource(environ, href)
@@ -109,7 +130,7 @@
         try:
             parsedTheme = parseHTML(theme)
         except Exception, message:
-            newmessage = "Unable to parse theme page (" + self.theme_uri + ")"
+            newmessage = "Unable to parse theme page (" + theme_uri + ")"
             if message:
                 newmessage += ":" + str(message)
             raise DeliveranceError(newmessage)
@@ -124,33 +145,40 @@
             theme=parsedTheme,
             theme_uri=full_theme_uri,
             rule=parsedRule, 
-            rule_uri=self.rule_uri,
+            rule_uri=rule_uri,
             reference_resolver=reference_resolver)
 
-    def rule(self, environ):
-        """
+    def rule(self, environ=None):
+        """ environ -> (rule, rule_uri)
         retrieves the data referred to by the rule_uri passed to the 
         initializer. 
         """
+        if environ is None:
+            environ = {}
+        rule_uri = get_rule_uri(environ, self.rule_uri)
         try:
-            return self.get_resource(environ, self.rule_uri)
+            return (self.get_resource(environ, rule_uri), rule_uri)
         except Exception, message:
-            newmessage = "Unable to retrieve rules from " + self.rule_uri 
+            newmessage = "Unable to retrieve rules from " + rule_uri 
             if message:
                 newmessage += ": " + str(message)
 
             raise DeliveranceError(newmessage)
 
-    def theme(self, environ):
-        """
+    def theme(self, environ=None):
+        """ environ -> (theme, theme_uri)
+
         retrieves the data referred to by the theme_uri passed to the 
         initializer. 
         """
+        if environ is None:
+            environ = {}
+        theme_uri = get_theme_uri(environ, self.theme_uri)
         try:
-            return self.get_resource(environ, self.theme_uri)
+            return (self.get_resource(environ, theme_uri), theme_uri)
         except Exception, message:
-            message.public_html = 'Unable to retrieve theme page from %s: %s' % (
-                self.theme_uri, message)
+            message.public_html = ('Unable to retrieve theme page from %s: %s'
+                                    % (theme_uri, message))
             raise
 
     def __call__(self, environ, start_response):
@@ -162,13 +190,16 @@
         initializer. 
         """
         qs = environ.get('QUERY_STRING', '')
-        environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False)
+        environ[DELIVERANCE_BASE_URL] = construct_url(environ,
+                                                      with_path_info=False,
+                                                      with_query_string=False)
         environ[DELIVERANCE_CACHE] = {} 
         notheme = 'notheme' in qs
         if environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest':
             notheme = True
         if notheme:
-            # eliminate the deliverance notheme query argument for the subrequest
+            # eliminate the deliverance notheme query argument for
+            # the subrequest
             if qs == 'notheme': 
                 environ['QUERY_STRING'] = ''
             elif qs.endswith('&notheme'): 
@@ -227,17 +258,18 @@
         type = header_value(headers, 'content-type')
         if type is None:
             return True # yerg, 304s can have no content-type 
-        return type.startswith('text/html') or type.startswith('application/xhtml+xml')
+        return (type.startswith('text/html') or
+                type.startswith('application/xhtml+xml'))
 
     def filter_body(self, environ, body):
         """
-        returns the result of the deliverance transformation on the string 'body' 
-        in the context of environ. The result is a string containing HTML. 
+        returns the result of the deliverance transform on the string 'body' 
+        in the context of environ. The result is a string containing HTML,
+        or whatever the configured serializer makes it.
         """
         content = self.get_renderer(environ).render(parseHTML(body))
-
-        return tostring(content, doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN",
-                                               "http://www.w3.org/TR/html4/loose.dtd"))
+        serializer = get_serializer(environ, self.serializer)
+        return serializer(content)
 
 
     def rebuild_check(self, environ, start_response): 
@@ -247,7 +279,8 @@
 
         etag_map = {}
         if 'HTTP_IF_NONE_MATCH' in environ: 
-            etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH'])
+            etag_map = cache_utils.parse_merged_etag(
+                                           environ['HTTP_IF_NONE_MATCH'])
 	    tag = etag_map.get(content_url, None)
 	    environ['HTTP_IF_NONE_MATCH'] = tag
 	    if tag: 
@@ -284,8 +317,8 @@
             return (status, headers, body)
             
         # got 304 Not Modified for content, check other resources 
-        rules = etree.XML(self.rule(environ))
-        resources = self.get_resource_uris(rules)        
+        rules = etree.XML(self.rule(environ)[0])
+        resources = self.get_resource_uris(rules, environ)        
         if self.any_modified(environ, resources, etag_map): 
             # something changed, 
             # get the content explicitly and give it back 
@@ -369,7 +402,8 @@
             else:
                 loc = ''
             raise DeliveranceError(
-                "Request for internal resource at %s (%r) failed with status code %r%s"
+                "Request for internal resource at %s (%r) failed "
+                "with status code %r%s"
                 % (construct_url(environ), path_info, status,
                    loc))
 
@@ -427,14 +461,18 @@
         return cleaned
     
 
-    def get_resource_uris(self, rules): 
+    def get_resource_uris(self, rules, environ=None): 
         """
         retrieves a list of uris pointing to the resources that 
         are components of rendering (excluding content) 
         """
+        if environ is None:
+            environ = {}
         resources = Set()
-        resources.add(self.rule_uri)
-        resources.add(self.theme_uri)
+        rule_uri = get_rule_uri(environ, self.rule_uri)
+        resources.add(rule_uri)
+        theme_uri = get_theme_uri(environ, self.theme_uri)
+        resources.add(theme_uri)
 
         for rule in rules: 
             href = rule.get("href",None)
@@ -444,16 +482,16 @@
         return list(resources)
 
             
-    def check_modification(self, environ, uri, httpdate_since=None, etag=None): 
+    def check_modification(self, environ, uri, httpdate_since=None, etag=None):
         """
-        if httpdate_since is set to an httpdate the If-Modified-Since HTTP header 
-          is used to check for modification 
+        if httpdate_since is set to an httpdate the If-Modified-Since HTTP
+        header is used to check for modification 
 
-        if etag is set to an etag for the resource, the If-None-Match HTTP header 
-          is used to check for modification 
+        if etag is set to an etag for the resource, the If-None-Match HTTP
+        header is used to check for modification 
 
-        the resulting (status, headers, body) tuple for the request is stored in 
-        environ[DELIVERANCE_CACHE][uri]. 
+        the resulting (status, headers, body) tuple for the request is stored
+        in environ[DELIVERANCE_CACHE][uri]. 
 
         """
 
@@ -498,16 +536,39 @@
         # blacklisting can happen here as well 
         return re.match(IGNORE_URL_PATTERN, url) is not None
 
+
+def always_external(uri, environ):
+    """Always return False so the external loader is used.
+
+    o Configure in paste config using the following:
+    
+      is_internal_uri = deliverance.wsgimiddleware:always_external
+    """
+    return False
+
+
 def make_filter(app, global_conf,
-                theme_uri=None, rule_uri=None,
-                renderer='py'):
+                theme_uri=None,
+                rule_uri=None,
+                renderer='py',
+                merge_cache_control=False,
+                is_internal_uri=None,
+                serializer=None,
+               ):
+    """ Configure DeliveranceError via Paste config.
+    """
     assert theme_uri is not None, (
         "You must give a theme_uri")
     assert rule_uri is not None, (
         "You must give a rule_uri")
-    return DeliveranceMiddleware(
-        app, theme_uri, rule_uri,
-        renderer=renderer)
+    return DeliveranceMiddleware(app=app,
+                                 theme_uri=theme_uri,
+                                 rule_uri=rule_uri,
+                                 renderer=renderer,
+                                 merge_cache_control=merge_cache_control,
+                                 is_internal_uri=is_internal_uri,
+                                 serializer=serializer,
+                                )
 
 _http_equiv_re = re.compile(r'<meta\s+[^>]*http-equiv="?content-type"?[^>]*>', re.I|re.S)
 _head_re = re.compile(r'<head[^>]*>', re.I|re.S)


More information about the z3-checkins mailing list