CHANGELOG: Document HTML syntax fix
[rss2email.git] / rss2email / util.py
index f707dcb84323039f1c90daeb26365a34246e4681..dc087d095e7f1e4330e8c95a60de5244deb4de34 100644 (file)
@@ -17,6 +17,9 @@
 """Odds and ends
 """
 
+import importlib as _importlib
+import pickle as _pickle
+import pickletools as _pickletools
 import sys as _sys
 import threading as _threading
 
@@ -74,3 +77,65 @@ class TimeLimitedFunction (_threading.Thread):
         elif self.isAlive():
             raise _error.TimeoutError(time_limited_function=self)
         return self.result
+
+
+def import_name(obj):
+    """Return the full import name for a Python object
+
+    Note that this does not always exist (e.g. for dynamically
+    generated functions).  This function does it's best, using Pickle
+    for the heavy lifting.  For example:
+
+    >>> import_name(import_name)
+    'rss2email.util import_name'
+
+    Note the space between the module (``rss2email.util``) and the
+    function within the module (``import_name``).
+
+    Some objects can't be pickled:
+
+    >>> import_name(lambda x: 'muahaha')
+    Traceback (most recent call last):
+      ...
+    _pickle.PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
+
+    Some objects don't have a global scope:
+
+    >>> import_name('abc')
+    Traceback (most recent call last):
+      ...
+    ValueError: abc
+    """
+    pickle = _pickle.dumps(obj)
+    for opcode,arg,pos in _pickletools.genops(pickle):
+        if opcode.name == 'GLOBAL':
+            return arg
+    raise ValueError(obj)
+
+def import_function(name):
+    """Import a function using the full import name
+
+    >>> import_function('rss2email.util import_function')  # doctest: +ELLIPSIS
+    <function import_function at 0x...>
+    >>> import_function(import_name(import_function))  # doctest: +ELLIPSIS
+    <function import_function at 0x...>
+
+    >>> import_function('rss2email.util does_not_exist')
+    Traceback (most recent call last):
+      ...
+    AttributeError: 'module' object has no attribute 'does_not_exist'
+    >>> import_function('rss2email.util has invalid syntax')
+    Traceback (most recent call last):
+      ...
+    AttributeError: 'module' object has no attribute 'has invalid syntax'
+    >>> import_function('rss2email.util.no_space')
+    Traceback (most recent call last):
+      ...
+    ValueError: rss2email.util.no_space
+    """
+    try:
+        module_name,function_name = name.split(' ', 1)
+    except ValueError as e:
+        raise ValueError(name) from e
+    module = _importlib.import_module(module_name)
+    return getattr(module, function_name)