You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mod_python-commits@quetz.apache.org by nl...@apache.org on 2005/05/01 11:36:55 UTC

svn commit: r165474 - in /httpd/mod_python/trunk: lib/python/mod_python/publisher.py test/htdocs/tests.py test/test.py

Author: nlehuen
Date: Sun May  1 02:36:54 2005
New Revision: 165474

URL: http://svn.apache.org/viewcvs?rev=165474&view=rev
Log:
New version of the publisher with support for old-style & new-style classes, iterators and generators.

Modified:
    httpd/mod_python/trunk/lib/python/mod_python/publisher.py
    httpd/mod_python/trunk/test/htdocs/tests.py
    httpd/mod_python/trunk/test/test.py

Modified: httpd/mod_python/trunk/lib/python/mod_python/publisher.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/lib/python/mod_python/publisher.py?rev=165474&r1=165473&r2=165474&view=diff
==============================================================================
--- httpd/mod_python/trunk/lib/python/mod_python/publisher.py (original)
+++ httpd/mod_python/trunk/lib/python/mod_python/publisher.py Sun May  1 02:36:54 2005
@@ -154,55 +154,18 @@
     realm, user, passwd = process_auth(req, module)
 
     # resolve the object ('traverse')
-    try:
-        object = resolve_object(req, module, func_path, realm, user, passwd)
-    except AttributeError:
-        raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
-    
-    # not callable, a class or an unbound method
-    if (not callable(object) or 
-        type(object) is ClassType or
-        (hasattr(object, 'im_self') and not object.im_self)):
-
-        result = str(object)
-        
-    else:
-        # callable, (but not a class or unbound method)
-        
-        # process input, if any
-        req.form = util.FieldStorage(req, keep_blank_values=1)
-        result = util.apply_fs_data(object, req.form, req=req)
+    object = resolve_object(req, module, func_path, realm, user, passwd)
 
-    # Now we'll send what the published object has returned
-    # TODO : I'm not sure we should always return apache.OK if something was sent
-    # or if there was an internal redirect.
-    if result or req.bytes_sent > 0 or req.next:
-        
-        if result is None:
-            result = ""
-        elif type(result) == UnicodeType:
-            return result
-        else:
-            result = str(result)
-
-        # unless content_type was manually set, we will attempt
-        # to guess it
-        if not req._content_type_set:
-            # make an attempt to guess content-type
-            if result[:100].strip()[:6].lower() == '<html>' \
-               or result.find('</') > 0:
-                req.content_type = 'text/html'
-            else:
-                req.content_type = 'text/plain'
+    # publish the object
+    published = publish_object(req, object)
+    
+    # we log a message if nothing was published, it helps with debugging
+    if (not published) and (req.bytes_sent==0) and (req.next is None):
+        log=int(req.get_config().get("PythonDebug", 0))
+        if log:
+            req.log_error("mod_python.publisher: nothing to publish.")
 
-        if req.method != "HEAD":
-            req.write(result)
-        else:
-            req.write("")
-        return apache.OK
-    else:
-        req.log_error("mod_python.publisher: %s returned nothing." % `object`)
-        return apache.HTTP_INTERNAL_SERVER_ERROR
+    return apache.OK
 
 def process_auth(req, object, realm="unknown", user=None, passwd=None):
 
