Skip to content

Commit 2f3db5f

Browse files
committed
ENH: Add type annotations for Variables
Now including global variables. PEP 526 recommends instance variables should be type annotated on the class. With source parsing involved, that's the only case we opt to support. Fixes: #121 Refs: * 62c66cb * b8758dd * #175
1 parent 8535421 commit 2f3db5f

File tree

3 files changed

+48
-26
lines changed

3 files changed

+48
-26
lines changed

pdoc/__init__.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,9 @@ def _pep224_docstrings(doc_obj: Union['Module', 'Class'], *,
264264
target = assign_node.targets[0]
265265
elif isinstance(assign_node, ast_AnnAssign):
266266
target = assign_node.target
267-
# TODO: use annotation
268-
# Note, need only for instance variables. Class variables are already
269-
# handled by `typing.get_type_hints()` elsewhere.
267+
# Skip the annotation. PEP 526 says:
268+
# > Putting the instance variable annotations together in the class
269+
# > makes it easier to find them, and helps a first-time reader of the code.
270270
else:
271271
continue
272272

@@ -1154,6 +1154,13 @@ def return_annotation(self, *, link=None) -> str:
11541154
pass
11551155
else:
11561156
raise
1157+
try:
1158+
# global variables
1159+
annot = _get_type_hints(not self.cls and self.module.obj)[self.name]
1160+
except Exception:
1161+
pass
1162+
else:
1163+
raise
11571164
try:
11581165
annot = inspect.signature(self.obj).return_annotation
11591166
except Exception:
@@ -1170,9 +1177,8 @@ def return_annotation(self, *, link=None) -> str:
11701177
except RuntimeError: # Success.
11711178
pass
11721179
else:
1173-
# Don't warn on instance variables. Their annotations remain TODO
1174-
# to be found when parsing _pep224_docstrings()
1175-
if not getattr(self, 'instance_var', False):
1180+
# Don't warn on variables. The annotation just isn't available.
1181+
if not isinstance(self, Variable):
11761182
warn("Error handling return annotation for {!r}".format(self), stacklevel=3)
11771183

11781184
if annot is inspect.Parameter.empty or not annot:

pdoc/templates/html.mako

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
1717
def to_html(text):
1818
return _to_html(text, module=module, link=link, latex_math=latex_math)
19+
20+
21+
def get_annotation(bound_method, sep=':'):
22+
annot = show_type_annotations and bound_method(link=link) or ''
23+
if annot:
24+
annot = ' ' + sep + '\N{NBSP}' + annot
25+
return annot
1926
%>
2027

2128
<%def name="ident(name)"><span class="ident">${name}</span></%def>
@@ -100,11 +107,9 @@
100107
<dt id="${f.refname}"><code class="name flex">
101108
<%
102109
params = ', '.join(f.params(annotate=show_type_annotations, link=link))
103-
returns = show_type_annotations and f.return_annotation(link=link) or ''
104-
if returns:
105-
returns = ' ->\N{NBSP}' + returns
110+
return_type = get_annotation(f.return_annotation, '->')
106111
%>
107-
<span>${f.funcdef()} ${ident(f.name)}</span>(<span>${params})${returns}</span>
112+
<span>${f.funcdef()} ${ident(f.name)}</span>(<span>${params})${return_type}</span>
108113
</code></dt>
109114
<dd>${show_desc(f)}</dd>
110115
</%def>
@@ -147,7 +152,8 @@
147152
<h2 class="section-title" id="header-variables">Global variables</h2>
148153
<dl>
149154
% for v in variables:
150-
<dt id="${v.refname}"><code class="name">var ${ident(v.name)}</code></dt>
155+
<% return_type = get_annotation(v.type_annotation) %>
156+
<dt id="${v.refname}"><code class="name">var ${ident(v.name)}${return_type}</code></dt>
151157
<dd>${show_desc(v)}</dd>
152158
% endfor
153159
</dl>
@@ -209,7 +215,8 @@
209215
<h3>Class variables</h3>
210216
<dl>
211217
% for v in class_vars:
212-
<dt id="${v.refname}"><code class="name">var ${ident(v.name)}</code></dt>
218+
<% return_type = get_annotation(v.type_annotation) %>
219+
<dt id="${v.refname}"><code class="name">var ${ident(v.name)}${return_type}</code></dt>
213220
<dd>${show_desc(v)}</dd>
214221
% endfor
215222
</dl>
@@ -226,12 +233,8 @@
226233
<h3>Instance variables</h3>
227234
<dl>
228235
% for v in inst_vars:
229-
<%
230-
var_type = show_type_annotations and v.type_annotation(link=link) or ''
231-
if var_type:
232-
var_type = ' ->\N{NBSP}' + var_type
233-
%>
234-
<dt id="${v.refname}"><code class="name">var ${ident(v.name)}${var_type}</code></dt>
236+
<% return_type = get_annotation(v.type_annotation) %>
237+
<dt id="${v.refname}"><code class="name">var ${ident(v.name)}${return_type}</code></dt>
235238
<dd>${show_desc(v)}</dd>
236239
% endfor
237240
</dl>

pdoc/test/__init__.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -818,14 +818,27 @@ def prop(self) -> typing.Optional[int]:
818818

819819
@ignore_warnings
820820
@unittest.skipIf(sys.version_info < (3, 6), 'variable annotation unsupported in <Py3.6')
821-
def test_Variable_type_annotation_py35plus(self):
822-
exec('''class Foo:
823-
var: int = 3
824-
"""var"""
825-
''')
826-
mod = pdoc.Module(pdoc)
827-
cls = pdoc.Class('Foobar', mod, locals()['Foo'])
828-
self.assertEqual(cls.doc['var'].type_annotation(), 'int')
821+
def test_Variable_type_annotation_py36plus(self):
822+
with temp_dir() as path:
823+
filename = os.path.join(path, 'module36syntax.py')
824+
with open(filename, 'w') as f:
825+
f.write('''
826+
var: str = 'x'
827+
"""dummy"""
828+
829+
class Foo:
830+
var: int = 3
831+
"""dummy"""
832+
833+
def __init__(self):
834+
self.var2: float = 1
835+
"""dummy"""
836+
''')
837+
mod = pdoc.Module(pdoc.import_module(filename))
838+
self.assertEqual(mod.doc['var'].type_annotation(), 'str')
839+
self.assertEqual(mod.doc['Foo'].doc['var'].type_annotation(), 'int')
840+
self.assertIsInstance(mod.doc['Foo'].doc['var2'], pdoc.Variable)
841+
self.assertEqual(mod.doc['Foo'].doc['var2'].type_annotation(), '') # Won't fix
829842

830843
@ignore_warnings
831844
def test_Class_docstring(self):

0 commit comments

Comments
 (0)