krassowski
krassowski

Reputation: 15379

Is it possible to generate import statement from the module object in python?

In Python, it is easy to check if an object is a module with isinstance(obj, types.ModuleType). We can also programmatically generate modules. I am however interested in going the other way around - generating code that would have created an import resulting in the module being added the globals/locals namespace. Basically a function assemble_import like this:

def assemble_import(module: types.ModuleType, name: str) -> str:
    pass

Roughly satisfying following conditions:

import statistics
assert assemble_import(statistics, 'statistics') = 'import statistics'

from os import path
assert assemble_import(path, 'path') = 'from os import path'

import collections.abc
abc = collections.abc
assert assemble_import(abc, 'abc') = 'from collections import abc'

import abc as xyz
assert assemble_import(xyz, 'xyz') = 'import abc as xyz'

I would not want it to use the abstract syntax tree, but rather the module object itself. What I have tried to so far:

the problem with the name attribute is that it is 'posixpath' for os.path, while from os import posixpath clearly does not work. Also, I do not see how to get the parent module (os in the os.path example).

Is achieving a workable (though not necessarily a production-ready/bulletproof) solution possible in Python? In other words, is the information about packages structure needed to recreate the imports code preserved/accessible?

Upvotes: 1

Views: 470

Answers (1)

user2357112
user2357112

Reputation: 280291

You can, and it's quite straightforward, but os.path will be a little weird:

def assemble_import(module, name):
    return 'import {} as {}'.format(module.__name__, name)

os.path is weird because it's a platform-dependent alias for either the ntpath module or the posixpath module. This function will use the module's actual name, either ntpath or posixpath. If you want to treat os.path as os.path, you can special-case it, though it might not be the best design choice.

For actual package submodules, like collections.abc, this function will treat them as submodules of their containing package:

>>> assemble_import(collections.abc, 'abc')
'import collections.abc as abc'

but for os.path, this will give you output like

>>> assemble_import(os.path, 'path')
'import posixpath as path'

If you want imports that look a bit more like what a human would normally write, you can add some logic to the function:

def assemble_import(module, name):
    pname, _, mname = module.__name__.rpartition('.')
    if pname:
       statement = 'from {} import {}'.format(pname, mname)
    else:
       statement = 'import ' + mname
    if mname != name:
       statement += ' as ' + name
    return statement

Upvotes: 1

Related Questions