← Back to team overview

txaws-dev team mailing list archive

[Merge] lp:~radix/txaws/additional-schema-info into lp:txaws

 

You have been requested to review the proposed merge of lp:~radix/txaws/additional-schema-info into lp:txaws.

For more details, see:
https://code.launchpad.net/~radix/txaws/additional-schema-info/+merge/104144

This simple branch adds the following constructor parameters and attributes:

 - 'doc' on Parameter and all subclasses
 - 'name' on Schema
 - 'doc' on Schema
 - 'result' on Schema
 - 'errors' on Schema

In addition, the 'extend' method has been updated to accept all these new parameters. For parameters, result, and errors, it merges them into the existing values instead of replacing them.

-- 
https://code.launchpad.net/~radix/txaws/additional-schema-info/+merge/104144
Your team txAWS Technical List is requested to review the proposed merge of lp:~radix/txaws/additional-schema-info into lp:txaws.
=== modified file 'txaws/server/schema.py'
--- txaws/server/schema.py	2012-04-30 16:36:20 +0000
+++ txaws/server/schema.py	2012-04-30 16:36:20 +0000
@@ -90,7 +90,8 @@
     supports_multiple = False
 
     def __init__(self, name=None, optional=False, default=None,
-                 min=None, max=None, allow_none=False, validator=None):
+                 min=None, max=None, allow_none=False, validator=None,
+                 doc=None):
         self.name = name
         self.optional = optional
         self.default = default
@@ -98,6 +99,7 @@
         self.max = max
         self.allow_none = allow_none
         self.validator = validator
+        self.doc = doc
 
     def coerce(self, value):
         """Coerce a single value according to this parameter's settings.
@@ -205,9 +207,10 @@
     greater_than_max_template = "Value exceeds maximum of %s."
 
     def __init__(self, name=None, optional=False, default=None,
-                 min=0, max=None, allow_none=False, validator=None):
+                 min=0, max=None, allow_none=False, validator=None,
+                 doc=None):
         super(Integer, self).__init__(name, optional, default, min, max,
-                                      allow_none, validator)
+                                      allow_none, validator, doc=doc)
 
     def parse(self, value):
         return int(value)
@@ -250,8 +253,10 @@
 
     kind = "enum"
 
-    def __init__(self, name=None, mapping=None, optional=False, default=None):
-        super(Enum, self).__init__(name, optional=optional, default=default)
+    def __init__(self, name=None, mapping=None, optional=False, default=None,
+                 doc=None):
+        super(Enum, self).__init__(name, optional=optional, default=default,
+                                   doc=doc)
         if mapping is None:
             raise TypeError("Must provide mapping")
         self.mapping = mapping
@@ -303,14 +308,16 @@
     kind = "list"
     supports_multiple = True
 
-    def __init__(self, name=None, item=None, optional=False, default=None):
+    def __init__(self, name=None, item=None, optional=False, default=None,
+                 doc=None):
         """
         @param item: A L{Parameter} instance which will be used to parse and
             format the values in the list.
         """
         if item is None:
             raise TypeError("Must provide item")
-        super(List, self).__init__(name, optional=optional, default=default)
+        super(List, self).__init__(name, optional=optional, default=default,
+                                   doc=doc)
         self.item = item
         if default is None:
             self.default = []
@@ -363,14 +370,15 @@
     kind = "structure"
     supports_multiple = True
 
-    def __init__(self, name=None, fields=None, optional=False, default=None):
+    def __init__(self, name=None, fields=None, optional=False, default=None,
+                 doc=None):
         """
         @param fields: A mapping of field name to field L{Parameter} instance.
         """
         if fields is None:
             raise TypeError("Must provide fields")
         super(Structure, self).__init__(name, optional=optional,
-                                        default=default)
+                                        default=default, doc=doc)
         self.fields = fields
 
     def parse(self, value):
@@ -466,11 +474,10 @@
     def __init__(self, *_parameters, **kwargs):
         """Initialize a new L{Schema} instance.
 
