10000 Merge branch 'enh-numpydoc' · numpy/numpy@629a2d4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 629a2d4

Browse files
committed
Merge branch 'enh-numpydoc'
There were some conflicts with the 2to3 work in numpy. I think I got the fixes right. * enh-numpydoc: DOC: fix doc/source/conf.py to work with Python 3 BUG: numpydoc: check that it works with sub-classes TST: numpydoc: more class tests BUG: numpydoc: fix bugs in attribute docstring extraction + improve presentation TST: numpydoc: add stub test files, to check that files at least import MAINT: always use plot directive from Matplotlib, and prefer Sphinx linkcode ENH: numpydoc: Python 2 & 3 in single codebase, restructure as a package ENH: numpydoc: deal with duplicated signatures DOC: numpydoc/linkcode: mention that the extension will be in Sphinx upstream BUG: numpydoc/linkcode: do not detect linkcode config changes Conflicts: doc/sphinxext/numpydoc/docscrape.py doc/sphinxext/numpydoc/docscrape_sphinx.py doc/sphinxext/numpydoc/linkcode.py doc/sphinxext/numpydoc/phantom_import.py doc/sphinxext/numpydoc/traitsdoc.py
2 parents 4b361f6 + 9d8722b commit 629a2d4

19 files changed

+279
-92
lines changed

doc/source/conf.py

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,8 @@
2020

2121
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.pngmath', 'numpydoc',
2222
'sphinx.ext.intersphinx', 'sphinx.ext.coverage',
23-
'sphinx.ext.doctest', 'sphinx.ext.autosummary']
24-
25-
# Determine if the matplotlib has a recent enough version of the
26-
# plot_directive, otherwise use the local fork.
27-
try:
28-
from matplotlib.sphinxext import plot_directive
29-
except ImportError:
30-
use_matplotlib_plot_directive = False
31-
else:
32-
try:
33-
use_matplotlib_plot_directive = (plot_directive.__version__ >= 2)
34-
except AttributeError:
35-
use_matplotlib_plot_directive = False
36-
37-
if use_matplotlib_plot_directive:
38-
extensions.append('matplotlib.sphinxext.plot_directive')
39-
else:
40-
extensions.append('plot_directive')
23+
'sphinx.ext.doctest', 'sphinx.ext.autosummary',
24+
'matplotlib.sphinxext.plot_directive']
4125

4226
# Add any paths that contain templates here, relative to this directory.
4327
templates_path = ['_templates']
@@ -61,7 +45,7 @@
6145
version = re.sub(r'(\.dev\d+).*?$', r'\1', version)
6246
# The full version, including alpha/beta/rc tags.
6347
release = numpy.__version__
64-
print version, release
48+
print("%s %s" % (version, release))
6549

6650
# There are two options for replacing |today|: either, you set today to some
6751
# non-false value, then it is used:
@@ -283,9 +267,6 @@
283267
'text.usetex': False,
284268
}
285269

286-
if not use_matplotlib_plot_directive:
287-
import matplotlib
288-
matplotlib.rcParams.update(plot_rcparams)
289270

290271
# -----------------------------------------------------------------------------
291272
# Source code links
@@ -294,15 +275,15 @@
294275
import inspect
295276
from os.path import relpath, dirname
296277

297-
for name in ['sphinx.ext.linkcode', 'linkcode', 'numpydoc.linkcode']:
278+
for name in ['sphinx.ext.linkcode', 'numpydoc.linkcode']:
298279
try:
299280
__import__(name)
300281
extensions.append(name)
301282
break
302283
except ImportError:
303284
pass
304285
else:
305-
print "NOTE: linkcode extension not found -- no links to source generated"
286+
print("NOTE: linkcode extension not found -- no links to source generated")
306287

