Searching for import location
<p>I'm trying to write a Python script/function to help determine where to import
something from.  I want this functionality specifically for PyQt4 but it could
be extended and useful for the Python standard library.</p>

<p>To accomplish this I need to dynamically search modules, shared objects,
classes, and possibly more 'types' of things for a given search term.</p>

<p>I want to pass a module/class/etc. as a string, import it dynamically and then
search it.</p>

<p>The interface of the function would be something like this:</p>

<pre><code>search(object_to_search, search_term)
</code></pre>

<p><strong>Examples:</strong></p>

<pre><code>search('datetime', 'today') -&gt; 'datetime.datetime.today'

search('PyQt4', 'NonModal') -&gt; 'PyQt4.QtCore.Qt.NonModal'

search('PyQt4', 'QIcon') -&gt; 'PyQt4.QtGui.QIcon'
</code></pre>

<p>I suppose this could also support wildcards like '*' to search all of
<code>sys.path</code> however this is beyond the scope of what I'm concered about at this
time.</p>

<p>This type of script would be especially useful for PyQt4.  There are a lot of
'enum-like' things like <code>NonModal</code> above and finding the location to import
or reference them from can be cumbersome.</p>

<p>I tend to use the C++ Qt documentation quite a bit for PyQt4 information since
it's typically easy to translate the examples into Python.  The C++ Qt
documentation is more comprehensive.  It's also much easier to find sample code
for Qt proper.  However, the import locations of some of these 'enum-like'
attributes are hard to find in PyQt4 without manually searching the docs or
guessing.  The goal is to automate this process and just have the script tell
me the location of the thing I'm looking for.</p>

<p>I have tried a few different ways of implementing this including just searching
the object recursively with <code>dir()</code>, using the <code>inspect</code> module, and a hacky
version of import/discovery for shared objects.  Below is my various
implementations, non of which really work quite right.</p>

<p>Is this type of thing even possible?  If so, what is the best way to do this?
Also, I've tried to trim down the number of things I search by omitting
built-in things, functions, etc., but I'm not convinced this is necessary or
even correct.</p>

<p><strong>Attempted solution:</strong></p>

<pre><code>def search_obj_inspect(obj, search_term):
    """Search given object for 'search_term' with 'inspect' module"""

    import inspect

    def searchable_type(obj):
        is_class = inspect.isclass(obj)
        root_type_or_obj = is_class and obj not in [object, type]
        abstract_class = is_class and inspect.isabstract(obj)
        method_or_func = inspect.ismethod(obj) or inspect.isfunction(obj)
        try:
            hidden_attribute = obj.__name__.startswith('__')
        except AttributeError:
            hidden_attribute = False

        searchable = True

        # Avoid infinite recursion when looking through 'object' and 'type' b/c
        # they are members of each other
        if True in [abstract_class, root_type_or_obj, hidden_attribute,
                    method_or_func, inspect.isbuiltin(obj)]:
            searchable = False

        return searchable


    # FIXME: Search obj itself for search term? obj.__name__ == search_term?
    for n, v in inspect.getmembers(obj,
                                   predicate=lambda x: searchable_type(x)):
        if search_term in n:
            try:
                return v.__name__
            except AttributeError:
                return str(v)

    return None


def search_obj_dir(obj, search_term):
    """Search given object and all it's python submodules for attr"""

    import re
    import types
    SEARCHABLE_TYPES = (types.ModuleType, types.ClassType, types.InstanceType,
                        types.DictProxyType)

    for item in dir(obj):
        if item.startswith('__'):
            continue

        if re.search(search_term, item):
            return obj.__dict__[item].__module__
        else:
            try:
                submod = obj.__dict__[item]
            except (KeyError, AttributeError):
                # Nothing left to recursively search
                continue

            if not isinstance(submod, SEARCHABLE_TYPES):
                continue

            #mod = search_obj_dir(submod, search_term)
            #if mod:
                #return '.'.join([mod, submod.__name__])

    return None


def search_so(module, attr):
    """Search given modules shared objects for attr"""

    import os
    import re

    try:
        path = module.__path__[0]
    except (AttributeError, IndexError):
        return False

    for root, dirs, files in os.walk(path):
        for filename in files:
            if filename.endswith('.so'):
                py_module_name = filename.split('.')[0]
                so_module = __import__(module.__name__, globals(), locals(),
                                        [py_module_name])

                if re.search(attr, py_module_name):
                    return ''.join([module.__name__, '.', py_module_name])
                try:
                    found = search_obj_dir(
                                    so_module.__dict__[py_module_name], attr)
                except KeyError:
                    # This isn't a top-level so, might be a subpackage
                    # FIXME: Could recursively search SO as well, but imports
                    #        might get weird
                    continue

                if found:
                    return found
</code></pre>
