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 gr...@apache.org on 2006/04/13 12:50:47 UTC

svn commit: r393781 - in /httpd/mod_python/branches/3.2.x: Doc/appendixc.tex Doc/modpython4.tex lib/python/mod_python/__init__.py lib/python/mod_python/util.py src/include/mpversion.h

Author: grahamd
Date: Thu Apr 13 03:50:36 2006
New Revision: 393781

URL: http://svn.apache.org/viewcvs?rev=393781&view=rev
Log:
Backported MODPYTHON-93 from trunk to branches/3.2.x.

Modified:
    httpd/mod_python/branches/3.2.x/Doc/appendixc.tex
    httpd/mod_python/branches/3.2.x/Doc/modpython4.tex
    httpd/mod_python/branches/3.2.x/lib/python/mod_python/__init__.py
    httpd/mod_python/branches/3.2.x/lib/python/mod_python/util.py
    httpd/mod_python/branches/3.2.x/src/include/mpversion.h

Modified: httpd/mod_python/branches/3.2.x/Doc/appendixc.tex
URL: http://svn.apache.org/viewcvs/httpd/mod_python/branches/3.2.x/Doc/appendixc.tex?rev=393781&r1=393780&r2=393781&view=diff
==============================================================================
--- httpd/mod_python/branches/3.2.x/Doc/appendixc.tex (original)
+++ httpd/mod_python/branches/3.2.x/Doc/appendixc.tex Thu Apr 13 03:50:36 2006
@@ -44,6 +44,9 @@
       (\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-119]{MODPYTHON-119})
       DbmSession unit test no longer uses the default directory for the dbm file,
       so the test will not interfer with the user's current apache instance.
+    \item
+      (\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-93]{MODPYTHON-93})
+      Improved util.FieldStorage efficiency.
   \end{itemize}
 
   Bug Fixes

Modified: httpd/mod_python/branches/3.2.x/Doc/modpython4.tex
URL: http://svn.apache.org/viewcvs/httpd/mod_python/branches/3.2.x/Doc/modpython4.tex?rev=393781&r1=393780&r2=393781&view=diff
==============================================================================
--- httpd/mod_python/branches/3.2.x/Doc/modpython4.tex (original)
+++ httpd/mod_python/branches/3.2.x/Doc/modpython4.tex Thu Apr 13 03:50:36 2006
@@ -1522,7 +1522,7 @@
 class. This class is similar to the standard library module
 \module{cgi} \class{FieldStorage}.
 
-\begin{classdesc}{FieldStorage}{req\optional{, keep_blank_values, strict_parsing}}
+\begin{classdesc}{FieldStorage}{req\optional{, keep_blank_values, strict_parsing, file_callback, field_callback}}
   This class provides uniform access to HTML form data submitted by the
   client.  \var{req} is an instance of the mod_python request object.
 
@@ -1533,6 +1533,14 @@
 
   The optional argument \var{strict_parsing} is not yet implemented.
 
+  The optional argument \var{file_callback} allows the application to override
+  both file creation/deletion semantics and location. See
+  \ref{pyapi-util-fstor-examples} ``FieldStorage Examples'' for additional
+  information. \emph{New in version 3.2}
+
+  The optional argument \var{field_callback} allows the application to override
+  both the creation/deletion semantics and behaviour. \emph{New in version 3.2}
+
   During initialization, \class{FieldStorage} class reads all of the
   data provided by the client. Since all data provided by the client is
   consumed at this point, there should be no more than one
@@ -1599,6 +1607,90 @@
   \end{methoddesc}
 
 \end{classdesc}
+
+\subsection{FieldStorage Examples\label{pyapi-util-fstor-examples}}
+
+  The following examples demonstrate how to use the \var{file_callback} 
+  parameter of the \class{FieldStorage} constructor to control file
+  object creation. The \class{Storage} classes created in both 
+  examples derive from FileType, thereby providing extended file
+  functionality.
+
+  These examples are provided for demonstration purposes only. The 
+  issue of temporary file location and security must be considered
+  when providing such overrides with mod_python in production use.
+
+\begin{description}
+  \item[Simple file control using class constructor]
+
+    This example uses the \class{FieldStorage} class constructor to
+    create the file object, allowing simple control. It is not 
+    advisable to add class variables to this if serving multiple sites
+    from apache. In that case use the factory method instead.
+
+\begin{verbatim}
+class Storage(file):
+
+    def __init__(self, advisory_filename):
+        self.advisory_filename = advisory_filename
+        self.delete_on_close = True
+        self.already_deleted = False
+        self.real_filename = '/someTempDir/thingy-unique-thingy'
+        super(Storage, self).__init__(self.real_filename, 'w+b')
+
+    def close(self):
+        if self.already_deleted:
+            return
+        super(Storage, self).close()
+        if self.delete_on_close:
+            self.already_deleted = True
+            os.remove(self.real_filename)
+
+request_data = util.FieldStorage(request, keep_blank_values=True, file_callback=Storage)
+\end{verbatim}
+
+
+  \item[Advanced file control using object factory]
+
+    Using a object factory can provide greater control over the
+    constructor parameters.
+
+\begin{verbatim}
+import os
+
+class Storage(file):
+
+    def __init__(self, directory, advisory_filename):
+        self.advisory_filename = advisory_filename
+        self.delete_on_close = True
+        self.already_deleted = False
+        self.real_filename = directory + '/thingy-unique-thingy'
+        super(Storage, self).__init__(self.real_filename, 'w+b')
+
+    def close(self):
+        if self.already_deleted:
+            return
+        super(Storage, self).close()
+        if self.delete_on_close:
+            self.already_deleted = True
+            os.remove(self.real_filename)
+
+class StorageFactory:
+
+    def __init__(self, directory):
+        self.dir = directory
+
+    def create(self, advisory_filename):
+        return Storage(self.dir, advisory_filename)
+
+file_factory = StorageFactory(someDirectory)
+[...sometime later...]
+request_data = util.FieldStorage(request, keep_blank_values=True,
+                                file_callback=file_factory.create)
+\end{verbatim}
+
+\end{description}
+
 
 \subsection{Field class\label{pyapi-util-fstor-fld}}
 

