Created
June 17, 2020 14:51
-
-
Save rijulg/3ea372bef35adb68e27080c949c942af to your computer and use it in GitHub Desktop.
Packagify - for loading python projects not maintained as packages like packages
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import builtins | |
import sys | |
class Packagify: | |
""" | |
Used to load python projects that aren't suitable to be used as packages | |
You can use this class as following: | |
``` | |
from packagify import Packagify | |
package = Packagify("/home/workspace/my_package") | |
object = package.import_module("module", ["object"]) | |
object1, object2 = package.import_module("module", ["object1", "object2"]) | |
``` | |
This will allow you to import modules and objects from my_package as and where it exists. | |
How this works: | |
1. This class overrides the import functionality of python while importing the module. | |
1.1. When the package tries to import certain modules from it's directory assuming | |
the script ran from there, we change the level of import from absolute to relative. | |
1.2. If a module adds a system path (using sys.path.append) we change the path to reflect | |
the location of the module relative to the location from where we are loading the entire | |
pacakage. | |
2. After importing we revert back the functions to originals so that rest of the importing can work as is | |
""" | |
def __init__(self, location): | |
self.location = location | |
parts = location.rsplit('/', 1) | |
sys.path.append(parts[0]) | |
self.__package = __import__(parts[1]) | |
sys.path.remove(parts[0]) | |
self.__save_originals() | |
def import_module(self, module, from_list = []): | |
self.__hijack() | |
locs = locals() | |
locs[self.__package.__name__] = self.__package | |
name = f"{self.__package.__name__}.{module}" | |
tmp = __import__(name, locals=locs, fromlist=from_list) | |
self.__unhijack() | |
if from_list: | |
modules = () | |
for fl in from_list: | |
modules += (getattr(tmp, fl), ) | |
if len(modules) > 1: | |
return modules | |
else: | |
return modules[0] | |
return tmp | |
def __save_originals(self): | |
self.original_import = builtins.__import__ | |
self.original_syspath = sys.path | |
def __hijack(self): | |
builtins.__import__ = self.__import__ | |
sys.path = self.SysPath(sys.path, self.location) | |
def __unhijack(self): | |
builtins.__import__ = self.original_import | |
sys.path = self.original_syspath | |
def __import__(self, name, globals=None, locals=None, fromlist=None, level=None): | |
params = { 'level': 0 } | |
if globals is not None: | |
params['globals'] = globals | |
if locals is not None: | |
params['locals'] = locals | |
if fromlist is not None: | |
params['fromlist'] = fromlist | |
if level is not None: | |
params['level'] = level | |
try: | |
module = self.original_import(name, **params) | |
except: | |
if name and locals and '__package__' in locals and '__file__' in locals and name in locals['__package__'] and self.__package.__name__ in locals['__file__']: | |
locals['__package__'] = locals['__package__'].replace(f'.{name}', '') | |
params['level'] += 1 | |
module = self.original_import(name, **params) | |
return module | |
class SysPath(list): | |
def __init__(self, args, location): | |
list.__init__(self, args) | |
self.location = location | |
def append(self, item): | |
if item[0] == '/': | |
list.append(self, item) | |
else: | |
list.append(self, f"{self.location}/{item}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
As I started using this much more in my work I published this on PyPi so as to not have to copy the file everywhere. You can get this through PyPi now with
pip install packagify
(https://pypi.org/project/packagify/1.0/)