Skip to content

Instantly share code, notes, and snippets.

@rijulg
Created June 17, 2020 14:51
Show Gist options
  • Save rijulg/3ea372bef35adb68e27080c949c942af to your computer and use it in GitHub Desktop.
Save rijulg/3ea372bef35adb68e27080c949c942af to your computer and use it in GitHub Desktop.
Packagify - for loading python projects not maintained as packages like packages
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}")
@rijulg
Copy link
Author

rijulg commented Jul 18, 2020

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/)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment