Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion petsctools/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import functools
import itertools
import numbers
import sys
import types
import warnings
import weakref
Expand All @@ -20,9 +21,40 @@
PetscToolsWarning,
)


def _import_petsc_without_initialising():
"""Import ``petsc4py.PETSc`` without initialising PETSc.

Returns
-------
types.ModuleType
The ``petsc4py.PETSc`` extension module.

Notes
-----
This is the dance `petsctools.init` does, except that it puts the import
machinery back as it found it. `petsc4py.lib.ImportPETSc` registers the
extension module as ``petsc4py.PETSc``, which is the name of the shim it
shadows, and that shim is the only thing that ever calls
``PETSc._initialize``. Left registered, it stops the shim running for the
rest of the process, so a later ``from petsc4py import PETSc`` hands back
a PETSc that nothing has initialised, and the first call into it
dereferences a null pointer.

"""
in_modules = "petsc4py.PETSc" in sys.modules
is_attribute = hasattr(petsc4py, "PETSc")
PETSc = petsc4py.lib.ImportPETSc()
if not in_modules:
del sys.modules["petsc4py.PETSc"]
if not is_attribute:
delattr(petsc4py, "PETSc")
return PETSc


# Do this instead of 'from petsc4py import PETSc' to make sure we don't import
# (and hence initialise) PETSc.
PETSc = petsc4py.lib.ImportPETSc()
PETSc = _import_petsc_without_initialising()

_commandline_options = None

Expand Down
20 changes: 20 additions & 0 deletions tests/test_options.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import subprocess
import sys
import warnings

import pytest
Expand Down Expand Up @@ -376,3 +378,21 @@ def test_inserted_options_dict():
with petsctools.inserted_options(parameters=params, options_prefix=prefix):
assert PETSc.Options().getInt("prefix_opt_int") == 3
assert PETSc.Options().getBool("prefix_opt_flag")


@pytest.mark.skipnopetsc4py
def test_importing_petsctools_leaves_petsc_initialisable():
"""Importing petsctools must not stop the petsc4py shim from running.

The shim is the only thing that calls ``PETSc._initialize``, so for any
code that does not call `petsctools.init` itself it is what initialises
PETSc. Run in a subprocess, since PETSc is already initialised here.
"""
script = (
"import sys\n"
"import petsctools\n"
"assert 'petsc4py.PETSc' not in sys.modules\n"
"from petsc4py import PETSc\n"
"assert PETSc.Sys.isInitialized()\n"
)
subprocess.run([sys.executable, "-c", script], check=True)
Loading