Skip to content

Commit 9ea83fd

Browse files
committed
ENH: Concat class and __init__ docstrings; have Class.params()
Fixes #24
1 parent 5df06cd commit 9ea83fd

File tree

4 files changed

+94
-19
lines changed

4 files changed

+94
-19
lines changed

pdoc/__init__.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ class Context(dict):
401401
If you don't pass your own `Context` instance to `Module` constructor,
402402
a global context object will be used.
403403
"""
404+
__pdoc__['Context.__init__'] = False
404405

405406

406407
_global_context = Context()
@@ -981,7 +982,8 @@ def _link_inheritance(self):
981982
'use `__pdoc__[key] = False` '
982983
'(key: {!r}, module: {!r}).'.format(name, self.name))
983984

984-
if name not in self.doc and refname not in self._context:
985+
if (not name.endswith('.__init__') and
986+
name not in self.doc and refname not in self._context):
985987
warn('__pdoc__-overriden key {!r} does not exist '
986988
'in module {!r}'.format(name, self.name))
987989

@@ -1073,6 +1075,10 @@ def find_ident(self, name: str) -> Doc:
10731075
`External` is returned populated with the given identifier.
10741076
"""
10751077
_name = name.rstrip('()') # Function specified with parentheses
1078+
1079+
if _name.endswith('.__init__'): # Ref to class' init is ref to class itself
1080+
_name = _name[:-len('.__init__')]
1081+
10761082
return (self.doc.get(_name) or
10771083
self._context.get(_name) or
10781084
self._context.get(self.name + '.' + _name) or
@@ -1127,6 +1133,11 @@ class Class(Doc):
11271133

11281134
def __init__(self, name, module, obj, *, docstring=None):
11291135
assert isinstance(obj, type)
1136+
if docstring is None:
1137+
init_doc = inspect.getdoc(obj.__init__) or ''
1138+
if init_doc == object.__init__.__doc__:
1139+
init_doc = ''
1140+
docstring = ((inspect.getdoc(obj) or '') + '\n\n' + init_doc).strip()
11301141
super().__init__(name, module, obj, docstring=docstring)
11311142

11321143
self.doc = {}
@@ -1138,8 +1149,7 @@ def __init__(self, name, module, obj, *, docstring=None):
11381149
for name, obj in inspect.getmembers(self.obj)
11391150
# Filter only *own* members. The rest are inherited
11401151
# in Class._fill_inheritance()
1141-
if (name in self.obj.__dict__ and
1142-
(_is_public(name) or name == '__init__'))]
1152+
if name in self.obj.__dict__ and _is_public(name)]
11431153
index = list(self.obj.__dict__).index
11441154
public_objs.sort(key=lambda i: index(i[0]))
11451155

@@ -1214,6 +1224,22 @@ def subclasses(self) -> List['Class']:
12141224
return [self.module.find_class(c)
12151225
for c in type.__subclasses__(self.obj)]
12161226

1227+
def params(self, *, annotate=False) -> List['str']:
1228+
"""
1229+
Return a list of formatted parameters accepted by the
1230+
class constructor (method `__init__`). See `pdoc.Function.params`.
1231+
"""
1232+
name = self.name + '.__init__'
1233+
qualname = self.qualname + '.__init__'
1234+
refname = self.refname + '.__init__'
1235+
exclusions = getattr(self.module.obj, "__pdoc__", {})
1236+
if name in exclusions or qualname in exclusions or refname in exclusions:
1237+
return []
1238+
1239+
params = Function._params(self.obj.__init__, annotate=annotate)
1240+
params = params[1:] if params[0] == 'self' else params
1241+
return params
1242+
12171243
def _filter_doc_objs(self, type: Type[T], include_inherited=True,
12181244
filter_func: Callable[[T], bool] = lambda x: True,
12191245
sort=True) -> List[T]:
@@ -1319,7 +1345,7 @@ class Function(Doc):
13191345

13201346
def __init__(self, name, module, obj, *, cls: Class = None, method=False):
13211347
"""
1322-
Same as `pdoc.Doc.__init__`, except `obj` must be a
1348+
Same as `pdoc.Doc`, except `obj` must be a
13231349
Python function object. The docstring is gathered automatically.
13241350
13251351
`cls` should be set when this is a method or a static function
@@ -1392,8 +1418,12 @@ def params(self, *, annotate: bool = False) -> List[str]:
13921418
13931419
[PEP 484]: https://www.python.org/dev/peps/pep-0484/
13941420
"""
1421+
return self._params(self.obj, annotate=annotate)
1422+
1423+
@staticmethod
1424+
def _params(func_obj, annotate=False):
13951425
try:
1396-
signature = inspect.signature(inspect.unwrap(self.obj))
1426+
signature = inspect.signature(inspect.unwrap(func_obj))
13971427
except ValueError:
13981428
# I guess this is for C builtin functions?
13991429
return ["..."]
@@ -1436,10 +1466,6 @@ def __repr__(self):
14361466

14371467
return params
14381468