@@ -312,14 +275,16 @@
 tp_rules.update({
     # Those are not traversable nor publishable
     ModuleType          : (False, False),
+    BuiltinFunctionType : (False, False),
+    
+    # This may change in the near future to (False, True)
     ClassType           : (False, False),
     TypeType            : (False, False),
-    BuiltinFunctionType : (False, False),
     
-    # XXX Generators should be publishable, see
-    # http://issues.apache.org/jira/browse/MODPYTHON-15
-    # Until they are, it is not interesting to publish them
-    GeneratorType       : (False, False),
+    # Publishing a generator may not seem to makes sense, because
+    # it can only be done once. However, we could get a brand new generator
+    # each time a new-style class property is accessed.
+    GeneratorType       : (False, True),
     
     # Old-style instances are traversable
     InstanceType        : (True, True),
@@ -358,7 +323,10 @@
         # we know it's OK to call getattr
         # note that getattr can really call some code because
         # of property objects (or attribute with __get__ special methods)...
-        obj = getattr(obj, obj_str)
+        try:
+            obj = getattr(obj, obj_str)
+        except AttributeError:
+            raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
 
         # we process the authentication for the object
         realm, user, passwd = process_auth(req, obj, realm, user, passwd)
@@ -373,3 +341,55 @@
          raise apache.SERVER_RETURN, apache.HTTP_FORBIDDEN
 
     return obj
+
+# This regular expression is used to test for the presence of an HTML header
+# tag, written in upper or lower case.
+re_html = re.compile(r"</HTML",re.I)
+re_charset = re.compile(r"charset\s*=\s*([^\s;]+)",re.I);
+
+def publish_object(req, object):
+    if callable(object):
+        req.form = util.FieldStorage(req, keep_blank_values=1)
+        return publish_object(req,util.apply_fs_data(object, req.form, req=req))
+    elif hasattr(object,'__iter__'):
+        result = False
+        for item in object:
+            result |= publish_object(req,item)
+        return result
+    else:
+        if object is None:
+            return False
+        elif isinstance(object,UnicodeType):
+            # We try to detect the character encoding
+            # from the Content-Type header
+            if req._content_type_set:
+                charset = re_charset.search(req.content_type)
+                if charset:
+                    charset = charset.group(1)
+                else:
+                    charset = 'UTF8'
+                    req.content_type += '; charset=UTF8'
+            else:
+                charset = 'UTF8'
+                
+            result = object.encode(charset)
+        else:
+            charset = None
+            result = str(object)
+            
+        if not req._content_type_set:
+            # make an attempt to guess content-type
+            # we look for a </HTML in the last 100 characters.
+            # re.search works OK with a negative start index (it starts from 0
+            # in that case)
+            if re_html.search(result,len(result)-100):
+                req.content_type = 'text/html'
+            else:
+                req.content_type = 'text/plain'
+            if charset is not None:
+                req.content_type += '; charset=UTF8'
+        
+        if req.method!='HEAD':
+            req.write(result)
+
+        return True

Modified: httpd/mod_python/trunk/test/htdocs/tests.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/test/htdocs/tests.py?rev=165474&r1=165473&r2=165474&view=diff
==============================================================================
--- httpd/mod_python/trunk/test/htdocs/tests.py (original)
+++ httpd/mod_python/trunk/test/htdocs/tests.py Sun May  1 02:36:54 2005
@@ -835,6 +835,8 @@
     return "test ok, interpreter=%s" % req.interpreter
 
 class OldStyleClassTest:
+    def __init__(self):
+        pass
     def __call__(self, req):
         return "test callable old-style instance ok"
     def traverse(self, req):
@@ -843,6 +845,15 @@
 
 test_dict = {1:1, 2:2, 3:3}
 test_dict_keys = test_dict.keys
+
+def test_dict_iteration(req):
+    return test_dict_keys()
+    
+def test_generator(req):
+    c = 0
+    while c < 10:
+        yield c
+        c += 1
 
 class InstanceTest(object):
     def __call__(self, req):

Modified: httpd/mod_python/trunk/test/test.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/test/test.py?rev=165474&r1=165473&r2=165474&view=diff
==============================================================================
--- httpd/mod_python/trunk/test/test.py (original)
+++ httpd/mod_python/trunk/test/test.py Sun May  1 02:36:54 2005
@@ -1224,6 +1224,27 @@
         if status != 403:
             self.fail('Vulnerability : built-in type publishing (%i)\n%s' % (status, response))
 
+    def test_publisher_iterator(self):
+        c = VirtualHost("*",
+                        ServerName("test_publisher"),
+                        DocumentRoot(DOCUMENT_ROOT),
+                        Directory(DOCUMENT_ROOT,
+                                  SetHandler("mod_python"),
+                                  PythonHandler("mod_python.publisher"),
+                                  PythonDebug("On")))
+        return str(c)
+
+    def test_publisher_iterator(self):
+        print "\n  * Testing mod_python.publisher iterators"
+
+        rsp = self.vhost_get("test_publisher", path="/tests.py/test_dict_iteration")
+        if (rsp != "123"):
+            self.fail(`rsp`)
+
+        rsp = self.vhost_get("test_publisher", path="/tests.py/test_generator")
+        if (rsp != "0123456789"):
+            self.fail(`rsp`)
+
     def test_publisher_old_style_instance_conf(self):
         c = VirtualHost("*",
                         ServerName("test_publisher"),
@@ -1363,6 +1384,7 @@
         perRequestSuite.addTest(PerRequestTestCase("test_publisher_old_style_instance"))
         perRequestSuite.addTest(PerRequestTestCase("test_publisher_instance"))
         perRequestSuite.addTest(PerRequestTestCase("test_publisher_security"))
+        perRequestSuite.addTest(PerRequestTestCase("test_publisher_iterator"))
         # this must be last so its error_log is not overwritten
         perRequestSuite.addTest(PerRequestTestCase("test_internal"))