307288
def linkcode_resolve(domain, info):
308289
"""

doc/sphinxext/MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
recursive-include tests *.py
1+
recursive-include numpydoc/tests *.py
22
include *.txt

doc/sphinxext/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

doc/sphinxext/numpydoc/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .numpydoc import setup

doc/sphinxext/comment_eater.py renamed to doc/sphinxext/numpydoc/comment_eater.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
from cStringIO import StringIO
1+
import sys
2+
if sys.version_info[0] >= 3:
3+
from io import StringIO
4+
else:
5+
from cStringIO import StringIO
6+
27
import compiler
38
import inspect
49
import textwrap
510
import tokenize
611

7-
from compiler_unparse import unparse
12+
from .compiler_unparse import unparse
813

914

1015
class Comment(object):
@@ -68,7 +73,11 @@ def __init__(self):
6873
def process_file(self, file):
6974
""" Process a file object.
7075
"""
71-
for token in tokenize.generate_tokens(file.next):
76+
if sys.version_info[0] >= 3:
77+
nxt = file.__next__
78+
else:
79+
nxt = file.next
80+
for token in tokenize.generate_tokens(nxt):
7281
self.process_token(*token)
7382
self.make_index()
7483

doc/sphinxext/compiler_unparse.py renamed to doc/sphinxext/numpydoc/compiler_unparse.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
"""
1313

1414
import sys
15-
import cStringIO
1615
from compiler.ast import Const, Name, Tuple, Div, Mul, Sub, Add
1716

17+
if sys.version_info[0] >= 3:
18+
from io import StringIO
19+
else:
20+
from cStringIO import StringIO
21+
1822
def unparse(ast, single_line_functions=False):
19-
s = cStringIO.StringIO()
23+
s = StringIO()
2024
UnparseCompilerAst(ast, s, single_line_functions)
2125
return s.getvalue().lstrip()
2226

doc/sphinxext/docscrape.py renamed to doc/sphinxext/numpydoc/docscrape.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
33
"""
44

5+
import sys
56
import inspect
67
import textwrap
78
import re
89
import pydoc
9-
from StringIO import StringIO
1010
from warnings import warn
1111
import collections
1212

13+
if sys.version_info[0] >= 3:
14+
from io import StringIO
15+
else:
16+
from cStringIO import StringIO
17+
1318
class Reader(object):
1419
"""A line-based string reader.
1520
@@ -114,7 +119,7 @@ def __getitem__(self,key):
114119
return self._parsed_data[key]
115120

116121
def __setitem__(self,key,val):
117-
if not key in self._parsed_data:
122+
if key not in self._parsed_data:
118123
warn("Unknown section %s" % key)
119124
else:
120125
self._parsed_data[key] = val
@@ -266,13 +271,17 @@ def _parse_summary(self):
266271
if self._is_at_section():
267272
return
268273

269-
summary = self._doc.read_to_next_empty_line()
270-
summary_str = " ".join([s.strip() for s in summary]).strip()
271-
if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
272-
self['Signature'] = summary_str
273-
if not self._is_at_section():
274-
self['Summary'] = self._doc.read_to_next_empty_line()
275-
else:
274+
# If several signatures present, take the last one
275+
while True:
276+
summary = self._doc.read_to_next_empty_line()
277+
summary_str = " ".join([s.strip() for s in summary]).strip()
278+
if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
279+
self['Signature'] = summary_str
280+
if not self._is_at_section():
281+
continue
282+
break
283+
284+
if summary is not None:
276285
self['Summary'] = summary
277286

278287
if not self._is_at_section():
@@ -371,7 +380,7 @@ def _str_index(self):
371380
idx = self['index']
372381
out = []
373382
out += ['.. index:: %s' % idx.get('default','')]
374-
for section, references in idx.iteritems():
383+
for section, references in idx.items():
375384
if section == 'default':
376385
continue
377386
out += [' :%s: %s' % (section, ', '.join(references))]
@@ -451,8 +460,8 @@ def __str__(self):
451460
'meth': 'method'}
452461

453462
if self._role:
454-
if not self._role in roles:
455-
print "Warning: invalid role %s" % self._role
463+
if self._role not in roles:
464+
print("Warning: invalid role %s" % self._role)
456465
out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''),
457466
func_name)
458467

