sphinx
sphinx copied to clipboard
sphinx.ext.autosummary with automatic recursion doesn't follow legitimate import statement in __init__.py file
Describe the bug
Sphinx fails to import & document a module that is itself imported in an __init__.py file. I'm at a loss as to understand why; on the face of it, there's nothing wrong with this import statement. If I comment out the import statement, Sphinx imports & documents the module perfectly. Why is this?
To Reproduce
- Clone
[email protected]:JamesALeedham/lusid-python-tools.gitand checkout thesphinx-bug-querybranch. - View the copy of the docs hosted here and observe that the
lusidtools.logger.LusidLoggerclass is not documented (ie. page is empty). - In the repo, edit
lusidtools/logger/__init__.pyand comment out the single import statement, ie.# from lusidtools.logger.LusidLogger import LusidLogger. - Follow the instructions in the docs Readme to rebuild and view the docs locally as HTML.
With the import statement in __init__.py commented out, Sphinx documents the lusidtools.logger.LusidLogger class perfectly. (Here's an example of what this page then looks like).
Environment info
- Sphinx version: 3.5.3
- Sphinx extensions:
sphinx.ext.autosummarywith the brilliant:recursive:option
hey everyone! I found the problematic code:
https://github.com/sphinx-doc/sphinx/blob/952866c689254d9a427e18693398bf36ccc505e9/sphinx/ext/autosummary/init.py#L633-L650
it's totally non-trivial I'd say. I try to explain it with a minimal example that you can access here https://github.com/Chilipp/autosummary-issue, or as zip file here. Assume your code looks like this:
autosummary_issue/
├── __init__.py
├── issue.py
with
__init__.pyfrom . import issueissue.pyimport autosummary_issue class DummyClass: """Some dummy class."""index.rst.. automodule:: autosummary_issue.issue :members: .. autosummary:: autosummary_issue.issue.DummyClass
when the import_by_name function get's called, it is getting two prefixes: "autosummary_issue.issue" from the .. currentmodule:: directive that is implicitly added by .. automodule::, and None.
The loop now goes through the prefixes and starts with creating the prefixed_name = "autosummary_issue.issue.autosummary_issue.issue.DummyClass". This is obviously the wrong name, but the problem is in this line:
https://github.com/sphinx-doc/sphinx/blob/952866c689254d9a427e18693398bf36ccc505e9/sphinx/ext/autosummary/init.py#L649
Here, the _import_by_name function should break, but it it doesn't, "autosummary_issue.issue.autosummary_issue.issue.DummyClass" is actually accessible because of the import statement (from . import issue) in __init__.py. So autosummary thinks that autosummary_issue.issue.autosummary_issue.issue.DummyClass is a valid object, tries to look it up in the index and obviously fails, because the index know that the real name is autosummary_issue.issue.DummyClass.
Hey @Chilipp , thanks for looking into this and glad you managed to find something...
One of the things that's puzzling me is that I ran Sphinx over a sister repo that also has import statements in __init__.py files and it worked fine. To be clear, it was exactly the same Sphinx setup (sphinx.ext.autosummary with :recursive: option) in exactly the same environment, and it documented the package perfectly (ie. I didn't have to comment out any of the import statements in the __init__.py file linked above to get Sphinx to work).
So perhaps a better title for this issue would be: why does Sphinx fail to follow some legitimate import statements in __init__.py files..?
Hey @JamesALeedham ! Did you use the same rst file in both cases? Because the critical thing in my example from above is the combination
- The import
- the
.. currentmodule::directive that is implicitly added with.. automodule::.
If you drop the automodule thing, or put the automodule behind the autosummary, everything works well
Just to be clear about my setup, I'm not handcrafting the .rst, I'm relying on :recursive: to do that for me: https://github.com/JamesALeedham/lusid-python-tools/blob/sphinx-setup/docs/api.rst
I guess Autosummary is then using my custom module template and class template to do the actual doc generation...
(At this point, I've probably reached my limit in understanding how Sphinx works... 😞 )
But yes, the Sphinx setup is 100% identical for both repos - one in which import statements in __init__.py files cause a problem, and one in which they don't.
Just to be clear about my setup, I'm not handcrafting the .rst, I'm relying on
:recursive:to do that for me: https://github.com/JamesALeedham/lusid-python-tools/blob/sphinx-setup/docs/api.rstI guess Autosummary is then using my custom module template and class template to do the actual doc generation...
(At this point, I've probably reached my limit in understanding how Sphinx works... disappointed )
But yes, the Sphinx setup is 100% identical for both repos - one in which
importstatements in__init__.pyfiles cause a problem, and one in which they don't.
Having this problem as well.
#8963 is linked
Hey @JamesALeedham ! Did you use the same rst file in both cases? Because the critical thing in my example from above is the combination
- The import
- the
.. currentmodule::directive that is implicitly added with.. automodule::.If you drop the automodule thing, or put the automodule behind the autosummary, everything works well
Switching automodule after autosummary manually worked for me. With the option "autosummary_generate_overwrite = False" allows to have something workable (more or less) and manage only the module level manually.
Not sure whether I got the topic correctly. I was trying to get functions imported in the __init__.py documented also at the corresponding module level. I just played around a bit and changing a single line would at least create an additional entry in the module level for such functions and classes.
https://github.com/sphinx-doc/sphinx/blob/952866c689254d9a427e18693398bf36ccc505e9/sphinx/ext/autosummary/generate.py#L254
changing this to
if imported or obj.__name__ in getattr(value, '__module__', None):
will work. However, i have looked into the code the first time. So no idea wehther I missed something important and this would damage other parts of the code.
Is there a workaround for this in the meanwhile?
For anyone dealing with this issue as well, I found a solution that worked for me. My auto generated rst looked like this:
..
module.rst
mypackage.mymodule
==================
.. automodule:: mypackage.mymodule
.. rubric:: Functions
.. autosummary::
:toctree:
:nosignatures:
:template: autosummary/base.rst
some_function
another_function
.. rubric:: Modules
.. autosummary::
:toctree:
:template: autosummary/module.rst
:recursive:
mypackage.mymodule.mysubmodule
The problem, as @Chilipp discovered, lies with automodule. Removing that without changing anything though will treat the functions as modules in and of themselves though. I ended up having to transform my rst file to look like this to get it working:
..
module.rst
mypackage.mymodule
==================
.. rubric:: Functions
.. autosummary::
:toctree:
:nosignatures:
:template: autosummary/base.rst
mypackage.mymodule.some_function
mypackage.mymodule.another_function
.. rubric:: Modules
.. autosummary::
:toctree:
:template: autosummary/module.rst
:recursive:
mypackage.mymodule.mysubmodule
Notice that the functions are given fully qualified import paths. Hope this helps someone else!