sphinx-doc / sphinx

The Sphinx documentation generator
https://www.sphinx-doc.org/
Other
6.45k stars 2.1k forks source link

[Feature Request] A new utility function returning True only if "autodoc" is currently running #9805

Open leycec opened 2 years ago

leycec commented 2 years ago

Is your feature request related to a problem? Please describe.

Firstly, my thanks to the tireless Sphinx crew (this means you!) for your dedication to the just cause of exhaustively well-documented Python APIs.

Secondly, hi! I'm @leycec, the author of @beartype, Python's constant-time runtime type-checking decorator. You can probably see where this is going already – but let's go there anyway.

Like most decorators, @beartype is typically applied at module scope. The autodoc extension imports the modules it imports, thus implicitly calling @beartype for each @beartype-decorated callable. This isn't a problem in and of itself. Problems arise, however, whenever a @beartype-decorated callable is annotated with one more classes mocked by autodoc_mock_imports. When that happens, @beartype raises exceptions at decoration time, because mocking subverts our assumptions and expectations about classes used as annotations.

Describe the solution you'd like

A new public utility function isautodoc() (or something) in the Sphinx API returning True only if autodoc is currently running. Given that, it would be trivial for @beartype to conditionally reduce to a noop (i.e., the identity decorator returning the passed callable undecorated) when autodoc is running.

I was quite surprised not to find such a function. There exist a multitude of StackOverflow posts on the subject – but none with anything resembling a satisfactory answer. For example:

Indeed, one of the above answers notes that:

I think the less surprising thing to go there is to put some explicit guard inside your "classmethod property cache" functions to not run when the file is being processed by sphinx. Since sphinx do not have this feature itself, you can use an environment variable for that...

In short, nothing works.

Describe alternatives you've considered

I briefly considered the Sphinx Event API. The autodoc extension fires off a number of autodoc-specific events. In theory, @beartype could connect an internal event handler to Sphinx's current app singleton on one or more of these events being fired – which would then notify @beartype that it should then reduce to a noop.

Of course, that's horribly convoluted, fragile, and non-trivial for something that should ideally just reduce to a trivial one-liner. I also have no idea how to actually implement that, because I have no idea how to retrieve Sphinx's current app singleton. I suppose I could violate sanity by searching up the call stack for a script named conf.py and then introspect the app attribute out of the stack frame encapsulating that call, but... now we've really violated sanity, because that wouldn't even be portable across Python interpreters.

Moreover, there isn't necessarily a one-to-one relationship between autodoc-specific events and whether or not autodoc is currently running. I can easily envision cases in which no autodoc-specific event fires – yet autodoc is running.

The fragile plenum of the @beartype API is now rupturing. :broken_heart:

Additional context

Thanks again, one and all. We cannot emphasize enough how amazing everyone here is. Viva la reStructuredText revolution! :fireworks:

john-hen commented 2 years ago

To add another hack to the mix, we could also do the following to find out if our package is currently being imported as part of a Sphinx build:

try:
    import sphinx
    sphinx_build = hasattr(sphinx, 'application')
except ImportError:
    sphinx_build = False

This takes advantage of the fact that Sphinx imports next to nothing when we just do import sphinx. The application module would only be in the name space if an entry point had been called. We could also check for sphinx.cmd, might be a tad more robust.

Or check sphinx.ext.autodoc specifically. but I don't really see the point of that. It's best to find out if Sphinx is running as there might be other extensions (Autosummary, Apidoc) that import external code.