Search code examples
pythonpython-3.xpython-importcompatibility

Reroute Python Module To Another Directory (Sub-version) on import


mypkg/
├── mypkg-1_0_0/
│   ├── __init__.py
├── __init__.py

The above directory is an example of what my modules directory looks like. The master module mypkg contains all the sub-versions. When I do import mypkg the __init__ file is meant to redirect the import to the correct version while running the target versions __init__ file and also setting the attributes to globals()

Here is what the __init__.py in mypkg looks like:

def __load():
    import os

    pkgrelease = "mypkg-1_0_0"

    if pkgrelease:
        current_path = __path__[0]

        new_path = os.path.join( current_path, pkgrelease )
        init_path = os.path.join( new_path, "__init__.py" )

        __path__[:] = [new_path]

        initf = open( init_path )
        exec compile( initf.read(), init_path, "exec" ) in globals()
        initf.close()

    else:
        raise ImportError( "Could not find [%s] package version" % os.path.basename(__path__) )

__load()

del __load

In python2 this works fine, the package gets redirected to version 1_0_0, and stuff like __version__ in that versions __init__ file, are retained and set in the globals(). But I also know there is a much better way to do what I'm trying to achieve here that is cross-compatible. This will not work in python3 due the exec compile statement.


Solution

  • I found one solution that works on both py2/3. Simply just need to tweak the exec function to take in the globals() an argument. This is shown here in the docs. Here is the revised code:

    def load():
        import os
    
        pkgrelease = "mypkg-1_0_0"
    
        current_path = __path__[0]
    
        new_path = os.path.join( current_path, pkgrelease )
        init_path = os.path.join( new_path, "__init__.py" )
    
        __path__[:] = [new_path]
        initf = open(init_path)
        module = compile(initf.read(), init_path, "exec")
        exec(module, globals())
        initf.close()
    
    
    load()
    
    del load