Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

automodule does not work with TYPE_CHECKING guarded code #13137

Open
LecrisUT opened this issue Nov 16, 2024 · 17 comments
Open

automodule does not work with TYPE_CHECKING guarded code #13137

LecrisUT opened this issue Nov 16, 2024 · 17 comments
Labels

Comments

@LecrisUT
Copy link

Describe the bug

I want to document TypeAlias variables which are typically guarded by if TYPE_CHECKING or they might be defined in a stub file. The issue is that these members are not automatically added and indexed by automodule with members. If I move the section to outside the TYPE_CHECKING if-guard, then the member is being documented.

How to Reproduce

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from typing import TypeAlias

    ComplexType: TypeAlias = int | str
    """This has some documentation"""

def some_func(input: ComplexType) -> None:
    """Some function that uses the type alias"""

Environment Information

Platform:              linux; (Linux-6.11.7-300.fc41.x86_64-x86_64-with-glibc2.40)
Python version:        3.13.0 (main, Oct  8 2024, 00:00:00) [GCC 14.2.1 20240912 (Red Hat 14.2.1-3)])
Python implementation: CPython
Sphinx version:        8.1.3
Docutils version:      0.21.2
Jinja2 version:        3.1.4
Pygments version:      2.18.0

Sphinx extensions

extensions = [
    "myst_parser",
    "sphinx.ext.intersphinx",
    "sphinx_tippy",
    "sphinx.ext.autodoc",
    "sphinx_autodoc_typehints",
]

Additional context

No response

@timhoffm
Copy link
Contributor

This is to be expected: autodoc introspects the module by importing them. This is done in a regular python run, in which case typing.TYPE_CHECKING is False. Therefore, your TypeAlias definition is not executed and thus does not exist. Consequently autodoc cannot see it.

I've not tested it, but you could try to adding

import typing
typing.TYPE_CHECKING = True

to your Sphinx conf.py. That should make your alias visible, but be aware that it cancels the effect of the TYPE_CHECKING variable and the guards it has put in place not only for your code but also for all the downstream dependencies you import.

@LecrisUT
Copy link
Author

I've tested it out a bit and it breaks on sphinx eources even, so it seems a more sophisticated solution is needed. Is it possible to interogate typing for what members it sees in __dict__, etc. and try to union the results?

@timhoffm
Copy link
Contributor

timhoffm commented Nov 17, 2024

typing is just a module. It doesn’t see anything. autodoc uses ordinary runtime introspection. It has no means to get information from a block that’s excluded from running through an effective if False statement. The only way to get that info would be statically parsing the AST.

You could try to unhide your specific location only by something like

if TYPE_CHECKING or “sphinx.ext.autodoc” in sys.modules:
    …

(Note: This is an idea, not tested because I don’t have a system to test at hand right now)

@LecrisUT
Copy link
Author

The or approach indeed worked. I guess one approach would be to patch in a context TYPE_CHECKING. Maybe this still fails when there are circular dependencies though 🤔.

I was thinking of typing.reveal_type or typing.get_type_hints, but I've tried those and that didn't help. I've also tried PEP695, but that does not have any support atm it seems. Static parsing would be nice and there are sphinx-autoapi and sphinx-autodoc2 that do the static documentation building, but these would not support well more complex objects like attrs that can dynamically alter the docstring. It would be great if these methods could be combined, having 2 passes complementing each other's documentations

@AA-Turner
Copy link
Member

@LecrisUT does setting SPHINX_AUTODOC_RELOAD_MODULES=1 work? It's a new environment variable in Sphinx 7.2.

A

@LecrisUT
Copy link
Author

@AA-Turner I supose you mean in reference to setting typing.TYPE_CHECKING=True. That didn't seem to work:

$ SPHINX_AUTODOC_RELOAD_MODULES=1 sphinx-build docs/ docs/_build
Running Sphinx v8.1.3
loading translations [en]... done

Extension error:
Could not import extension sphinx.builders.changes (exception: No module named 'typing_extensions')

Installing typing-extensions did not help, it simply delayed the error a bit further.

@AA-Turner
Copy link
Member

I imagine this is as you use typing_extensions in TYPE_CHECKING blocks. What was the second error?

A

@LecrisUT
Copy link
Author

Second attempt is trickier:

$ SPHINX_AUTODOC_RELOAD_MODULES=1 sphinx-build docs/ docs/_build
Running Sphinx v8.1.3
loading translations [en]... done

