Skip to content

Commit

Permalink
improve TypedDict rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
mhils committed May 4, 2022
1 parent af758aa commit 44f2621
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 58 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# Unreleased: pdoc next

- Improve rendering of `typing.TypedDict` subclasses.
([#386](https://github.com/mitmproxy/pdoc/issues/386), [@mhils](https://github.com/mhils))


# 2022-04-24: pdoc 11.1.0

- Display line numbers when viewing source code.
Expand Down
2 changes: 1 addition & 1 deletion pdoc/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
default="restructuredtext",
choices=("markdown", "google", "numpy", "restructuredtext"),
help="The default docstring format. For non-Markdown formats, pdoc will first convert matching syntax elements to "
"Markdown and then process everything as Markdown.",
"Markdown and then process everything as Markdown.",
)
renderopts.add_argument(
"-e",
Expand Down
13 changes: 13 additions & 0 deletions pdoc/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,15 @@ class Class(Namespace[type]):
def __repr__(self):
return f"<{_decorators(self)}class {self.modulename}.{self.qualname}{_docstr(self)}{_children(self)}>"

@cached_property
def docstring(self) -> str:
doc = Doc.docstring.__get__(self) # type: ignore
if doc == dict.__doc__:
# Don't display default docstring for dict subclasses (primarily TypedDict).
return ""
else:
return doc

@cached_property
def _var_docstrings(self) -> dict[str, str]:
docstrings: dict[str, str] = {}
Expand Down Expand Up @@ -603,6 +612,7 @@ def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
try:
return self._declarations[member_name]
except KeyError: # pragma: no cover
# TypedDict botches __mro__ and may need special casing here.
warnings.warn(
f"Cannot determine where {self.fullname}.{member_name} is taken from, assuming current file."
)
Expand Down Expand Up @@ -639,6 +649,9 @@ def _member_objects(self) -> dict[str, Any]:
# Special case: Do not show a constructor for enums. They are typically not constructed by users.
# The alternative would be showing __new__, as __call__ is too verbose.
del unsorted["__init__"]
elif issubclass(self.obj, dict):
# Special case: Do not show a constructor for dict subclasses.
del unsorted["__init__"]
else:
# Check if there's a helpful Metaclass.__call__ or Class.__new__. This dance is very similar to
# https://github.com/python/cpython/blob/9feae41c4f04ca27fd2c865807a5caeb50bf4fc4/Lib/inspect.py#L2359-L2376
Expand Down
2 changes: 1 addition & 1 deletion pdoc/doc_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import typing
import warnings
from types import BuiltinFunctionType, ModuleType
from typing import Any, TYPE_CHECKING
from typing import _GenericAlias # type: ignore
from typing import TYPE_CHECKING, Any

from . import extract
from ._compat import GenericAlias, Literal, UnionType, get_origin
Expand Down
4 changes: 3 additions & 1 deletion pdoc/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@

def configure(
*,
docformat: Literal["markdown", "google", "numpy", "restructuredtext"] = "restructuredtext",
docformat: Literal[
"markdown", "google", "numpy", "restructuredtext"
] = "restructuredtext",
edit_url_map: Mapping[str, str] | None = None,
favicon: str | None = None,
footer_text: str = "",
Expand Down
74 changes: 47 additions & 27 deletions test/testdata/misc_py39.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,18 @@ <h2>API Documentation</h2>
<li>
<a class="class" href="#Foo">Foo</a>
<ul class="memberlist">
<li>
<a class="variable" href="#Foo.a">a</a>
</li>
</ul>

</li>
<li>
<a class="class" href="#Bar">Bar</a>
<ul class="memberlist">
<li>
<a class="variable" href="#Bar.b">b</a>
</li>
</ul>

</li>
Expand Down Expand Up @@ -95,10 +101,15 @@ <h1 class="modulename">
</span><span id="L-21"><a href="#L-21"><span class="linenos">21</span></a><span class="c1"># Testing some edge cases in our inlined implementation of ForwardRef._evaluate in _eval_type.</span>
</span><span id="L-22"><a href="#L-22"><span class="linenos">22</span></a><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="n">TypedDict</span><span class="p">):</span>
</span><span id="L-23"><a href="#L-23"><span class="linenos">23</span></a> <span class="n">a</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span>
</span><span id="L-24"><a href="#L-24"><span class="linenos">24</span></a>
</span><span id="L-24"><a href="#L-24"><span class="linenos">24</span></a> <span class="sd">&quot;&quot;&quot;First attribute.&quot;&quot;&quot;</span>
</span><span id="L-25"><a href="#L-25"><span class="linenos">25</span></a>
</span><span id="L-26"><a href="#L-26"><span class="linenos">26</span></a><span class="k">class</span> <span class="nc">Bar</span><span class="p">(</span><span class="n">Foo</span><span class="p">,</span> <span class="n">total</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span><span id="L-27"><a href="#L-27"><span class="linenos">27</span></a> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span>
</span><span id="L-26"><a href="#L-26"><span class="linenos">26</span></a>
</span><span id="L-27"><a href="#L-27"><span class="linenos">27</span></a><span class="k">class</span> <span class="nc">Bar</span><span class="p">(</span><span class="n">Foo</span><span class="p">,</span> <span class="n">total</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span><span id="L-28"><a href="#L-28"><span class="linenos">28</span></a> <span class="sd">&quot;&quot;&quot;A TypedDict subclass. TypedDict botches the MRO, so things aren&#39;t perfect here.&quot;&quot;&quot;</span>
</span><span id="L-29"><a href="#L-29"><span class="linenos">29</span></a> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span>
</span><span id="L-30"><a href="#L-30"><span class="linenos">30</span></a> <span class="sd">&quot;&quot;&quot;Second attribute.&quot;&quot;&quot;</span>
</span><span id="L-31"><a href="#L-31"><span class="linenos">31</span></a> <span class="n">c</span><span class="p">:</span> <span class="nb">str</span>
</span><span id="L-32"><a href="#L-32"><span class="linenos">32</span></a> <span class="c1"># undocumented attribute</span>
</span></pre></div>

</details>
Expand Down Expand Up @@ -191,28 +202,30 @@ <h5>Inherited Members</h5>
<summary>View Source</summary>
<div class="pdoc-code codehilite"><pre><span></span><span id="Foo-23"><a href="#Foo-23"><span class="linenos">23</span></a><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="n">TypedDict</span><span class="p">):</span>
</span><span id="Foo-24"><a href="#Foo-24"><span class="linenos">24</span></a> <span class="n">a</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span>
</span><span id="Foo-25"><a href="#Foo-25"><span class="linenos">25</span></a> <span class="sd">&quot;&quot;&quot;First attribute.&quot;&quot;&quot;</span>
</span></pre></div>

</details>

<div class="docstring"><p>dict() -> new empty dictionary
dict(mapping) -> new dictionary initialized from a mapping object's
(key, value) pairs
dict(iterable) -> new dictionary initialized as if via:
d = {}
for k, v in iterable:
d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
in the keyword argument list. For example: dict(one=1, two=2)</p>


<div id="Foo.a" class="classattr">
<div class="attr variable"><a class="headerlink" href="#Foo.a">#&nbsp;&nbsp</a>

<span class="name">a</span><span class="annotation">: Optional[int]</span>
</div>


<div class="docstring"><p>First attribute.</p>
</div>


</div>
<div class="inherited">
<h5>Inherited Members</h5>
<dl>
<div><dt>builtins.dict</dt>
<dd id="Foo.__init__" class="class">dict</dd>
<dd id="Foo.get" class="function">get</dd>
<dd id="Foo.get" class="function">get</dd>
<dd id="Foo.setdefault" class="function">setdefault</dd>
<dd id="Foo.pop" class="function">pop</dd>
<dd id="Foo.popitem" class="function">popitem</dd>
Expand All @@ -239,30 +252,37 @@ <h5>Inherited Members</h5>

<details>
<summary>View Source</summary>
<div class="pdoc-code codehilite"><pre><span></span><span id="Bar-27"><a href="#Bar-27"><span class="linenos">27</span></a><span class="k">class</span> <span class="nc">Bar</span><span class="p">(</span><span class="n">Foo</span><span class="p">,</span> <span class="n">total</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span><span id="Bar-28"><a href="#Bar-28"><span class="linenos">28</span></a> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span>
<div class="pdoc-code codehilite"><pre><span></span><span id="Bar-28"><a href="#Bar-28"><span class="linenos">28</span></a><span class="k">class</span> <span class="nc">Bar</span><span class="p">(</span><span class="n">Foo</span><span class="p">,</span> <span class="n">total</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span><span id="Bar-29"><a href="#Bar-29"><span class="linenos">29</span></a> <span class="sd">&quot;&quot;&quot;A TypedDict subclass. TypedDict botches the MRO, so things aren&#39;t perfect here.&quot;&quot;&quot;</span>
</span><span id="Bar-30"><a href="#Bar-30"><span class="linenos">30</span></a> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span>
</span><span id="Bar-31"><a href="#Bar-31"><span class="linenos">31</span></a> <span class="sd">&quot;&quot;&quot;Second attribute.&quot;&quot;&quot;</span>
</span><span id="Bar-32"><a href="#Bar-32"><span class="linenos">32</span></a> <span class="n">c</span><span class="p">:</span> <span class="nb">str</span>
</span><span id="Bar-33"><a href="#Bar-33"><span class="linenos">33</span></a> <span class="c1"># undocumented attribute</span>
</span></pre></div>

</details>

<div class="docstring"><p>dict() -> new empty dictionary
dict(mapping) -> new dictionary initialized from a mapping object's
(key, value) pairs
dict(iterable) -> new dictionary initialized as if via:
d = {}
for k, v in iterable:
d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
in the keyword argument list. For example: dict(one=1, two=2)</p>
<div class="docstring"><p>A TypedDict subclass. TypedDict botches the MRO, so things aren't perfect here.</p>
</div>


<div id="Bar.b" class="classattr">
<div class="attr variable"><a class="headerlink" href="#Bar.b">#&nbsp;&nbsp</a>

<span class="name">b</span><span class="annotation">: int</span>
</div>


<div class="docstring"><p>Second attribute.</p>
</div>


</div>
<div class="inherited">
<h5>Inherited Members</h5>
<dl>
<div><dt>builtins.dict</dt>
<dd id="Bar.__init__" class="class">dict</dd>
<dd id="Bar.get" class="function">get</dd>
<dd id="Bar.get" class="function">get</dd>
<dd id="Bar.setdefault" class="function">setdefault</dd>
<dd id="Bar.pop" class="function">pop</dd>
<dd id="Bar.popitem" class="function">popitem</dd>
Expand Down
5 changes: 5 additions & 0 deletions test/testdata/misc_py39.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class NamedTupleExample(NamedTuple):
# Testing some edge cases in our inlined implementation of ForwardRef._evaluate in _eval_type.
class Foo(TypedDict):
a: Optional[int]
"""First attribute."""


class Bar(Foo, total=False):
"""A TypedDict subclass. TypedDict botches the MRO, so things aren't perfect here."""
b: int
"""Second attribute."""
c: str
# undocumented attribute
32 changes: 4 additions & 28 deletions test/testdata/misc_py39.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,8 @@
<var id: int # Alias for field numb…>
<method def index(self, value, start=0, stop=9223372036854775807, /): ... # inherited from builtins.tuple.index, Return first index o…>
<method def count(self, value, /): ... # inherited from builtins.tuple.count, Return number of occ…>>
<class misc_py39.Foo # dict() -> new empty …
<class misc_py39.Foo.__init__ # inherited from builtins.dict.__init__, dict() -> new empty …
<method def __init__(self, /, *args, **kwargs): ... # inherited from builtins.dict.__init__>
<method def get(self, key, default=None, /): ... # inherited from builtins.dict.get, Return the value for…>
<method def setdefault(self, key, default=None, /): ... # inherited from builtins.dict.setdefault, Insert key with a va…>
<method def pop(unknown): ... # inherited from builtins.dict.pop, D.pop(k[,d]) -> v, r…>
<method def popitem(self, /): ... # inherited from builtins.dict.popitem, Remove and return a …>
<method def keys(unknown): ... # inherited from builtins.dict.keys, D.keys() -> a set-li…>
<method def items(unknown): ... # inherited from builtins.dict.items, D.items() -> a set-l…>
<method def values(unknown): ... # inherited from builtins.dict.values, D.values() -> an obj…>
<method def update(unknown): ... # inherited from builtins.dict.update, D.update([E, ]**F) -…>
<method def fromkeys(type, iterable, value=None, /): ... # inherited from builtins.dict.fromkeys, Create a new diction…>
<method def clear(unknown): ... # inherited from builtins.dict.clear, D.clear() -> None. …>
<method def copy(unknown): ... # inherited from builtins.dict.copy, D.copy() -> a shallo…>>
<class misc_py39.Foo
<var a: Optional[int] # First attribute.>
<method def get(self, key, default=None, /): ... # inherited from builtins.dict.get, Return the value for…>
<method def setdefault(self, key, default=None, /): ... # inherited from builtins.dict.setdefault, Insert key with a va…>
<method def pop(unknown): ... # inherited from builtins.dict.pop, D.pop(k[,d]) -> v, r…>
Expand All @@ -30,20 +18,8 @@
<method def fromkeys(type, iterable, value=None, /): ... # inherited from builtins.dict.fromkeys, Create a new diction…>
<method def clear(unknown): ... # inherited from builtins.dict.clear, D.clear() -> None. …>
<method def copy(unknown): ... # inherited from builtins.dict.copy, D.copy() -> a shallo…>>
<class misc_py39.Bar # dict() -> new empty …
<class misc_py39.Bar.__init__ # inherited from builtins.dict.__init__, dict() -> new empty …
<method def __init__(self, /, *args, **kwargs): ... # inherited from builtins.dict.__init__>
<method def get(self, key, default=None, /): ... # inherited from builtins.dict.get, Return the value for…>
<method def setdefault(self, key, default=None, /): ... # inherited from builtins.dict.setdefault, Insert key with a va…>
<method def pop(unknown): ... # inherited from builtins.dict.pop, D.pop(k[,d]) -> v, r…>
<method def popitem(self, /): ... # inherited from builtins.dict.popitem, Remove and return a …>
<method def keys(unknown): ... # inherited from builtins.dict.keys, D.keys() -> a set-li…>
<method def items(unknown): ... # inherited from builtins.dict.items, D.items() -> a set-l…>
<method def values(unknown): ... # inherited from builtins.dict.values, D.values() -> an obj…>
<method def update(unknown): ... # inherited from builtins.dict.update, D.update([E, ]**F) -…>
<method def fromkeys(type, iterable, value=None, /): ... # inherited from builtins.dict.fromkeys, Create a new diction…>
<method def clear(unknown): ... # inherited from builtins.dict.clear, D.clear() -> None. …>
<method def copy(unknown): ... # inherited from builtins.dict.copy, D.copy() -> a shallo…>>
<class misc_py39.Bar # A TypedDict subclass…
<var b: int # Second attribute.>
<method def get(self, key, default=None, /): ... # inherited from builtins.dict.get, Return the value for…>
<method def setdefault(self, key, default=None, /): ... # inherited from builtins.dict.setdefault, Insert key with a va…>
<method def pop(unknown): ... # inherited from builtins.dict.pop, D.pop(k[,d]) -> v, r…>
Expand Down

0 comments on commit 44f2621

Please sign in to comment.