PEP 302 -- New Import Hooks specifies ways to hook the import mechanism of Python. One of them is to create a module finder/loader and add it to sys.meta_path
.
I seek to create a module finder that is capable of re-routing the import of sub-packages. So let's say if I write import mypackage.sub
it should actually import the module mypackage_sub
.
mypackage.sub => mypackage_sub
mypackage.sub.another => mypackage_sub.another
mypackage.sub.another.yikes => mypackage_sub.another.yikes
My attempts to implement such a module finder where not successful. For now, I install the finder class in the main Python file, but my aim is to install it in the root package mypackage
.
The finder will receive a call to .find_module(fullname, path)
with 'mypackage'
, but never with 'mypackage.sub'
. Therefore, I just get the following error for the code below.
Traceback (most recent call last):
File "test.py", line 63, in <module>
import mypackage.sub
ImportError: No module named sub
import sys
sys.meta_path.append(_ExtensionFinder('mypackage'))
import mypackage.sub
Here's the code for the _ExtensionFinder
class. I appreciate any solution that would allow me to put this _ExtensionFinder
class into the root module mypackage
.
class _ExtensionFinder(object):
"""
This class implements finding extension modules being imported by
a prefix. The dots in the prefix are converted to underscores and
will then be imported.
.. seealso:: PEP 302 -- New Import Hooks
"""
def __init__(self, prefix):
super(_ExtensionFinder, self).__init__()
self.prefix = prefix
def _transform_name(self, fullname):
if self.prefix == fullname:
return fullname
elif fullname.startswith(self.prefix + '.'):
newpart = fullname[len(self.prefix) + 1:]
newname = self.prefix.replace('.', '_') + '_' + newpart
return newname
return None
def find_module(self, fullname, path=None):
print "> find_module({0!r}, {1!r})".format(fullname, path)
newname = self._transform_name(fullname)
if newname:
return self
def load_module(self, fullname):
print "> load_module({0!r})".format(fullname)
# If there is an existing module object named 'fullname'
# in sys.modules, the loader must use that existing module.
if fullname in sys.modules:
return sys.modules[fullname]
newname = self._transform_name(fullname)
if newname in sys.modules:
sys.modules[fullname] = sys.modules[newname]
return sys.modules[newname]
# Find and load the module.
data = imp.find_module(newname)
mod = imp.load_module(newname, *data)
# The __loader__ attribute must be set to the loader object.
mod.__loader__ = self
# The returned module must have a __name__, __file__
# and __package__ attribute.
assert all(hasattr(mod, n) for n in ['__name__', '__file__', '__package__'])
return mod
Edit: I found out that the right words to describe what I want to achieve is "namespace package"