Last active
December 12, 2015 09:39
-
-
Save goodwillcoding/4753404 to your computer and use it in GitHub Desktop.
This file contains 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
# -*- coding: utf-8 -*- | |
""" | |
""" | |
from importlib import import_module | |
from inspect import stack | |
from inspect import ismethod | |
from os.path import dirname | |
from os.path import split | |
# --------------------------------------------------------------------------- # | |
def get_inst_func(module_path, method_path): | |
""" | |
Given a module path and path to an instance metho retrieve the function | |
corresponding to the instance method. The returned function is not bound | |
to the class and as such and "self" can be passed in. | |
Relative imports are supported. | |
:param module_path: path to the module. (i.e. foo.bar or ..foo.bar) | |
:type module_path: str/unicode | |
:param method_path: path to the instance method inside the module | |
:type module_path: str/unicode | |
:return: unbound function (not unbound method) | |
:rtype: function | |
Example: | |
Lets say in 'project/my_module.py' you have: | |
class FooClass(object): | |
def bar_method(self): | |
pass | |
Calling get_inst_func will get you this: | |
>>> print get_inst_func('project.my_file', 'FooClass.bar_method') | |
<function bar_method at 0xbadbeef> | |
""" | |
absolute_package = None | |
error_message = None | |
# get information about the caller | |
caller_frame = stack()[1] | |
caller_frame_file = caller_frame[1] | |
# check if this is a relative path, and if so traverse up the the last | |
# frames file name and compute the the full name package for that | |
# called import_func | |
last_path = dirname(caller_frame_file) | |
caller_package_parts = [] | |
for part in module_path.split('.'): | |
if len(part) == 0: | |
last_path, module_part = split(last_path) | |
caller_package_parts.insert(0, module_part) | |
else: | |
break | |
# this should get us the caller package path | |
if len(caller_package_parts) > 0: | |
absolute_package = '.'.join(caller_package_parts) | |
# import the module | |
subject = import_module(module_path, package=absolute_package) | |
# now lets handle the function import bit | |
func_parts = method_path.split('.') | |
# iterate over all the part of the method path, "recursively" retrieving | |
# its next part. if any part is missing, compile the error message | |
# with proper path to be raised later | |
for part_idx in range(len(func_parts)): | |
part = func_parts[part_idx] | |
if hasattr(subject, part): | |
subject = getattr(subject, part) | |
else: | |
if absolute_package is not None: | |
# if this is a relative package, use the given module_path | |
# and current subject path | |
part_path = "%s.%s" % (module_path, subject.__name__) | |
elif part_idx == 0: | |
# if this the first element the part path is the module name | |
part_path = subject.__name__ | |
else: | |
# otherwise use the absolute computed path | |
part_path = '%s.%s' % (subject.__module__, subject.__name__) | |
error_message = "cannot import '%s' from '%s'" % (part, part_path) | |
break | |
# now lets see if subject we got is a method and if its not we compile an | |
# error message to be raised later | |
if error_message is None and ismethod(subject) is False: | |
bad_method_name = '%s.%s' % (module_path, method_path) | |
error_message = "'%s' is not an instance method" % bad_method_name | |
# clean up the reference to frame stack | |
del caller_frame | |
# raise any error that happened | |
if error_message is not None: | |
raise ImportError(error_message) | |
return subject.im_func |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment