rootpkg.oldpkg.subpkg.mod used to contain ClsInMod, but we decide
rootpkg.oldpkg needs to be moved to
newpkg. Can this be done without prior users needing to change their code when they upgrade?
I tried a variety of different things, but I finally arrived at this:
# # (C)2020 Dustin Spicuzza, MIT license # # To redirect your own modules: # # * Copy this file to your 'old' package as `__init__.py` # * Change 'import newpkg' to 'import my_new_pkg as newpkg' # * Everything else should 'just work' # import newpkg import importlib import importlib.util import sys import warnings class LoaderWrapper: def __init__(self, name): self.name = name # Use deprecated load_module precisely because we *don't* want to # set the import-related attributes properly, as we're just passing # the other module through without modification # # TODO: file a bug to get this undeprecated or find a better way def load_module(self, fullname): sys.modules[fullname] = importlib.import_module(self.name) class PackageFinder: oldpkg = __name__ + "." newpkg = newpkg.__name__ + "." @classmethod def find_spec(cls, fullname, path, target=None): if fullname.startswith(cls.oldpkg): newfullname = cls.newpkg + fullname[len(cls.oldpkg) :] loader = LoaderWrapper(newfullname) return importlib.util.spec_from_loader(fullname, loader) warnings.warn( __name__ + " is deprecated, use " + newpkg.__name__ + " instead", DeprecationWarning ) sys.meta_path.insert(0, PackageFinder) sys.modules[__name__] = newpkg del newpkg
It’s pretty neat, and I’ve put a working demonstration of my idea here if you want to play with it: https://github.com/virtuald/transparent-pypkg-redir
The one thing that’s a bit troubling is that the
load_module method of an importlib loader is deprecated, so eventually my implementation will no longer work. It says that should instead use
exec_module to implement it. However, by using those methods, that causes the module’s
__path__ to be modified, which causes the module’s name to change depending on the order you import things – eg, exactly what I don’t want. I want the new package and the old package to be identical.
Is there another way to accomplish this without using the deprecated