-        Any number of L{Parameter} instances can be passed. The parameter path
-        is used as the target in L{Schema.extract} and L{Schema.bundle}. For
-        example::
+        Any number of L{Parameter} instances can be passed. The parameter names
+        are used in L{Schema.extract} and L{Schema.bundle}. For example::
 
-          schema = Schema(Unicode('Name'))
+          schema = Schema(name="SetName", parameters={"Name": Unicode()})
 
         means that the result of L{Schema.extract} would have a C{Name}
         attribute. Similarly, L{Schema.bundle} would look for a C{Name}
@@ -478,12 +485,34 @@
 
         A more complex example::
 
-          schema = Schema(List('Names', item=Unicode()))
+          schema = Schema(
+              name="SetNames",
+              parameters={"Names": List(item=Unicode())})
 
         means that the result of L{Schema.extract} would have a C{Names}
         attribute, which would itself contain a list of names. Similarly,
         L{Schema.bundle} would look for a C{Names} attribute.
+
+        Currently all parameters other than C{parameters} have no effect; they
+        are merely exposed as attributes of instances of Schema, and are able
+        to be overridden in L{extend}.
+
+        @param name: (keyword) The name of the API call that this schema
+            represents. Accessible via the C{name} attribute.
+        @param parameters: (keyword) The parameters of the API, as a mapping
+            of parameter names to L{Parameter} instances.
+        @param doc: (keyword) The documentation of this API Call. Accessible
+            via the C{doc} attribute.
+        @param result: (keyword) A description of the result of this API call,
+            in the same format as C{parameters}. Accessible via the C{result}
+            attribute.
+        @param errors: (keyword) A list of exception classes that the API can
+            potentially raise. Accessible via the C{result} attribute.
         """
+        self.name = kwargs.pop('name', None)
+        self.doc = kwargs.pop('doc', None)
+        self.result = kwargs.pop('result', None)
+        self.errors = kwargs.pop('errors', [])
         if 'parameters' in kwargs:
             if len(_parameters) > 0:
                 raise TypeError("parameters= must only be passed "
@@ -599,17 +628,31 @@
                 _result[path] = v
         return _result
 
-    def extend(self, *schema_items):
+    def extend(self, *schema_items, **kwargs):
         """
         Add any number of schema items to a new schema.
+
+        Takes the same arguments as the constructor, and returns a new
+        L{Schema} instance.
+
+        If parameters, result, or errors is specified, they will be merged with
+        the existing parameters, result, or errors.
         """
-        parameters = self._parameters.values()
-        for item in schema_items:
-            if isinstance(item, Parameter):
-                parameters.append(item)
-            else:
-                raise TypeError("Illegal argument %s" % item)
-        return Schema(*parameters)
+        new_kwargs = {
+            'name': self.name,
+            'doc': self.doc,
+            'parameters': self._parameters.copy(),
+            'result': self.result.copy() if self.result else {},
+            'errors': self.errors[:] if self.errors else []}
+        new_kwargs['parameters'].update(kwargs.pop('parameters', {}))
+        new_kwargs['result'].update(kwargs.pop('result', {}))
+        new_kwargs['errors'].extend(kwargs.pop('errors', []))
+        new_kwargs.update(kwargs)
+
+        if schema_items:
+            parameters = self._convert_old_schema(schema_items)
+            new_kwargs['parameters'].update(parameters)
+        return Schema(**new_kwargs)
 
     def _convert_old_schema(self, parameters):
         """

=== modified file 'txaws/server/tests/test_schema.py'
--- txaws/server/tests/test_schema.py	2012-04-30 16:36:20 +0000
+++ txaws/server/tests/test_schema.py	2012-04-30 16:36:20 +0000
@@ -189,6 +189,23 @@
         parameter.kind = "test_parameter"
         self.assertEqual("foo", parameter.coerce("foo"))
 