Modified: httpd/mod_python/branches/3.2.x/lib/python/mod_python/__init__.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/branches/3.2.x/lib/python/mod_python/__init__.py?rev=393781&r1=393780&r2=393781&view=diff
==============================================================================
--- httpd/mod_python/branches/3.2.x/lib/python/mod_python/__init__.py (original)
+++ httpd/mod_python/branches/3.2.x/lib/python/mod_python/__init__.py Thu Apr 13 03:50:36 2006
@@ -20,5 +20,5 @@
 __all__ = ["apache", "cgihandler", "psp",
            "publisher", "util", "python22"]
 
-version = "3.2.9-unreleased-20060412"
+version = "3.2.9-unreleased-20060413"
 

Modified: httpd/mod_python/branches/3.2.x/lib/python/mod_python/util.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/branches/3.2.x/lib/python/mod_python/util.py?rev=393781&r1=393780&r2=393781&view=diff
==============================================================================
--- httpd/mod_python/branches/3.2.x/lib/python/mod_python/util.py (original)
+++ httpd/mod_python/branches/3.2.x/lib/python/mod_python/util.py Thu Apr 13 03:50:36 2006
@@ -48,19 +48,8 @@
 """
 
 class Field:
-   def __init__(self, name, file, ctype, type_options,
-                disp, disp_options, headers = {}):
+   def __init__(self, name):
        self.name = name
-       self.file = file
-       self.type = ctype
-       self.type_options = type_options
-       self.disposition = disp
-       self.disposition_options = disp_options
-       if disp_options.has_key("filename"):
-           self.filename = disp_options["filename"]
-       else:
-           self.filename = None
-       self.headers = headers
 
    def __repr__(self):
        """Return printable representation."""
@@ -81,13 +70,34 @@
        self.file.close()
 
 class StringField(str):
-   """ This class is basically a string with
-   a value attribute for compatibility with std lib cgi.py
-   """
-
-   def __init__(self, str=""):
-       str.__init__(self, str)
-       self.value = self.__str__()
+    """ This class is basically a string with
+    added attributes for compatibility with std lib cgi.py. Basically, this
+    works the opposite of Field, as it stores its data in a string, but creates
+    a file on demand. Field creates a value on demand and stores data in a file.
+    """
+    filename = None
+    headers = {}
+    ctype = "text/plain"
+    type_options = {}
+    disposition = None
+    disp_options = None
+    
+    # I wanted __init__(name, value) but that does not work (apparently, you
+    # cannot subclass str with a constructor that takes >1 argument)
+    def __init__(self, value):
+        '''Create StringField instance. You'll have to set name yourself.'''
+        str.__init__(self, value)
+        self.value = value
+
+    def __getattr__(self, name):
+        if name != 'file':
+            raise AttributeError, name
+        self.file = cStringIO.StringIO(self.value)
+        return self.file
+        
+    def __repr__(self):
+       """Return printable representation (to pass unit tests)."""
+       return "Field(%s, %s)" % (`self.name`, `self.value`)
 
 class FieldStorage:
 
@@ -103,8 +113,7 @@
        if req.args:
            pairs = parse_qsl(req.args, keep_blank_values)
            for pair in pairs:
-               file = cStringIO.StringIO(pair[1])
-               self.list.append(Field(pair[0], file, "text/plain", {}, None, {}))
+               self.add_field(pair[0], pair[1])
 
        if req.method != "POST":
            return
@@ -123,9 +132,7 @@
        if ctype.startswith("application/x-www-form-urlencoded"):
            pairs = parse_qsl(req.read(clen), keep_blank_values)
            for pair in pairs:
-               # TODO : isn't this a bit heavyweight just for form fields ?
-               file = cStringIO.StringIO(pair[1])
-               self.list.append(Field(pair[0], file, "text/plain", {}, None, {}))
+               self.add_field(pair[0], pair[1])
            return
 
        if not ctype.startswith("multipart/"):
@@ -205,32 +212,43 @@
            else:
                name = None
 
+           # create a file object
            # is this a file?
            if disp_options.has_key("filename"):
                if file_callback and callable(file_callback):
                    file = file_callback(disp_options["filename"])
                else:
-                   file = self.make_file()
+                   file = tempfile.TemporaryFile("w+b")
            else:
                if field_callback and callable(field_callback):
                    file = field_callback()
                else:
-                   file = self.make_field()
+                   file = cStringIO.StringIO()
 
            # read it in
-           end_of_stream = self.read_to_boundary(req, boundary, file)
+           self.read_to_boundary(req, boundary, file)
            file.seek(0)
-
+ 
            # make a Field
-           field = Field(name, file, ctype, type_options, disp, disp_options, headers)
-
+           if disp_options.has_key("filename"):
+               field = Field(name)
+               field.filename = disp_options["filename"]
+           else:
+               field = StringField(file.read())
+               field.name = name
+           field.file = file
+           field.type = ctype
+           field.type_options = type_options
+           field.disposition = disp
+           field.disposition_options = disp_options
+           field.headers = headers
            self.list.append(field)
 
-   def make_file(self):
-       return tempfile.TemporaryFile("w+b")
-
-   def make_field(self):
-       return cStringIO.StringIO()
+   def add_field(self, key, value):
+       """Insert a field as key/value pair"""
+       item = StringField(value)
+       item.name = key
+       self.list.append(item)
 
    def read_to_boundary(self, req, boundary, file):
         previous_delimiter = None
@@ -283,18 +301,7 @@
 
    def __getitem__(self, key):
        """Dictionary style indexing."""
-       if self.list is None:
-           raise TypeError, "not indexable"
-       found = []
-       for item in self.list:
-           if item.name == key:
-               if isinstance(item.file, FileType) or \
-                      isinstance(getattr(item.file, 'file', None), FileType):
-                   found.append(item)
-               else:
-                   found.append(StringField(item.value))
-       if not found:
-           raise KeyError, key
+       found = self.dictionary[key]
        if len(found) == 1:
            return found[0]
        else:
@@ -303,56 +310,56 @@
    def get(self, key, default):
        try:
            return self.__getitem__(key)
-       except KeyError:
+       except (TypeError, KeyError):
            return default
 
    def keys(self):
        """Dictionary style keys() method."""
-       if self.list is None:
-           raise TypeError, "not indexable"
-       keys = []
-       for item in self.list:
-           if item.name not in keys: keys.append(item.name)
-       return keys
+       return self.dictionary.keys()
 
    def has_key(self, key):
        """Dictionary style has_key() method."""
-       if self.list is None:
-           raise TypeError, "not indexable"
-       for item in self.list:
-           if item.name == key: return 1
-       return 0
+       return (key in self.dictionary)
 
    __contains__ = has_key
 
    def __len__(self):
        """Dictionary style len(x) support."""
-       return len(self.keys())
+       return len(self.dictionary.keys())
 
    def getfirst(self, key, default=None):
        """ return the first value received """
-       for item in self.list:
-           if item.name == key:
-               if isinstance(item.file, FileType) or \
-                      isinstance(getattr(item.file, 'file', None), FileType):
-                   return item
-               else:
-                   return StringField(item.value)
-       return default
+       try:
+           return self.dictionary[key][0]
+       except KeyError:
+           return default
 
    def getlist(self, key):
        """ return a list of received values """
-       if self.list is None:
+       try:
+           return self.dictionary[key]
+       except KeyError:
+           return []
+           
+   def items(self):
+       """Dictionary-style items(), except that items are returned in the same
+       order as they were supplied in the form."""
+       return [(item.name, item) for item in self.list]
+       
+   def __getattr__(self, name):
+       if name != 'dictionary':
+          raise AttributeError, name
+       list = self.list
+       if list is None:
            raise TypeError, "not indexable"
-       found = []
-       for item in self.list:
-           if item.name == key:
-               if isinstance(item.file, FileType) or \
-                      isinstance(getattr(item.file, 'file', None), FileType):
-                   found.append(item)
-               else:
-                   found.append(StringField(item.value))
-       return found
+       result = {}
+       self.dictionary = result
+       for item in list:
+           if item.name in result:
+              result[item.name].append(item)
+           else:
+              result[item.name] = [item]
+       return result
 
 def parse_header(line):
    """Parse a Content-type like header.

Modified: httpd/mod_python/branches/3.2.x/src/include/mpversion.h
URL: http://svn.apache.org/viewcvs/httpd/mod_python/branches/3.2.x/src/include/mpversion.h?rev=393781&r1=393780&r2=393781&view=diff
==============================================================================
--- httpd/mod_python/branches/3.2.x/src/include/mpversion.h (original)
+++ httpd/mod_python/branches/3.2.x/src/include/mpversion.h Thu Apr 13 03:50:36 2006
@@ -1,5 +1,5 @@
 #define MPV_MAJOR 3
 #define MPV_MINOR 2
 #define MPV_PATCH 9
-#define MPV_BUILD 20060412
-#define MPV_STRING "3.2.9-unreleased-20060412"
+#define MPV_BUILD 20060413
+#define MPV_STRING "3.2.9-unreleased-20060413"