@@ -482,12 +491,19 @@ def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
482491
NumpyDocString.__init__(self, doc)
483492

484493
if config.get('show_class_members', True):
485-
if not self['Methods']:
486-
self['Methods'] = [(name, '', '')
487-
for name in sorted(self.methods)]
488-
if not self['Attributes']:
489-
self['Attributes'] = [(name, '', '')
490-
for name in sorted(self.properties)]
494+
def splitlines_x(s):
495+
if not s:
496+
return []
497+
else:
498+
return s.splitlines()
499+
500+
for field, items in [('Methods', self.methods),
501+
('Attributes', self.properties)]:
502+
if not self[field]:
503+
self[field] = [
504+
(name, '',
505+
splitlines_x(pydoc.getdoc(getattr(self._cls, name))))
506+
for name in sorted(items)]
491507

492508
@property
493509
def methods(self):
@@ -503,4 +519,6 @@ def properties(self):
503519
if self._cls is None:
504520
return []
505521
return [name for name,func in inspect.getmembers(self._cls)
506-
if not name.startswith('_') and func is None]
522+
if not name.startswith('_') and
523+
(func is None or isinstance(func, property) or
524+
inspect.isgetsetdescriptor(func))]

doc/sphinxext/docscrape_sphinx.py renamed to doc/sphinxext/numpydoc/docscrape_sphinx.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re, inspect, textwrap, pydoc
22
import sphinx
3-
from docscrape import NumpyDocString, FunctionDoc, ClassDoc
43
import collections
4+
from .docscrape import NumpyDocString, FunctionDoc, ClassDoc
55

66
class SphinxDocString(NumpyDocString):
77
def __init__(self, docstring, config={}):
@@ -73,7 +73,16 @@ def _str_member_list(self, name):
7373
others = []
7474
for param, param_type, desc in self[name]:
7575
param = param.strip()
76-
if not self._obj or hasattr(self._obj, param):
76+
77+
# Check if the referenced member can have a docstring or not
78+
param_obj = getattr(self._obj, param, None)
79+
if not (callable(param_obj)
80+
or isinstance(param_obj, property)
81+
or inspect.isgetsetdescriptor(param_obj)):
82+
param_obj = None
83+
84+
if param_obj and (pydoc.getdoc(param_obj) or not desc):
85+
# Referenced object has a docstring
7786
autosum += [" %s%s" % (prefix, param)]
7887
else:
7988
others.append((param, param_type, desc))
@@ -83,15 +92,15 @@ def _str_member_list(self, name):
8392
out += autosum
8493

8594
if others:
86-
maxlen_0 = max([len(x[0]) for x in others])
87-
maxlen_1 = max([len(x[1]) for x in others])
88-
hdr = "="*maxlen_0 + " " + "="*maxlen_1 + " " + "="*10
89-
fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1)
90-
n_indent = maxlen_0 + maxlen_1 + 4
91-
out += [hdr]
95+
maxlen_0 = max(3, max([len(x[0]) for x in others]))
96+
hdr = u"="*maxlen_0 + u" " + u"="*10
97+
fmt = u'%%%ds %%s ' % (maxlen_0,)
98+
out += ['', hdr]
9299
for param, param_type, desc in others:
93-
out += [fmt % (param.strip(), param_type)]
94-
out += self._str_indent(desc, n_indent)
100+
desc = u" ".join(x.strip() for x in desc).strip()
101+
if param_type:
102+
desc = "(%s) %s" % (param_type, desc)
103+
out += [fmt % (param.strip(), desc)]
95104
out += [hdr]
96105
out += ['']
97106
return out
@@ -128,7 +137,7 @@ def _str_index(self):
128137
return out
129138

130139
out += ['.. index:: %s' % idx.get('default','')]
131-
for section, references in idx.iteritems():
140+
for section, references in idx.items():
132141
if section == 'default':
133142
continue
134143
elif section == 'refguide':