+    def test_parameter_doc(self):
+        """
+        All L{Parameter} subclasses accept a 'doc' keyword argument.
+        """
+        parameters = [
+            Unicode(doc="foo"),
+            RawStr(doc="foo"),
+            Integer(doc="foo"),
+            Bool(doc="foo"),
+            Enum(mapping={"hey": 1}, doc="foo"),
+            Date(doc="foo"),
+            List(item=Integer(), doc="foo"),
+            Structure(fields={}, doc="foo")
+            ]
+        for parameter in parameters:
+            self.assertEqual("foo", parameter.doc)
+
 
 class UnicodeTestCase(TestCase):
 
@@ -802,3 +819,109 @@
         arguments, _ = schema.extract({"foos.0.field": u"existent"})
         self.assertEqual(u"existent", arguments.foos[0].field)
         self.assertEqual(u"hi", arguments.foos[0].field2)
+
+    def test_additional_schema_attributes(self):
+        """
+        Additional data can be specified on the Schema class for specifying a
+        more rich schema.
+        """
+        result = {
+                'id': Integer(),
+                'name': Unicode(),
+                'data': RawStr()}
+        errors = [APIError]
+
+        schema = Schema(
+            name="GetStuff",
+            doc="""Get the stuff.""",
+            parameters={
+                'id': Integer(),
+                'scope': Unicode()},
+            result=result,
+            errors=errors)
+
+        self.assertEqual("GetStuff", schema.name)
+        self.assertEqual("Get the stuff.", schema.doc)
+        self.assertEqual(result, schema.result)
+        self.assertEqual(errors, schema.errors)
+
+    def test_extend_with_additional_schema_attributes(self):
+        """
+        The additional schema attributes can be passed to L{Schema.extend}.
+        """
+        result = {
+                'id': Integer(),
+                'name': Unicode(),
+                'data': RawStr()}
+        errors = [APIError]
+
+        schema = Schema(
+            name="GetStuff",
+            parameters={"id": Integer()})
+
+        schema2 = schema.extend(
+            name="GetStuff2",
+            doc="Get stuff 2",
+            parameters={'scope': Unicode()},
+            result=result,
+            errors=errors)
+
+        self.assertEqual("GetStuff2", schema2.name)
+        self.assertEqual("Get stuff 2", schema2.doc)
+        self.assertEqual(result, schema2.result)
+        self.assertEqual(errors, schema2.errors)
+
+        arguments, _ = schema2.extract({'id': '5', 'scope': u'foo'})
+        self.assertEqual(5, arguments.id)
+        self.assertEqual(u'foo', arguments.scope)
+
+    def test_extend_maintains_existing_attributes(self):
+        """
+        If additional schema attributes aren't passed to L{Schema.extend}, they
+        stay the same.
+        """
+        result = {
+                'id': Integer(),
+                'name': Unicode(),
+                'data': RawStr()}
+        errors = [APIError]
+
+        schema = Schema(
+            name="GetStuff",
+            doc="""Get the stuff.""",
+            parameters={'id': Integer()},
+            result=result,
+            errors=errors)
+
+        schema2 = schema.extend(parameters={'scope': Unicode()})
+
+        self.assertEqual("GetStuff", schema2.name)
+        self.assertEqual("Get the stuff.", schema2.doc)
+        self.assertEqual(result, schema2.result)
+        self.assertEqual(errors, schema2.errors)
+
+        arguments, _ = schema2.extract({'id': '5', 'scope': u'foo'})
+        self.assertEqual(5, arguments.id)
+        self.assertEqual(u'foo', arguments.scope)
+
+    def test_extend_result(self):
+        """
+        Result fields can also be extended with L{Schema.extend}.
+        """
+        schema = Schema(
+            result={'name': Unicode()}
+            )
+        schema2 = schema.extend(
+            result={'id': Integer()})
+        result_structure = Structure(fields=schema2.result)
+        self.assertEqual(
+            {'name': u'foo', 'id': 5},
+            result_structure.coerce({'name': u'foo', 'id': '5'}))
+
+    def test_extend_errors(self):
+        """
+        Errors can be extended with L{Schema.extend}.
+        """
+        schema = Schema(parameters={}, errors=[APIError])
+        schema2 = schema.extend(errors=[ZeroDivisionError])
+        self.assertEqual([APIError, ZeroDivisionError], schema2.errors)


References