When I do import mypackage.mymodule, something in the gargantuan import tree imports a certain third-party package that I do not want to be imported unless I explicitly call a function that depends on it. How can I find out what module is importing the third-party package and what module is importing that module all the way up to mypackage.mymodule?
Most of the modules involved are third-party dependencies outside of my control, so I can’t edit them.
The only thing close to a solution I’ve found is to run python -v -c 'import mypackage.mymodule', but the output from the -v switch is borderline garbage.
That might work if the import “depth” was indicated in the output, but it’s not. For example, the first occurrence of import 'pandas.... in the output comes right after import 're._compiler'. I’m assuming that re._compiler is at the end of an “import branch” and that pandas is actually being imported by something higher up, but I can’t tell what.
I ended up doing sys.modules["package"] = None for most of the problem imports, but I had to resort to editing numpy/__init__.py to add a raise RuntimeError in order to catch one library that was doing try: import numpy; except ImportError: ....
Getting much deeper into the import system, we can install a hook into sys.meta_path (which controls the ways that Python searches for modules and loads them, rather than simply the places it looks for absolute imports), like so:
# This is a *real* hack; you're meant to define a class following
# the API laid out in the `importlib.machinery` documentation.
# (It's not really clear to me whether you're meant to use the class
# itself with a `@classmethod` or `@staticmethod`, or instantiate
# it and put an instance in `sys.meta_path`...)
from types import SimpleNamespace
def reject_numpy(fullname, path, target=None):
if fullname == 'numpy':
# This way, we can customize the exception
# so that it can be something that isn't caught by the library.
raise RuntimeError('Importing NumPy is disallowed.')
# by default, this falls through and returns `None`, which in turn
# lets the other module-loaders on `sys.meta_path` have a go at it.
# Adapt it to the loader protocol and put it before the defaults.
(This still needs to happen before the attempt to do the normal imports.)