How about we make the first call to get or __getitem__ create the dictionary? We could put code in __getattr__ to create it when it's referenced.

Here is the patch to util.py to use a dictionary-on-demand.
Lots of code removed for this one, and a few lines added.

There's also a brand new items() function, which, contrary to common belief, preserves argument ordering.

Note: This also includes my previous StringField patch, since that is crucial to make the implementation as simple as it is now.

Anxiously awaiting your comments and benchmark/profiler results!

['Lies', 'Damn lies', 'Statistics', 'Delivery dates', 'Benchmarks']
--
Mike Looijmans
Philips Natlab / Topic Automation
Index: util.py
===================================================================
--- util.py     (revision 348746)
+++ util.py     (working copy)
@@ -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
-   """
+    """ 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 __init__(self, str=""):
-       str.__init__(self, str)
-       self.value = self.__str__()
+    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,33 +212,44 @@
            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 add_field(self, key, value):
+       """Insert a field as key/value pair"""
+       item = StringField(value)
+       item.name = key
+       self.list.append(item)
 
-   def make_field(self):
-       return cStringIO.StringIO()
-
    def read_to_boundary(self, req, boundary, file):
         previous_delimiter = None
         while True:
@@ -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.

Reply via email to