Extension error:
Could not import extension sphinx.builders.epub3 (exception: cannot import name '_ReadableStream' from 'sphinx.util.typing' (/home/lecris/PycharmProjects/fmf-jinja/venv/lib/python3.13/site-packages/sphinx/util/typing.py))

@AA-Turner
Copy link
Member

Can you provide a full reproducer? It shouldn't be reloading internal modules (such as sphinx.builders.epub3), I think.

A

@LecrisUT
Copy link
Author

The project I'm working on is at: https://github.com/LecrisUT/fmf-jinja/tree/fix/use-upstream-fmf

That branch lacks the typing.TYPE_CHECKING change in conf.py, but it should be straight forward to add for a reproduction. The test item is in ``fmf_jinja.utils`

@timhoffm
Copy link
Contributor

timhoffm commented Nov 17, 2024

@AA-Turner I've seen the changelog entry for SPHINX_AUTODOC_RELOAD_MODULES. However, from that I've not been able to understand when it should be used and what it does exactly. There's no mentioning of it in the regular docs.

I also feel this is a bit inflexible because it's a global variable. Does it only affect the inspected module or may it propagate to modules that are imported in there as well? Even if it only affects the imports for autodoc, it affects all the documented modules. Extending the if TYPE_CHECKING: guard to if TYPE_CHECKING or [sphinx autodoc]: seems like the more flexible approach. how exactly or [sphinx autodoc] is spelled out can be discussed or “sphinx.ext.autodoc” in sys.modules was a quick guess.

@LecrisUT
Copy link
Author

Extending the if TYPE_CHECKING: guard to if TYPE_CHECKING or [sphinx autodoc]: seems like the more flexible approach

Preferably there would be a different approach. This method:

  • might affect static analysis tools like ruff which have a rule that check if TYPE_CHECKING guard
  • requires a lot of changes on the user side
  • would break if a circular dependency is introduced in the if-guard

@electric-coder
Copy link

electric-coder commented Nov 18, 2024

@timhoffm

Extending the if TYPE_CHECKING: guard to if TYPE_CHECKING or [sphinx autodoc]: seems like the more flexible approach.

This doesn't make any sense, you are not supposed to do invasive changes to the py source for autodoc to work. (Many previous contributors have addressed this -there are workarounds available that get the job done, with some digging in the knowledge base they can be found- and none of them saw the need to change the TYPE_CHECKING clause itself.)

TypeAlias itself isn't currently supported as far as I can tell (wait a minute! I checked one of my projects and I got it working since at least Sphinx 7.2, I published a description of the workaround that several others built on, it's somewhere in an open issue here in the Sphinx repo) what's more there are about 10 outstanding type alias bugs not counting the specific relation between TYPE_CHECKING and how the imports are done, see #9813 - that's to say that currently the prevalent workaround is to use strings instead of the bare type when using the alias.

By all accounts this bug report is a duplicate.

@arthur-tacca
Copy link

If there's nothing weird in the typing check branch then there's no reason to use the check at all. The snippet in the opening comment is a case in point. Why isn't it just:

from typing import TypeAlias

ComplexType: TypeAlias = int | str
"""This has some documentation"""

def some_func(input: ComplexType) -> None:
    """Some function that uses the type alias"""

That would work fine with type checking, autodoc and regular execution.

I'd agree that if your code is complex enough to need a TYPE_CHECKING at all then it's complex enough that you need to be explicit about which branch you want autodoc to use by explicitly specify in your code. That's not an unreasonable imposition on the code author.

(Just an interested autodoc user.)

@LecrisUT
Copy link
Author

LecrisUT commented Nov 23, 2024

One issue is with __future__.annotations and guaranteeing the code is compatible with lower python versions, with the type-annotation only running on later python versions. The | operator is not acceptable with python<3.10 which is the case for RHEL9 and some other LTS distros that are currently being supported.

@spacemanspiff2007
Copy link

I'd agree that if your code is complex enough to need a TYPE_CHECKING at all then it's complex enough that you need to be explicit about which branch you want autodoc to use by explicitly specify in your code.

I strongly disagree!
Using TYPE_CHECKING is the standard mechanism to prevent cyclic imports.
Additionally linters like ruff will complain about unused imports and even automatically move typing imports into a TYPE_CHECKING block.

Typing imports under TYPE_CHECKING is the status quo in python and should supported accordingly.

@LecrisUT
Copy link
Author

Randomly browsing the PEPs, PEP649 seems related to TYPE_CHECKING. Not sure if this would offer some useful mechanism here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants