pdoc icon indicating copy to clipboard operation
pdoc copied to clipboard

Types with nested "|" (Union) are not formatted well

Open hhoppe opened this issue 3 years ago • 5 comments

For this program:

from __future__ import annotations
from typing import Iterable

def function1(value: int | str = 1) -> None:
  pass

def function2(value: int | str | Iterable[int | str] = 1) -> None:
  pass

pdoc produces the documentation:

def function1(value: int | str = 1) -> None:

def function2(value: Union[int, str, Iterable[int | str]] = 1) -> None:

If feasible, it might be best to always use the more modern ... | ... syntax and never the Union[...] syntax.

hhoppe avatar Aug 03 '22 19:08 hhoppe

This seems to be an upstream bug in Python itself:

>>> from typing import Iterable
>>> int | str | Iterable[int | str]
typing.Union[int, str, typing.Iterable[int | str]]

as well as

>>> import inspect
>>> inspect.formatannotation(int | str | Iterable[int | str])
'Union[int, str, Iterable[int | str]]'

mhils avatar Aug 03 '22 19:08 mhils

Interesting; I can reproduce your results above, but then also:

>>> def f(a: int | str | Iterable[int | str] = 1):
>>>   return a
>>> inspect.formatannotation(f.__annotations__)
"{'a': 'int | str | Iterable[int | str]'}"

The difference might be related to the postponed evaluation of the annotation context?

hhoppe avatar Aug 03 '22 23:08 hhoppe

Actually, f.__annotations__ already contains the nice strings: {'a': 'int | str | Iterable[int | str]'}

I'm curious: what would be the drawbacks of directly using the strings from f.__annotations__ for the pdoc documentation (assuming that the code has enabled from __future__ import annotations)? It gives the user the choice to refer to local type aliases or to fully qualified module.types, etc.

hhoppe avatar Aug 04 '22 00:08 hhoppe

The main drawback is that linking becomes much harder (how do we know that Doc is actually pdoc.doc.Doc, but maybe we can do some tricks here similar (but simpler) to 1a in the other issue. I'll take a look! :)

mhils avatar Aug 04 '22 04:08 mhils

For the record, the problem here is that typing does not perfectly interoperate with the new types.UnionType (on which the | syntax is based). The following works as expected:

from collections.abc import Iterable  # not from typing!

def function2(value: int | str | Iterable[int | str] = 1) -> None:
  pass

mhils avatar Aug 12 '22 13:08 mhils

Amazing that you found this! I just confirmed on my code that this issue goes away when migrating to using collections.abc.

It does make that pdoc documentation longer, e.g. collections.abc.Iterable[...] so some control over verbosity of type expansion (#420) would still be handy.

hhoppe avatar Aug 12 '22 19:08 hhoppe