support empty defaults for labels, and add a URLListType for use with query strings
authorTero Marttila <terom@fixme.fi>
Thu, 12 Feb 2009 01:38:29 +0200
changeset 58 a4261592020a
parent 57 2ed89377f339
child 59 3b9a95c333e5
support empty defaults for labels, and add a URLListType for use with query strings
urltree.py
--- a/urltree.py	Thu Feb 12 01:37:02 2009 +0200
+++ b/urltree.py	Thu Feb 12 01:38:29 2009 +0200
@@ -252,7 +252,7 @@
         A label that has a name and a simple string value
     """
 
-    EXPR = re.compile(r'^\{(?P<key>[a-zA-Z_][a-zA-Z0-9_]*)(:(?P<type>[a-zA-Z_][a-zA-Z0-9_]*))?(=(?P<default>[^}]+))?\}$')
+    EXPR = re.compile(r'^\{(?P<key>[a-zA-Z_][a-zA-Z0-9_]*)(:(?P<type>[a-zA-Z_][a-zA-Z0-9_]*))?(=(?P<default>[^}]*))?\}$')
 
     def __init__ (self, key, type_name, type, default) :
         """
@@ -324,6 +324,14 @@
 
         abstract
     
+    def append (self, old_value, value) :
+        """
+            Handle multiple values for this type, by combining the given old value and new value (both from parse).
+
+            Defaults to raise an error
+        """
+
+        raise URLError("Multiple values for argument")
    
     def build (self, obj) :
         """
@@ -332,11 +340,18 @@
 
         abstract
 
+    def build_multi (self, obj) :
+        """
+            Return a list of string values for the given object value (as from parse/append).
+
+            Defaults to return [self.build(obj)]
+        """
+
+        return [self.build(obj)]
+
 class URLStringType (URLType) :
     """
         The default URLType, just plain strings.
-
-        Note that this does not accept empty strings as valid
     """
 
     def parse (self, value) :
@@ -347,9 +362,6 @@
         return value
 
     def build (self, obj) :
-        if not obj :
-            raise ValueError("String must not be empty")
-
         return str(obj)
 
 class URLIntegerType (URLType) :
@@ -400,6 +412,20 @@
 
         return unicode(self._validate(obj))
     
+class URLListType (URLType) :
+    """
+        A list of strings
+    """
+
+    def parse (self, value) :
+        return [value]
+    
+    def append (self, old_value, value) :
+        return old_value + value
+
+    def build_multi (self, obj) :
+        return obj
+
 class URLConfig (object) :
     """
         Global configuration relevant to all URLs. This can be used to construct a set of URLs and then create an
@@ -419,6 +445,9 @@
 
         # integer
         'int'   : URLIntegerType(),
+
+        # list of strs
+        'list'  : URLListType(),
     }
 
     def __init__ (self, type_dict=None, ignore_extra_args=True) :
@@ -599,8 +628,14 @@
                 # otherwise, fail
                 raise URLError("No value given for required argument: %r" % (key, ))
             
-            # set key
-            kwargs[key] = value
+            # already have a value?
+            if key in kwargs :
+                # append to old value
+                kwargs[key] = type.append(kwargs[key], value)
+
+            else :
+                # set key
+                kwargs[key] = value
         
         # then check all query args
         for key, (type, default) in self.query_args.iteritems() :
@@ -647,10 +682,10 @@
         # join
         url = '/'.join(segment for is_default, segment in segments if segment is not None)
         
-        # build query args
-        query_args = dict((key, type.build(values[key])) for key, (type, default) in self.query_args.iteritems() if key in values and values[key] is not None)
+        # build query args as { key -> [value] }
+        query_args = dict((key, type.build_multi(values[key])) for key, (type, default) in self.query_args.iteritems() if key in values and values[key] is not None)
 
-        return "%s%s" % (url, '?%s' % ('&'.join('%s=%s' % tup for tup in query_args.iteritems())) if query_args else '')
+        return "%s%s" % (url, '?%s' % ('&'.join('%s=%s' % (key, value) for value in values for key, values in query_args.iteritems())) if query_args else '')
 
     def __str__ (self) :
         return '/'.join(str(label) for label in self.label_path)