Merged in dhellmann/wsme-sphinx (pull request #5)

This commit is contained in:
Christophe de Vienne 2012-12-11 09:25:50 +01:00
commit f9b76d3a5d
5 changed files with 123 additions and 36 deletions

View File

@ -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)')

View File

@ -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(

View File

@ -27,6 +27,11 @@ field_re = re.compile(r':(?P<field>\w+)(\s+(?P<name>\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__
@ -157,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):
@ -194,34 +220,51 @@ class TypeDocumenter(autodoc.ClassDocumenter):
return False
def add_content(self, more_content, no_docstring=False):
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'<wsme.sphinxext')
# Check where to include the samples
samples_slot = self.options.samples_slot or self.default_samples_slot
self.add_line(u'', '<wsme.sphinxext>')
super(TypeDocumenter, self).add_content(
more_content, no_docstring)
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'<wsme.sphinxext')
self.add_line(u'', '<wsme.sphinxext>')
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):
@ -341,13 +384,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):
@ -360,6 +404,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(
@ -390,14 +438,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):
@ -406,8 +453,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

View File

@ -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 == {'': ''}

View File

@ -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):