Hi,

there’s a similar issue #63912 and a thread on the guix-devel
mailinglist at
https://lists.gnu.org/archive/html/guix-devel/2023-06/msg00066.html

> So, looks like the default Python behavior is to load the usercustomize
> after the sitecustomize [1], which leads to exactly the behavior you're
> experiencing.  I don't know what we should do here, maybe pass `-s` to
> the shebang line of Python to disable loading the usercustomize?  That
> would probably be a world-rebuild though.  CC'ing the Python team to see
> what they think.

I think the problem is bigger than usercustomize. Any custom PYTHONPATH
also slips through and causes this issue, as well as any custom
GUIX_PYTHONPATH, because the executable wrapper appends it (think nested
`guix shell` invokations with different versions of a library for
an example where this could go wrong).

Guix-managed Python packages (libraries nor applications) should
generally not pick up dependencies from random paths – only those
from their package description, so we can keep Guix’ promise of being
self-contained.

I have experimented with customizing Python’s importing mechanism
through a custom MetaPathFinder. It works by adding a __guix_pythonpath__
variable to every Python package’s __init__.py file and modifying the
module loader’s search path accordingly if such a variable exists. It
would provide exactly that guarantee, but it’s just a PoC at this
point – see attached file.

Apart from that I don’t see a good short-term solution right now. It’s
just how Python works.

Cheers,
Lars

# Credits to
# 
https://tenthousandmeters.com/blog/python-behind-the-scenes-11-how-the-python-import-system-works/
# for the very good explanation of how Python’s import statement works.

import sys, os
from importlib.abc import MetaPathFinder
from importlib.machinery import SourceFileLoader, ModuleSpec, PathFinder

class GuixPythonFinder (MetaPathFinder):
    def find_spec (self, fullname, path, target=None):
        # Short-circuit for non-top-level imports, which already have a path.
        if path:
            return None

        attrname = '__guix_pythonpath__'
        searchPath = None
        # Search for our caller.
        frame = sys._getframe ()
        while frame:
            # If he has a search path, use it. This is mainly for executable
            # scripts with `__name__ == '__main__'`.
            searchPath = frame.f_globals.get (attrname, None)
            if not searchPath:
                # Otherwise check the top-level package for search paths
                # declared in __init__.py
                package = frame.f_globals.get ('__package__')
                if package:
                    module = sys.modules.get (package)
                    if module:
                        searchPath = getattr (module, attrname, None)
            if searchPath is not None:
                break
            frame = frame.f_back

        # If we have a caller…
        if searchPath is not None:
            return PathFinder.find_spec (fullname, searchPath, target=target)
        else:
            # Otherwise we’re not responsible for this module.
            return None

sys.meta_path.insert (0, GuixPythonFinder ())

Reply via email to