Open leycec opened 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.
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. Theautodoc
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 byautodoc_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 returningTrue
only ifautodoc
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) whenautodoc
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:
Path(argv[0]).name == "sphinx-build" or Path(argv[0]).name == "build.py"
) to detect whether Sphinx is currently running. Of course, that's horrifyingly non-portable, fragile, and overly coarse-grained. Leveraging that test in@beartype
would have the unintended side effect of prohibiting third-party Sphinx extension authors from type-checking their APIs with@beartype
. Moreover, it fails to generalize to build scripts, systems, and process not satisfying that finite list of basenames. This can't be the way.SPHINX_BUILD
,GENERATING_DOCS
) from within aconf.py
configuration and then subsequently detecting that variable from within a decorator. Of course, we can't do that;@beartype
doesn't control third-partyconf.py
configurations.builtins.__sphinx_build__
global dunder attribute (guaranteed to break the back of Python and/or Sphinx at some future date) from within aconf.py
configuration and then subsequently detecting that variable from within a decorator. Of course, we can't do that;@beartype
doesn't control third-partyconf.py
configurations.Indeed, one of the above answers notes that:
In short, nothing works.
Describe alternatives you've considered
I briefly considered the Sphinx Event API. The
autodoc
extension fires off a number ofautodoc
-specific events. In theory,@beartype
could connect an internal event handler to Sphinx's currentapp
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 namedconf.py
and then introspect theapp
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 notautodoc
is currently running. I can easily envision cases in which noautodoc
-specific event fires – yetautodoc
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: