Christopher Armstrong has proposed merging lp:~radix/txaws/additional-schema-info into lp:txaws with lp:~radix/txaws/parameter-enrichment as a prerequisite.
Requested reviews: txAWS Technical List (txaws-tech) txAWS Technical List (txaws-tech) Related bugs: Bug #984660 in txAWS: "Enrich schema declarations to allow for describing all the details of an API" https://bugs.launchpad.net/txaws/+bug/984660 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)
_______________________________________________ Mailing list: https://launchpad.net/~txaws-dev Post to : txaws-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~txaws-dev More help : https://help.launchpad.net/ListHelp