doc/sphinxext/linkcode.py renamed to doc/sphinxext/numpydoc/linkcode.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212
import warnings
1313
import collections
14-
warnings.warn("This extension has been submitted to Sphinx upstream. "
15-
"Use the version from there if it is accepted "
14+
15+
warnings.warn("This extension has been accepted to Sphinx upstream. "
16+
"Use the version from there (Sphinx >= 1.2) "
1617
"https://bitbucket.org/birkenfeld/sphinx/pull-request/47/sphinxextlinkcode",
1718
FutureWarning, stacklevel=1)
1819

@@ -77,4 +78,4 @@ def doctree_read(app, doctree):
7778

7879
def setup(app):
7980
app.connect('doctree-read', doctree_read)
80-
app.add_config_value('linkcode_resolve', None, 'env')
81+
app.add_config_value('linkcode_resolve', None, '')

doc/sphinxext/numpydoc.py renamed to doc/sphinxext/numpydoc/numpydoc.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
if sphinx.__version__ < '1.0.1':
2323
raise RuntimeError("Sphinx 1.0.1 or newer is required")
2424

25-
import os, re, pydoc
26-
from docscrape_sphinx import get_doc_object, SphinxDocString
25+
import os, sys, re, pydoc
26+
from .docscrape_sphinx import get_doc_object, SphinxDocString
2727
from sphinx.util.compat import Directive
2828
import inspect
2929

@@ -35,28 +35,32 @@ def mangle_docstrings(app, what, name, obj, options, lines,
3535

3636
if what == 'module':
3737
# Strip top title
38-
title_re = re.compile(ur'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*',
38+
title_re = re.compile(u'^\\s*[#*=]{4,}\\n[a-z0-9 -]+\\n[#*=]{4,}\\s*',
3939
re.I|re.S)
4040
lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n")
4141
else:
4242
doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg)
43-
lines[:] = unicode(doc).split(u"\n")
43+
if sys.version_info[0] >= 3:
44+
doc = str(doc)
45+
else:
46+
doc = str(doc).decode('utf-8')
47+
lines[:] = doc.split(u"\n")
4448

4549
if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \
4650
obj.__name__:
4751
if hasattr(obj, '__module__'):
4852
v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__))
4953
else:
5054
v = dict(full_name=obj.__name__)
51-
lines += [u'', u'.. htmlonly::', '']
55+
lines += [u'', u'.. htmlonly::', u'']
5256
lines += [u' %s' % x for x in
5357
(app.config.numpydoc_edit_link % v).split("\n")]
5458

5559
# replace reference numbers so that there are no duplicates
5660
references = []
5761
for line in lines:
5862
line = line.strip()
59-
m = re.match(ur'^.. \[([a-z0-9_.-])\]', line, re.I)
63+
m = re.match(u'^.. \\[([a-z0-9_.-])\\]', line, re.I)
6064
if m:
6165
references.append(m.group(1))
6266

@@ -65,7 +69,7 @@ def mangle_docstrings(app, what, name, obj, options, lines,
6569
if references:
6670
for i, line in enumerate(lines):
6771
for r in references:
68-
if re.match(ur'^\d+$', r):
72+
if re.match(u'^\\d+$', r):
6973
new_r = u"R%d" % (reference_offset[0] + int(r))
7074
else:
7175
new_r = u"%s%d" % (r, reference_offset[0])
@@ -92,6 +96,9 @@ def mangle_signature(app, what, name, obj, options, sig, retann):
9296
return sig, u''
9397

9498
def setup(app, get_doc_object_=get_doc_object):
99+
if not hasattr(app, 'add_config_value'):
100+
return # probably called by nose, better bail out
101+
95102
global get_doc_object
96103
get_doc_object = get_doc_object_
97104

@@ -121,7 +128,7 @@ def __init__(self, *a, **kw):
121128
self.wrap_mangling_directives()
122129

123130
def wrap_mangling_directives(self):
124-
for name, objtype in self.directive_mangling_map.items():
131+
for name, objtype in list(self.directive_mangling_map.items()):
125132
self.directives[name] = wrap_mangling_directive(
126133
self.directives[name], objtype)
127134

0 commit comments

Comments
 (0)
0