1439-
def __lt__(self, other):
1440-
# Push __init__ to the top.
1441-
return self.name == '__init__' or super().__lt__(other)
1442-
14431469
@property
14441470
def refname(self):
14451471
return (self.cls.refname if self.cls else self.module.refname) + '.' + self.name
@@ -1455,7 +1481,7 @@ class Variable(Doc):
14551481
def __init__(self, name, module, docstring, *,
14561482
obj=None, cls: Class = None, instance_var=False):
14571483
"""
1458-
Same as `pdoc.Doc.__init__`, except `cls` should be provided
1484+
Same as `pdoc.Doc`, except `cls` should be provided
14591485
as a `pdoc.Class` object when this is a class or instance
14601486
variable.
14611487
"""

pdoc/templates/css.mako

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,6 @@
174174
.name.class > span:nth-child(2) {
175175
margin-left: .4em;
176176
}
177-
.name small {
178-
font-weight: normal;
179-
}
180177
.inherited {
181178
color: #999;
182179
border-left: 5px solid #eee;

pdoc/templates/html.mako

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,26 @@
181181
methods = c.methods(show_inherited_members, sort=sort_identifiers)
182182
mro = c.mro()
183183
subclasses = c.subclasses()
184+
params = ', '.join(c.params(annotate=show_type_annotations))
184185
%>
185186
<dt id="${c.refname}"><code class="flex name class">
186187
<span>class ${ident(c.name)}</span>
187-
% if mro:
188-
<span>(</span><span><small>ancestors:</small> ${', '.join(link(cls) for cls in mro)})</span>
189-
%endif
188+
% if params:
189+
<span>(</span><span>${params | h})</span>
190+
% endif
190191
</code></dt>
191192
192193
<dd>${show_desc(c)}
193194
195+
% if mro:
196+
<h3>Ancestors</h3>
197+
<ul class="hlist">
198+
% for cls in mro:
199+
<li>${link(cls)}</li>
200+
% endfor
201+
</ul>
202+
%endif
203+
194204
% if subclasses:
195205
<h3>Subclasses</h3>
196206
<ul class="hlist">

pdoc/test/__init__.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,12 +415,12 @@ def test__pdoc__dict(self):
415415
self.assertIn('A', mod.doc)
416416
self.assertNotIn('B', mod.doc)
417417

418-
with patch.object(module, '__pdoc__', {'B.__init__': False}):
418+
with patch.object(module, '__pdoc__', {'B.f': False}):
419419
mod = pdoc.Module(module)
420420
pdoc.link_inheritance()
421421
self.assertIn('B', mod.doc)
422-
self.assertNotIn('__init__', mod.doc['B'].doc)
423-
self.assertIsInstance(mod.find_ident('B.__init__'), pdoc.External)
422+
self.assertNotIn('f', mod.doc['B'].doc)
423+
self.assertIsInstance(mod.find_ident('B.f'), pdoc.External)
424424

425425
def test__pdoc__invalid_value(self):
426426
module = pdoc.import_module(EXAMPLE_MODULE)
@@ -450,6 +450,10 @@ def test_find_ident(self):
450450
self.assertIsInstance(result, pdoc.External)
451451
self.assertEqual(result.name, nonexistent)
452452

453+
# Ref by class __init__
454+
mod = pdoc.Module(pdoc)
455+
self.assertIs(mod.find_ident('pdoc.Doc.__init__').obj, pdoc.Doc)
456+
453457
def test_inherits(self):
454458
module = pdoc.Module(pdoc.import_module(EXAMPLE_MODULE))
455459
pdoc.link_inheritance()
@@ -573,6 +577,44 @@ def f() -> typing.List[typing.Union[str, pdoc.Doc]]: pass
573577
func = pdoc.Function('f', pdoc.Module(pdoc), f)
574578
self.assertEqual(func.return_annotation, 'List[Union[str,\xA0pdoc.Doc]]')
575579

580+
@ignore_warnings
581+
def test_Class_docstring(self):
582+
class A:
583+
"""foo"""
584+
585+
class B:
586+
def __init__(self):
587+
"""foo"""
588+
589+
class C:
590+
"""foo"""
591+
def __init__(self):
592+
"""bar"""
593+
594+
class D(C):
595+
"""baz"""
596+
597+
class E(C):
598+
def __init__(self):
599+
"""baz"""
600+
601+
self.assertEqual(pdoc.Class('A', pdoc.Module(pdoc), A).docstring, """foo""")
602+
self.assertEqual(pdoc.Class('B', pdoc.Module(pdoc), B).docstring, """foo""")
603+
self.assertEqual(pdoc.Class('C', pdoc.Module(pdoc), C).docstring, """foo\n\nbar""")
604+
self.assertEqual(pdoc.Class('D', pdoc.Module(pdoc), D).docstring, """baz\n\nbar""")
605+
self.assertEqual(pdoc.Class('E', pdoc.Module(pdoc), E).docstring, """foo\n\nbaz""")
606+
607+
@ignore_warnings
608+
def test_Class_params(self):
609+
class C:
610+
def __init__(self, x):
611+
pass
612+
613+
mod = pdoc.Module(pdoc)
614+
self.assertEqual(pdoc.Class('C', mod, C).params(), ['x'])
615+
with patch.dict(mod.obj.__pdoc__, {'C.__init__': False}):
616+
self.assertEqual(pdoc.Class('C', mod, C).params(), [])
617+
576618
def test_url(self):
577619
mod = pdoc.Module(pdoc.import_module(EXAMPLE_MODULE))
578620
pdoc.link_inheritance()

0 commit comments

Comments
 (0)