From 4fb26f6f2d2a19cd43ae12132222b7c8d1ac01d6 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 5 Dec 2012 13:06:25 -0500 Subject: [PATCH 1/8] use tag name 'result' for sample data to match data returned by services --HG-- extra : source : d6ac5de7ab98151eae6e99462c7d15141515ecf0 --- wsme/rest/xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsme/rest/xml.py b/wsme/rest/xml.py index 7e05278..486b02f 100644 --- a/wsme/rest/xml.py +++ b/wsme/rest/xml.py @@ -255,7 +255,7 @@ def encode_error(context, errordetail): def encode_sample_value(datatype, value, format=False): - r = toxml(datatype, 'value', value) + r = toxml(datatype, 'result', value) if format: xml_indent(r) content = et.tostring(r) From 322dca63c5df3e6b8771069ff12d32cb8eb15b23 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 5 Dec 2012 13:06:58 -0500 Subject: [PATCH 2/8] show the docstring for a type before the formatted sample values --HG-- extra : source : e5fa8d5e8dc27b91fca92e8b3a109981f2ed37b0 --- wsme/sphinxext.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wsme/sphinxext.py b/wsme/sphinxext.py index 5a6d02f..5a97fb4 100644 --- a/wsme/sphinxext.py +++ b/wsme/sphinxext.py @@ -194,6 +194,8 @@ class TypeDocumenter(autodoc.ClassDocumenter): return False def add_content(self, more_content, no_docstring=False): + super(TypeDocumenter, self).add_content( + more_content, no_docstring) protocols = get_protocols( self.options.protocols or self.env.app.config.wsme_protocols ) @@ -220,8 +222,6 @@ class TypeDocumenter(autodoc.ClassDocumenter): self.add_line(line, u'') - super(TypeDocumenter, self).add_content( - more_content, no_docstring) class AttributeDocumenter(autodoc.AttributeDocumenter): From 6af9b928786a990b2161f54f89ca226a7c1028dc Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 5 Dec 2012 19:22:14 -0500 Subject: [PATCH 3/8] Update datetypename() to work with DictType and ArrayType instances. --HG-- extra : source : cef0a57e57255c4437922d3c2b0a81c84ad29b07 --- tests/test_sphinxext.py | 19 +++++++++++++++++++ wsme/sphinxext.py | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/tests/test_sphinxext.py b/tests/test_sphinxext.py index a86e11f..62d0ee1 100644 --- a/tests/test_sphinxext.py +++ b/tests/test_sphinxext.py @@ -3,6 +3,7 @@ import sphinx import os.path import wsme.types +from wsme import sphinxext docpath = os.path.join( os.path.dirname(__file__), @@ -24,3 +25,21 @@ class TestSphinxExt(unittest.TestCase): '-d', '.test_sphinxext/doctree', docpath, '.test_sphinxext/html']) == 0 + + +class TestDataTypeName(unittest.TestCase): + def test_user_type(self): + self.assertEqual(sphinxext.datatypename(ASampleType), + 'ASampleType') + + def test_dict_type(self): + d = wsme.types.DictType(str, str) + self.assertEqual(sphinxext.datatypename(d), 'dict(str: str)') + d = wsme.types.DictType(str, ASampleType) + self.assertEqual(sphinxext.datatypename(d), 'dict(str: ASampleType)') + + def test_array_type(self): + d = wsme.types.ArrayType(str) + self.assertEqual(sphinxext.datatypename(d), 'list(str)') + d = wsme.types.ArrayType(ASampleType) + self.assertEqual(sphinxext.datatypename(d), 'list(ASampleType)') diff --git a/wsme/sphinxext.py b/wsme/sphinxext.py index 5a97fb4..3f845d3 100644 --- a/wsme/sphinxext.py +++ b/wsme/sphinxext.py @@ -27,6 +27,11 @@ field_re = re.compile(r':(?P\w+)(\s+(?P\w+))?:') def datatypename(datatype): if isinstance(datatype, wsme.types.UserType): return datatype.name + if isinstance(datatype, wsme.types.DictType): + return 'dict(%s: %s)' % (datatypename(datatype.key_type), + datatypename(datatype.value_type)) + if isinstance(datatype, wsme.types.ArrayType): + return 'list(%s)' % datatypename(datatype.item_type) return datatype.__name__ From bd36dc2f35df2f5625ca5086f23628480adf682a Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 7 Dec 2012 15:48:49 -0500 Subject: [PATCH 4/8] Copy properties to decorator Use functools.wraps() to copy the docstring and name from the decorated function to the closure so the autodoc code gets the right values. --- wsme/pecan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wsme/pecan.py b/wsme/pecan.py index 7a478a8..e9ba454 100644 --- a/wsme/pecan.py +++ b/wsme/pecan.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import functools import inspect import sys @@ -57,6 +58,7 @@ def wsexpose(*args, **kwargs): funcdef = wsme.api.FunctionDefinition.get(f) funcdef.resolve_types(wsme.types.registry) + @functools.wraps(f) def callfunction(self, *args, **kwargs): try: args, kwargs = wsme.rest.args.get_args( From 23a276058d8c67d175f227d8d966b2411b2d91f1 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 7 Dec 2012 16:09:14 -0500 Subject: [PATCH 5/8] Roll back previous change to the root XML tag name for sample data. --- wsme/rest/xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsme/rest/xml.py b/wsme/rest/xml.py index 486b02f..7e05278 100644 --- a/wsme/rest/xml.py +++ b/wsme/rest/xml.py @@ -255,7 +255,7 @@ def encode_error(context, errordetail): def encode_sample_value(datatype, value, format=False): - r = toxml(datatype, 'result', value) + r = toxml(datatype, 'value', value) if format: xml_indent(r) content = et.tostring(r) From e1f82b01ceac8533f52866ae6cb464a7ae5eb033 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 7 Dec 2012 16:12:48 -0500 Subject: [PATCH 6/8] Add sample() method to ArrayType and DictType Update ArrayType and DictType so they have sample() methods for the sphinxext code to use when documenting nested types. --- wsme/tests/test_types.py | 12 ++++++++++++ wsme/types.py | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/wsme/tests/test_types.py b/wsme/tests/test_types.py index cbfb45d..0784aa5 100644 --- a/wsme/tests/test_types.py +++ b/wsme/tests/test_types.py @@ -355,3 +355,15 @@ class TestTypes(unittest.TestCase): def test_array_eq(self): l = [types.ArrayType(str)] assert types.ArrayType(str) in l + + def test_array_sample(self): + s = types.ArrayType(str).sample() + assert isinstance(s, list) + assert s + assert s[0] == '' + + def test_dict_sample(self): + s = types.DictType(str, str).sample() + assert isinstance(s, dict) + assert s + assert s == {'': ''} diff --git a/wsme/types.py b/wsme/types.py index c88acf9..8758569 100644 --- a/wsme/types.py +++ b/wsme/types.py @@ -34,6 +34,9 @@ class ArrayType(object): return isinstance(other, ArrayType) \ and self.item_type == other.item_type + def sample(self): + return [getattr(self.item_type, 'sample', self.item_type)()] + @property def item_type(self): if isinstance(self._item_type, weakref.ref): @@ -67,6 +70,11 @@ class DictType(object): def __hash__(self): return hash((self.key_type, self.value_type)) + def sample(self): + key = getattr(self.key_type, 'sample', self.key_type)() + value = getattr(self.value_type, 'sample', self.value_type)() + return {key: value} + @property def value_type(self): if isinstance(self._value_type, weakref.ref): From c5c7f6df37baa48d38bacbc70cd5b0aedd96a1f4 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 7 Dec 2012 16:15:49 -0500 Subject: [PATCH 7/8] Fixes for sphinxext Restore check that restricts the use of FunctionDocumenter to nodes under a ServiceDocumenter to prevent it from being used for classes not related to the web service. Replace direct uses of __name__ with datatypename(). Fix a problem that showed up when get_doc() does not find any docstrings for a function. Use for:else instead of checking a sentinal variable after a search loop. --- wsme/sphinxext.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/wsme/sphinxext.py b/wsme/sphinxext.py index 3f845d3..bd51d27 100644 --- a/wsme/sphinxext.py +++ b/wsme/sphinxext.py @@ -346,13 +346,14 @@ class FunctionDocumenter(autodoc.MethodDocumenter): @classmethod def can_document_member(cls, member, membername, isattr, parent): - return wsme.api.iswsmefunction(member) + return (isinstance(parent, ServiceDocumenter) + and wsme.api.iswsmefunction(member)) def import_object(self): ret = super(FunctionDocumenter, self).import_object() self.directivetype = 'function' self.wsme_fd = wsme.api.FunctionDefinition.get(self.object) - self.retann = self.wsme_fd.return_type.__name__ + self.retann = datatypename(self.wsme_fd.return_type) return ret def format_args(self): @@ -365,6 +366,10 @@ class FunctionDocumenter(autodoc.MethodDocumenter): """Inject the type and param fields into the docstrings so that the user can add its own param fields to document the parameters""" docstrings = super(FunctionDocumenter, self).get_doc(encoding) + # If the function doesn't have a docstring, add an empty list + # so the default behaviors below work correctly. + if not docstrings: + docstrings.append([]) found_params = set() protocols = get_protocols( @@ -395,14 +400,13 @@ class FunctionDocumenter(autodoc.MethodDocumenter): and m.group('name') == arg.name: pos = (si, i + 1) break - break docstring = docstrings[pos[0]] docstring[pos[1]:pos[1]] = content next_param_pos = (pos[0], pos[1] + len(content)) if self.wsme_fd.return_type: content = [ - u':rtype: %s' % self.wsme_fd.return_type.__name__ + u':rtype: %s' % datatypename(self.wsme_fd.return_type) ] pos = None for si, docstring in enumerate(docstrings): @@ -411,8 +415,7 @@ class FunctionDocumenter(autodoc.MethodDocumenter): if m and m.group('field') == 'return': pos = (si, i + 1) break - break - if pos is None: + else: pos = next_param_pos docstring = docstrings[pos[0]] docstring[pos[1]:pos[1]] = content From c1440fff51a5200054638a3624231b6ea0b2c429 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 7 Dec 2012 17:23:29 -0500 Subject: [PATCH 8/8] Add samples_slot option to TypeDocumenter Allow the output of the TypeDocumenter to come in different orders, depending on the sample-slot option. Default to showing samples after the rendered docstring, but also allow it to come before the docstring by setting the option to 'before-docstring'. Use 'none' to disable the sample output for a type entirely. --- wsme/sphinxext.py | 98 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/wsme/sphinxext.py b/wsme/sphinxext.py index bd51d27..898b333 100644 --- a/wsme/sphinxext.py +++ b/wsme/sphinxext.py @@ -162,16 +162,37 @@ class AttributeDirective(PyClassmember): ] +def check_samples_slot(value): + """Validate the samples_slot option to the TypeDocumenter. + + Valid positions are 'before-docstring' and + 'after-docstring'. Using the explicit 'none' disables sample + output. The default is after-docstring. + """ + if not value: + return 'after-docstring' + val = directives.choice( + value, + ('none', # do not include + 'before-docstring', # show samples then docstring + 'after-docstring', # show docstring then samples + )) + return val + + class TypeDocumenter(autodoc.ClassDocumenter): objtype = 'type' directivetype = 'type' domain = 'wsme' required_arguments = 1 + default_samples_slot = 'after-docstring' - option_spec = dict(autodoc.ClassDocumenter.option_spec, **{ - 'protocols': lambda l: [v.strip() for v in l.split(',')] - }) + option_spec = dict( + autodoc.ClassDocumenter.option_spec, + **{'protocols': lambda l: [v.strip() for v in l.split(',')], + 'samples-slot': check_samples_slot, + }) @classmethod def can_document_member(cls, member, membername, isattr, parent): @@ -199,34 +220,51 @@ class TypeDocumenter(autodoc.ClassDocumenter): return False def add_content(self, more_content, no_docstring=False): - super(TypeDocumenter, self).add_content( - more_content, no_docstring) - protocols = get_protocols( - self.options.protocols or self.env.app.config.wsme_protocols - ) - content = [] - if protocols: - sample_obj = make_sample_object(self.object) - content.extend([ - l_(u'Data samples:'), - u'', - u'.. cssclass:: toggle', - u'' - ]) - for name, protocol in protocols: - language, sample = protocol.encode_sample_value( - self.object, sample_obj, format=True) - content.extend([ - name, - u' .. code-block:: ' + language, - u'', - ]) - content.extend(( - u' ' * 8 + line for line in sample.split('\n'))) - for line in content: - self.add_line(line, u'') + print 'SAMPLES SLOT:', self.options.samples_slot + + def add_docstring(): + super(TypeDocumenter, self).add_content( + more_content, no_docstring) + + def add_samples(): + protocols = get_protocols( + self.options.protocols or self.env.app.config.wsme_protocols + ) + content = [] + if protocols: + sample_obj = make_sample_object(self.object) + content.extend([ + l_(u'Data samples:'), + u'', + u'.. cssclass:: toggle', + u'' + ]) + for name, protocol in protocols: + language, sample = protocol.encode_sample_value( + self.object, sample_obj, format=True) + content.extend([ + name, + u' .. code-block:: ' + language, + u'', + ]) + content.extend(( + u' ' * 8 + line for line in sample.split('\n'))) + for line in content: + self.add_line(line, u'') + + if samples_slot == 'after-docstring': + add_docstring() + add_samples() + elif samples_slot == 'before-docstring': + add_samples() + add_docstring() + else: + add_docstring() class AttributeDocumenter(autodoc.AttributeDocumenter):