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

Sphinx 7.2.5 breaks with PyO3: "modules may only be initialized once per interpreter process" #11662

Closed
pbybee opened this issue Aug 30, 2023 · 38 comments

Comments

@pbybee
Copy link

pbybee commented Aug 30, 2023

Describe the bug

New error message with this patch release. 7.2.4 succeeds in building the same documents.
PyO3 modules may only be initialized once per interpreter process

Full traceback
Traceback (most recent call last):
  File ".venv/lib/python3.10/site-packages/sphinx/cmd/build.py", line 298, in build_main
    app.build(args.force_all, args.filenames)
  File ".venv/lib/python3.10/site-packages/sphinx/application.py", line 351, in build
    self.builder.build_all()
  File ".venv/lib/python3.10/site-packages/sphinx/builders/__init__.py", line 252, in build_all
    self.build(None, summary=__('all source files'), method='all')
  File ".venv/lib/python3.10/site-packages/sphinx/builders/__init__.py", line 312, in build
    with logging.pending_warnings():
  File "/usr/lib/python3.10/contextlib.py", line 142, in __exit__
    next(self.gen)
  File ".venv/lib/python3.10/site-packages/sphinx/util/logging.py", line 222, in pending_warnings
    memhandler.flushTo(logger)
  File ".venv/lib/python3.10/site-packages/sphinx/util/logging.py", line 187, in flushTo
    logger.handle(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 1634, in handle
    self.callHandlers(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 1696, in callHandlers
    hdlr.handle(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 964, in handle
    rv = self.filter(record)
  File "/usr/lib/python3.10/logging/__init__.py", line 821, in filter
    result = f.filter(record)
  File ".venv/lib/python3.10/site-packages/sphinx/util/logging.py", line 427, in filter
    raise exc
sphinx.errors.SphinxWarning: autodoc: failed to import module 'test' from module 'test'; the following exception was raised:
PyO3 modules may only be initialized once per interpreter process

How to Reproduce

install latest sphinx and run
sphinx-build -WaE docs/project docs/project/_build/html

Environment Information

Platform:              linux; (Linux-5.4.0-149-generic-x86_64-with-glibc2.31)
Python version:        3.10.12 (main, Jun  7 2023, 12:45:35) [GCC 9.4.0])
Python implementation: CPython
Sphinx version:        7.2.5
Docutils version:      0.18.1
Jinja2 version:        3.1.2
Pygments version:      2.16.1

Sphinx extensions

myst-parser==2.0.0

Additional context

No response

@AA-Turner
Copy link
Member

@pbybee Please provide a full reproducer. PyO3 is for rust projects, which you haven't mentioned in your description. Does the error appear with Sphinx 7.2.4?

A

@AA-Turner AA-Turner changed the title patch release 7.2.5 breaks document builds with PyO3 modules may only be initialized once per interpreter process Sphinx 7.2.5 breaks with PyO3: "modules may only be initialized once per interpreter process" Aug 30, 2023
@pbybee
Copy link
Author

pbybee commented Aug 30, 2023

No 7.2.4 builds correctly. We are not using PyO3 in our project. Perhaps it's a dependancy of sphinx that revved. I'll dig a little more as I don't have good repro steps at the moment without digging through the sphinx build process.

@AA-Turner
Copy link
Member

If you're able to bisect, try before/after this commit: 8248be3

@AA-Turner
Copy link
Member

Perhaps it's a dependancy of sphinx that revved.

Very unlikely, more likely is that a dependency of the object you're documenting uses PyO3.

@picnixz
Copy link
Member

picnixz commented Aug 31, 2023

Can you check if the bad module contains a TYPE_CHECKING block and if any of the imports in this block contains some PyO3 object somewhere (or depends on it)? Could you also give us the dependencies of your project?

As Adam pointed out in another issue where the feature of reloading a module with TYPE_CHECKING enabled makes jax fails, can you check the bad module with pyanalyze?

@AA-Turner I am wondering, but for projects that would fail like that, could we perhaps make the TYPE_CHECKING feature optional? (it's probably impossible to make it optional per module due to dependencies that we would then need to build in advance).

@picnixz
Copy link
Member

picnixz commented Aug 31, 2023

Ok I know what happened.

  • Assume that you have a module using PyO3 say yay and a bad module boo
  • Assume you have a module a resp. b) importing yay (resp. yay and boo) in a TYPE_CHECKING block.
  • Import a: success for yay, failure for boo. We remove yay from sys.modules (the commit Adam referred to).
  • Import b: failure for yay even if sys.modules does not contain yay.

Reason: PyO3 seems to import modules but does not seem to "unimport" them if they are deleted from sys.modules. As such, it's probably impossible for us to do anything about that.

EDIT: I concluded as such by creating a module using PyO3, importing it, deleting it from.sys.modules, and re-importing it again and here is my configuration:

Platform:              linux; (Linux-5.10.136-android12-9-26258365-abA336BXXS7CWG1-aarch64-with-libc)
Python version:        3.11.4 (main, Jul  2 2023, 11:17:00) [Clang 14.0.7 (https://android.googlesource.com/toolchain/llvm-project 4c603efb0)
Python implementation: CPython
Sphinx version:        7.2.4
Docutils version:      0.18.1
Jinja2 version:        3.1.2
Pygments version:      2.16.1

@adityagoel4512
Copy link

This may not apply exclusively to PyO3. We are seeing a similar issue with importing the onnx package (which is built using Pybind11).

WARNING: autodoc: failed to import module 'onnx.testing' from module <module-name>; the following exception was raised:
Traceback (most recent call last):
  File "/opt/conda/envs/env-name/lib/python3.9/site-packages/sphinx/ext/autodoc/importer.py", line 65, in import_module
    return importlib.import_module(modname)
  File "/opt/conda/envs/env-name/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)

This error is not present in 7.2.4.

@picnixz
Copy link
Member

picnixz commented Aug 31, 2023

Actually, it's probably something related to how extension modules are imported. It appears that deleting them from sys.modules is not sufficient. By the way, what is the full traceback error? you are missing the exception message (or there is none?)

@jaraco
Copy link

jaraco commented Aug 31, 2023

I've also encountered this issue in jaraco/skeleton#88 and I've produced a minimal reproducer in https://github.com/jaraco/jaraco.mongodb/tree/triage/36-minimal. Run with tox -e docs.

In this case, the offending import is import inflect, which has a dependency on pydantic, which has a rust extension.

@picnixz
Copy link
Member

picnixz commented Aug 31, 2023

Mmh thank you. As I only have my phone for investing this, it will be hard. In the meantime, maybe the issue could be directly related to PyO3? Or, alternatively, for extension modules, maybe we could skip their removal, though they would still be leftovers modules... So I don't really know what to do (except pinning the version prior to 7.1.x. I wouldn't recommend 7.2.x as this is kind of subject to some unstability).

@jaraco
Copy link

jaraco commented Aug 31, 2023

If you're able to bisect, try before/after this commit: 8248be3

I installed the commit prior to that one and the issue remains.

Correction - after ensuring that I'm forcing the reinstall, I have confirmed that commit is implicated:

 jaraco.mongodb triage/36-minimal @ .tox/docs/bin/pip install --force --quiet 'git+https://github.com/sphinx-doc/sphinx@8248be3'
  WARNING: Did not find branch or tag 'e494baa', assuming revision or ref.
 jaraco.mongodb triage/36-minimal @ tox -e docs
.pkg: _optional_hooks> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: get_requires_for_build_editable> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: build_editable> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
docs: install_package> python -I -m pip install --force-reinstall --no-deps /Users/jaraco/code/jaraco/jaraco.mongodb/.tox/.tmp/package/39/jaraco.mongodb-0.0.0-0.editable-py3-none-any.whl
docs: commands[0] /Users/jaraco/code/jaraco/jaraco.mongodb/docs> python -m sphinx -W --keep-going . /Users/jaraco/code/jaraco/jaraco.mongodb/build/html
Running Sphinx v7.2.5+/f90b3be
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
writing output... 
building [html]: targets for 1 source files that are out of date
updating environment: 0 added, 1 changed, 0 removed
reading sources... [100%] index
WARNING: autodoc: failed to import module 'oplog' from module 'jaraco.mongodb'; the following exception was raised:
PyO3 modules may only be initialized once per interpreter process
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... copying static files... done
copying extra files... done
done
writing output... [100%] index
generating indices... genindex done
writing additional pages... search done
dumping search index in English (code: en)... done
dumping object inventory... done
build finished with problems, 1 warning.
docs: exit 1 (0.63 seconds) /Users/jaraco/code/jaraco/jaraco.mongodb/docs> python -m sphinx -W --keep-going . /Users/jaraco/code/jaraco/jaraco.mongodb/build/html pid=49155
.pkg: _exit> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
  docs: FAIL code 1 (1.15=setup[0.52]+cmd[0.63] seconds)
  evaluation failed :( (1.20 seconds)
 jaraco.mongodb triage/36-minimal @ .tox/docs/bin/pip install --quiet --force 'git+https://github.com/sphinx-doc/sphinx@8248be3~1'
  WARNING: Did not find branch or tag '8248be3~1', assuming revision or ref.
 jaraco.mongodb triage/36-minimal @ tox -e docs
.pkg: _optional_hooks> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: get_requires_for_build_editable> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: build_editable> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
docs: install_package> python -I -m pip install --force-reinstall --no-deps /Users/jaraco/code/jaraco/jaraco.mongodb/.tox/.tmp/package/41/jaraco.mongodb-0.0.0-0.editable-py3-none-any.whl
docs: commands[0] /Users/jaraco/code/jaraco/jaraco.mongodb/docs> python -m sphinx -W --keep-going . /Users/jaraco/code/jaraco/jaraco.mongodb/build/html
Running Sphinx v7.2.5+/f90b3be
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
writing output... 
building [html]: targets for 1 source files that are out of date
updating environment: 0 added, 0 changed, 0 removed
reading sources... 
looking for now-outdated files... none found
preparing documents... done
copying assets... copying static files... done
copying extra files... done
done
writing output... [100%] index
generating indices... genindex py-modindex done
writing additional pages... search done
dumping search index in English (code: en)... done
dumping object inventory... done
build succeeded.

The HTML pages are in ../build/html.
.pkg: _exit> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
  docs: OK (1.16=setup[0.54]+cmd[0.62] seconds)
  congratulations :) (1.22 seconds)

@AA-Turner
Copy link
Member

cc @godlygeek as the author of #11645 if you've any ideas re PyO3/extension module unloading in general.

@AA-Turner
Copy link
Member

Another simple reproducer:

import importlib, sys
orig_modules = frozenset(sys.modules)
module = importlib.import_module('pydantic')
for m in [m for m in sys.modules if m not in orig_modules]:
    sys.modules.pop(m)
module = importlib.import_module('pydantic')
# ImportError: PyO3 modules may only be initialized once per interpreter process

@AA-Turner
Copy link
Member

See also https://github.com/PyO3/pyo3/blob/v0.19.2/guide/src/migration.md#each-pymodule-can-now-only-be-initialized-once-per-process

cc: @davidhewitt -- sorry for the ping, but I wondered if you'd have any advice here? The problem is Sphinx tries to import modules twice (with TYPE_CHECKING set to True and then False on failure), but wants to reset sys.modules to the initial state in the case of a failed import, to work around corrupted partial imports (#11645).

Is there an officially blessed way to unload a PyO3 module such that we're able to attempt importing it again? (Other ideas to work around this are very welcome also, I've not had much luck finding discussion on this point).

Thanks,
Adam

@picnixz
Copy link
Member

picnixz commented Sep 1, 2023

One idea I have is to allow people to just disable the new feature (it will be quite hard to disable it per a module basis since they are dependencies to know in advance, so it's better to allow turning it off completely only).

@davidhewitt
Copy link

Thanks for the ping, and sorry this is causing folks pain. This check was introduced as a defense against subinterpreters, which are a very complex thing to support at the native level (sharing arbitrary python objects between subinterpreters is disallowed, but "static" variables at the native level can make it dangerously easy to do so accidentally). Unfortunately the defense as implemented is hitting this edge case when the PyO3 module is removed from sys.modules. I never anticipated this edge case; not even importlib.reload does this!

I'm keen to find ways to resolve this, we also hit the same issue in pydantic/pydantic#6584

Rereading the original suggestion in PyO3/pyo3#2346 (comment) to introduce this check, there was a more sophisticated form of the check:

Or record the current interpreter at startup and check it's the only one used from Rust.

I'll investigate if this is possible to rework the check to do this. Unfortunately it cannot fix PyO3 modules already shipped, but we can release a patch fix asap to get this through the ecosystem.

@davidhewitt
Copy link

For existing modules if you need a workaround for right now - the only options I can think of right now are:

  • move one of your import phases into a subprocess (probably with multiprocessing), which may or may not be feasible depending on exactly what you need to get from the imports.
  • explicitly unload the .so file using dlclose, which I assume will reset the check. This is probably fraught with complexity and edge cases that might be worse than the current crash.

... in hindsight I wish I'd implemented an environment variable UNSAFE_PYO3_ALLOW_RELOAD or similar...

@AA-Turner
Copy link
Member

AA-Turner commented Sep 1, 2023

Thanks for the very fast response David!

To be cheeky, could I ask if there is there a 'blessed' way to introspect if a module is PyO3 generated? It seems the variables mod.build_profile and mod.build_info might be heuristics, but a __PYO3__ = True flag or similar (a la '__pypy__' in sys.modules) might be useful for future cases?

I think for now I might try and skip unloading all extension modules with isinstance(mod.__spec__.loader, importlib.machinery.ExtensionFileLoader), but that has its own trade-offs.

A

@godlygeek
Copy link
Contributor

cc @godlygeek as the author of #11645 if you've any ideas re PyO3/extension module unloading in general.

Well... The other approach I proposed in #11642 (comment) wouldn't have this particular issue, as far as I can see, since

importlib.reload(sys.modules["pydantic_core._pydantic_core"])

doesn't raise an exception. We could change this code to something like:

module = import_module(modname, warningiserror=warningiserror)
try:
    typing.TYPE_CHECKING = True
    with contextlib.suppress(ImportError):
        module = importlib.reload(module)
finally:
    typing.TYPE_CHECKING = False

and it wouldn't have this particular problem, as far as I can see.

But I can't be sure it wouldn't have some other obscure problem... Ultimately, I don't think that Sphinx ought to be setting TYPE_CHECKING = True at all, and rather than having an option to opt out of that behavior, an option to opt into it would likely cause much less breakage... I started investigating this because this broke docs generation for about a half dozen of my projects at work.

@picnixz
Copy link
Member

picnixz commented Sep 1, 2023

As I mentioned, reloading a module might have side-effects (like unpatching things that were set up by extensions I think) so it's maybe not a good idea.

+1 for the option to enable the feature though. Ideally we should have an approach based on static analysis, extending the ModuleAnalyzer but it's a bit hard (I have something like that in my projects but it doesn't work well for some cases...)

@davidhewitt
Copy link

@AA-Turner great question - not something we have directly attempted to support - see PyO3/pyo3#3426, we should definitely think about adding this.

@godlygeek
Copy link
Contributor

like unpatching things that were set up by extensions I think

If we only reload a module that we just imported (that is, we check that it wasn't in sys.modules, we import it, we see that it is in sys.modules, and we reload it) is there still a way that some extension-specific patching could be undone? As long as we don't re-import anything that was already loaded before autodoc started importing stuff, I think we'd be fine as far as patches go, but maybe I'm missing something

@picnixz
Copy link
Member

picnixz commented Sep 1, 2023

Err I think it's ok? To clarify here is what I understood:

  • Module m1 is patched by some sphinx extension.

  • We import m1 to document it -> already cached (because to patch it we need to import it) -> not good to reload it.

  • Module m2 not patched and never imported.

  • We import it. We change the TYPE_CHECKING. We reload it. It fails. But what about modules that were imported before failing? do we discard them as you did?

Sorry I am a bit too tired now to think of more on this issue so I'll just think about it tomorrow with a reposed mind.

@godlygeek
Copy link
Contributor

Yes, this is what I mean. I think it ought to be... Well, reasonably safe. I think importing the module twice would actually solve most circular imports. And I think it's probably fine-ish, since we don't want to actually call any functions from the module. But there may be edge cases I'm not seeing...

jasujm added a commit to jasujm/hrefs that referenced this issue Sep 1, 2023
I'm seeing the "PyO3 modules may only be initialized once per interpreter
process" errors when creating sphinx documentation. That may be related to more
recent sphinx versions, re: sphinx-doc/sphinx#11662

Downgrade sphinx to an earlier version
@inducer
Copy link

inducer commented Sep 7, 2023

For what it's worth, here's another example of opaque breakage brought on by the attempted double import: https://github.com/inducer/grudge/actions/runs/6062181047/job/16460837326. (Again, builds fine with 7.2.4, breaks with 7.2.5.) We have not yet figured out in detail how the double import breaks things, but... we kind of shouldn't have to?

I do not think #11645 can ever be made to work reliably.

@dolfinus
Copy link

dolfinus commented Sep 7, 2023

Just add an option for default value of typing.TYPE_CHECKING, let user to decide.

@Luthaf
Copy link

Luthaf commented Sep 7, 2023

I have something that looks similar to this as well in https://github.com/lab-cosmo/metatensor. The docs builds fine with sphinx 7.2.4, and fails with 7.2.5 with a numpy error that looks related to typechecking: numpy.core._exceptions._UFuncNoLoopError: ufunc 'equal' did not contain a loop with signature matching types (<class 'numpy.dtypes.Float64DType'>, <class 'numpy.dtypes.StrDType'>) -> None (full error below).

sphinx-build -E -W -b html docs/src docs/build/html
Sphinx v7.2.5 en cours d'exécution
<metatensor>/.tox/docs/lib/python3.11/site-packages/breathe/project.py:116: RemovedInSphinx80Warning: Sphinx 8 will drop support for representing paths as strings. Use "pathlib.Path" or "os.fspath" instead.
  self._default_build_dir = os.path.dirname(app.doctreedir.rstrip(os.sep))
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
chargement de l'inventaire intersphinx de https://docs.python.org/3/objects.inv...
chargement de l'inventaire intersphinx de https://numpy.org/doc/stable/objects.inv...
chargement de l'inventaire intersphinx de https://pytorch.org/docs/stable/objects.inv...
Construction en cours [mo] : cibles périmées pour les fichiers po 0
Écriture...
Construction [html] : cibles périmées pour les fichiers sources 77
Mise à jour de l'environnement : [nouvelle configuration] 77 ajouté(s), 0 modifié(s), 0 supprimé(s)
Lecture des sources... [100%] reference/torch/tensor

Warning, treated as error:
autodoc: failed to import function 'checks_enabled' from module 'metatensor'; the following exception was raised:
Traceback (most recent call last):
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/sphinx/ext/autodoc/importer.py", line 65, in import_module
    return importlib.import_module(modname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<miniforge3>/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/__init__.py", line 28, in <module>
    from . import core  # noqa
    ^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/__init__.py", line 2, in <module>
    from .block import TensorBlock
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/block.py", line 6, in <module>
    from ._c_lib import _get_library
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/_c_lib.py", line 8, in <module>
    from .data.extract import ExternalCpuArray, register_external_data_wrapper
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/data/__init__.py", line 1, in <module>
    from .array import ArrayWrapper  # noqa
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/data/array.py", line 3, in <module>
    import numpy as np
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/__init__.py", line 144, in <module>
    from . import lib
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/lib/__init__.py", line 25, in <module>
    from . import index_tricks
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/lib/index_tricks.py", line 12, in <module>
    import numpy.matrixlib as matrixlib
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/matrixlib/__init__.py", line 4, in <module>
    from . import defmatrix
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/matrixlib/defmatrix.py", line 12, in <module>
    from numpy.linalg import matrix_power
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/linalg/__init__.py", line 73, in <module>
    from . import linalg
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/linalg/linalg.py", line 37, in <module>
    from numpy._typing import NDArray
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/_typing/__init__.py", line 207, in <module>
    from ._ufunc import (
ModuleNotFoundError: No module named 'numpy._typing._ufunc'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/sphinx/ext/autodoc/importer.py", line 90, in import_object
    module = import_module(modname, warningiserror=warningiserror)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/sphinx/ext/autodoc/importer.py", line 69, in import_module
    raise ImportError(exc, traceback.format_exc()) from exc
ImportError: (ModuleNotFoundError("No module named 'numpy._typing._ufunc'"), 'Traceback (most recent call last):\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/sphinx/ext/autodoc/importer.py", line 65, in import_module\n    return importlib.import_module(modname)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "<miniforge3>/lib/python3.11/importlib/__init__.py", line 126, in import_module\n    return _bootstrap._gcd_import(name[level:], package, level)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import\n  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load\n  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked\n  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked\n  File "<frozen importlib._bootstrap_external>", line 940, in exec_module\n  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/__init__.py", line 28, in <module>\n    from . import core  # noqa\n    ^^^^^^^^^^^^^^^^^^\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/__init__.py", line 2, in <module>\n    from .block import TensorBlock\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/block.py", line 6, in <module>\n    from ._c_lib import _get_library\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/_c_lib.py", line 8, in <module>\n    from .data.extract import ExternalCpuArray, register_external_data_wrapper\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/data/__init__.py", line 1, in <module>\n    from .array import ArrayWrapper  # noqa\n    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/data/array.py", line 3, in <module>\n    import numpy as np\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/__init__.py", line 144, in <module>\n    from . import lib\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/lib/__init__.py", line 25, in <module>\n    from . import index_tricks\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/lib/index_tricks.py", line 12, in <module>\n    import numpy.matrixlib as matrixlib\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/matrixlib/__init__.py", line 4, in <module>\n    from . import defmatrix\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/matrixlib/defmatrix.py", line 12, in <module>\n    from numpy.linalg import matrix_power\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/linalg/__init__.py", line 73, in <module>\n    from . import linalg\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/linalg/linalg.py", line 37, in <module>\n    from numpy._typing import NDArray\n  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/_typing/__init__.py", line 207, in <module>\n    from ._ufunc import (\nModuleNotFoundError: No module named \'numpy._typing._ufunc\'\n')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/sphinx/ext/autodoc/importer.py", line 65, in import_module
    return importlib.import_module(modname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<miniforge3>/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/__init__.py", line 28, in <module>
    from . import core  # noqa
    ^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/__init__.py", line 2, in <module>
    from .block import TensorBlock
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/block.py", line 6, in <module>
    from ._c_lib import _get_library
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/_c_lib.py", line 8, in <module>
    from .data.extract import ExternalCpuArray, register_external_data_wrapper
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/data/__init__.py", line 1, in <module>
    from .array import ArrayWrapper  # noqa
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/metatensor/core/data/array.py", line 3, in <module>
    import numpy as np
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/__init__.py", line 387, in <module>
    _mac_os_check()
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/__init__.py", line 381, in _mac_os_check
    _ = polyfit(x, y, 2, cov=True)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/lib/polynomial.py", line 669, in polyfit
    c, resids, rank, s = lstsq(lhs, rhs, rcond)
                         ^^^^^^^^^^^^^^^^^^^^^^
  File "<metatensor>/.tox/docs/lib/python3.11/site-packages/numpy/linalg/linalg.py", line 2303, in lstsq
    if rcond == "warn":
       ^^^^^^^^^^^^^^^
numpy.core._exceptions._UFuncNoLoopError: ufunc 'equal' did not contain a loop with signature matching types (<class 'numpy.dtypes.Float64DType'>, <class 'numpy.dtypes.StrDType'>) -> None

EDIT: we are only seeing this because we build docs with sphinx-build -W, other people might be affected but ignoring the warning.

@godlygeek
Copy link
Contributor

godlygeek commented Sep 7, 2023

Well, #11679 is an implementation of my second idea for how to make this work, leveraging importlib.reload().

I'm not convinced that any of this is a good idea, and I think that leaving typing.TYPE_CHECKING alone is a better idea - or at the very least, making it opt-in for Sphinx to mess with it. TYPE_CHECKING is explicitly meant for static analysis, and setting it at runtime seems to just be asking for trouble.

But, this new PR does seem to work in more cases than #11645 did. It handles pydantic, at least (I tested that locally, rather than introducing a new test dependency on pydantic). It also passes the tests we added for #11645. I think it's also more likely to leave things in a valid state if the reload fails, by virtue of loading with TYPE_CHECKING == False first, so that there's something valid already in sys.modules that we can leverage if reloading with TYPE_CHECKING == True fails.

@godlygeek
Copy link
Contributor

@inducer Would you be able to test #11679 with grudge by any chance? I tried to reproduce with pip install grudge and .. automodule:: grudge but didn't get a failure even with Sphinx 7.2.5

@matthiasdiener
Copy link

@inducer Would you be able to test #11679 with grudge by any chance? I tried to reproduce with pip install grudge and .. automodule:: grudge but didn't get a failure even with Sphinx 7.2.5

The grudge doc build still fails with #11679, with a new error message:

WARNING: autodoc: failed to import function 'op.elementwise_min' from module 'grudge'; the following exception was raised:
Traceback (most recent call last):
  File "/Users/mdiener/Work/emirge/sphinx/sphinx/ext/autodoc/importer.py", line 66, in import_module
    return importlib.import_module(modname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mdiener/Work/emirge/miniforge3/envs/ceesd/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/mdiener/Work/emirge/grudge/grudge/op.py", line 115, in <module>
    from grudge.trace_pair import (
  File "/Users/mdiener/Work/emirge/grudge/grudge/trace_pair.py", line 95, in <module>
    @dataclass_array_container
     ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mdiener/Work/emirge/arraycontext/arraycontext/container/dataclass.py", line 117, in dataclass_array_container
    raise ValueError(f"'{cls}' must have fields with array container type "
ValueError: '<class 'grudge.trace_pair.TracePair'>' must have fields with array container type in order to use the 'dataclass_array_container' decorator

@godlygeek
Copy link
Contributor

godlygeek commented Sep 7, 2023

And it works with Sphinx 7.2.4? That's weird. Hm. Can you give me a recipe I can follow to reproduce that failure myself?

I tried cloning https://github.com/inducer/grudge.git and doing a pip install path/to/clone, but things failed for me entirely differently than for you:

WARNING: autodoc: failed to import module 'grudge'; the following exception was raised:
cannot import name 'BoundaryTag' from 'meshmode.mesh' (/home/godlygeek/3ps/sphinx/venv/lib/python3.11/site-packages/meshmode/mesh/__init__.py)

That's with meshmode 2021.2, and grudge's requirement is meshmode>=2020.2, so it ought to be compatible...

@matthiasdiener
Copy link

matthiasdiener commented Sep 7, 2023

And it works with Sphinx 7.2.4? That's weird. Hm. Can you give me a recipe I can follow to reproduce that failure myself?

Yes, it works with sphinx 7.2.4. I think the easiest way to install grudge and the correct dependencies might be via emirge:

git clone https://github.com/illinois-ceesd/emirge
cd emirge
./install.sh
source config/activate_env.sh
pip install matplotlib
cd grudge/doc
make html

@picnixz
Copy link
Member

picnixz commented Sep 7, 2023

Concerning the reason why we had this bug, I suggested the following: #11652 (comment)

@godlygeek
Copy link
Contributor

Well, it looks like the issue with grudge is that it's using matplotlib.sphinxext.plot_directive, which does execute code from the module being documented. It does so after we've reloaded those modules, and some of the modules in this program don't take kindly to being reloaded.

One of my assumptions with the "use importlib.reload" idea was that Sphinx wasn't going to actually try to call functions from the module, but matplotlib.sphinxext.plot_directive does.

So... I'm not sure I have any good ideas at this point. I'm not seeing any reasonable way to import modules with typing.TYPE_CHECKING set that isn't going to badly break some things.

For the sake of anyone stumbling upon this issue, it might be helpful to document that there's a reasonable workaround for both the problems caused by 7.2.0 and the different problems caused by 7.2.5: if you import the modules that you're trying to document yourself from inside your conf.py file, neither the 7.2.0 version of the TYPE_CHECKING reimport nor the 7.2.5 version will trigger. The already-loaded module will just get picked up from sys.modules with no additional magic.

@davidhewitt
Copy link

Posted PyO3/pyo3#3446 - I'll try to ship this within a week and update Pydantic to use.

Julian added a commit to python-jsonschema/jsonschema that referenced this issue Sep 11, 2023
See sphinx-doc/sphinx#11662 which seems like it will be
resolved with an upstream PyO3 release.

May have to come back and change this pin to be an upper
pin if need be / the upstream issue doesn't merge soon.
@picnixz
Copy link
Member

picnixz commented Sep 21, 2023

@pbybee With #11679 merged (7.2.6), I think your issue should not persist anymore. Could you confirm?

@Luthaf
Copy link

Luthaf commented Sep 21, 2023

7.2.6 does fix the issue in my case (#11662 (comment)). Thanks a lot to all contributors!

@pbybee
Copy link
Author

pbybee commented Sep 22, 2023

Confirmed fixed in 7.2.6. Thanks for the hard work on this!

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests