How to import all submodules? - python

I have a directory structure as follows:
| main.py
| scripts
|--| __init__.py
| script1.py
| script2.py
| script3.py
From main.py, the module scripts is imported. I tried using pkgutils.walk_packages in combination with __all__, but using that, I can only import all the submodules directly under main using from scripts import *. I would like to get them all under scripts. What would be the cleanest way to import all the submodules of scripts so that I could access scripts.script1 from main?
EDIT: I am sorry that I was a bit vague. I would like to import the submodules on run-time without specifying them explicitly in __init__.py. I can use pkgutils.walk_packages to get the submodule names (unless someone knows of a better way), but I am not sure of the cleanest way to use these names (or maybe the ImpImporters that walk_packages returns?) to import them.

Edit: Here's one way to recursively import everything at runtime...
(Contents of __init__.py in top package directory)
import pkgutil
__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
__all__.append(module_name)
_module = loader.find_module(module_name).load_module(module_name)
globals()[module_name] = _module
I'm not using __import__(__path__+'.'+module_name) here, as it's difficult to properly recursively import packages using it. If you don't have nested sub-packages, and wanted to avoid using globals()[module_name], though, it's one way to do it.
There's probably a better way, but this is the best I can do, anyway.
Original Answer (For context, ignore othwerwise. I misunderstood the question initially):
What does your scripts/__init__.py look like? It should be something like:
import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']
You could even do without defining __all__, but things (pydoc, if nothing else) will work more cleanly if you define it, even if it's just a list of what you imported.

This is based on the answer that kolypto provided, but his answer does not perform recursive import of packages, whereas this does. Although not required by the main question, I believe recursive import applies and can be very useful in many similar situations. I, for one, found this question when searching on the topic.
This is a nice, clean way of performing the import of the subpackage's modules, and should be portable as well, and it uses the standard lib for python 2.7+ / 3.x.
import importlib
import pkgutil
def import_submodules(package, recursive=True):
""" Import all submodules of a module, recursively, including subpackages
:param package: package (name or actual module)
:type package: str | module
:rtype: dict[str, types.ModuleType]
"""
if isinstance(package, str):
package = importlib.import_module(package)
results = {}
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
full_name = package.__name__ + '.' + name
results[full_name] = importlib.import_module(full_name)
if recursive and is_pkg:
results.update(import_submodules(full_name))
return results
Usage:
# from main.py, as per the OP's project structure
import scripts
import_submodules(scripts)
# Alternatively, from scripts.__init__.py
import_submodules(__name__)

Simply works, and allows relative import inside packages:
def import_submodules(package_name):
""" Import all submodules of a module, recursively
:param package_name: Package name
:type package_name: str
:rtype: dict[types.ModuleType]
"""
package = sys.modules[package_name]
return {
name: importlib.import_module(package_name + '.' + name)
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__)
}
Usage:
__all__ = import_submodules(__name__).keys()

Not nearly as clean as I would like, but none of the cleaner methods worked for me. This achieves the specified behaviour:
Directory structure:
| pkg
|--| __init__.py
| main.py
| scripts
|--| __init__.py
| script1.py
| script2.py
| script3.py
Where pkg/scripts/__init__.py is empty, and pkg/__init__.py contains:
import importlib as _importlib
import pkgutil as _pkgutil
__all__ = [_mod[1].split(".")[-1] for _mod in
filter(lambda _mod: _mod[1].count(".") == 1 and not
_mod[2] and __name__ in _mod[1],
[_mod for _mod in _pkgutil.walk_packages("." + __name__)])]
__sub_mods__ = [".".join(_mod[1].split(".")[1:]) for _mod in
filter(lambda _mod: _mod[1].count(".") > 1 and not
_mod[2] and __name__ in _mod[1],
[_mod for _mod in
_pkgutil.walk_packages("." + __name__)])]
from . import *
for _module in __sub_mods__:
_importlib.import_module("." + _module, package=__name__)
Although it's messy, it should be portable. I've used this code for several different packages.

I got tired of this problem myself, so I wrote a package called automodinit to fix it. You can get it from http://pypi.python.org/pypi/automodinit/. Usage is like this:
Include the automodinit package into your setup.py dependencies.
Add the following to the beginning of the __init__.py file:
__all__ = ["I will get rewritten"]
# Don't modify the line above, or this line!
import automodinit
automodinit.automodinit(__name__, __file__, globals())
del automodinit
# Anything else you want can go after here, it won't get modified.
That's it! From now on importing a module will set __all__ to
a list of .py[co] files in the module and will also import each
of those files as though you had typed:
for x in __all__: import x
Therefore the effect of from M import * matches exactly import M.
automodinit is happy running from inside ZIP archives and is therefore ZIP safe.

To just load all submodules of a package, you can use this simple function:
import importlib
import pkgutil
def import_submodules(module):
"""Import all submodules of a module, recursively."""
for loader, module_name, is_pkg in pkgutil.walk_packages(
module.__path__, module.__name__ + '.'):
importlib.import_module(module_name)
Use case: load all database models of a Flask app, so that Flask-Migrate could detect changes to the schema. Usage:
import myproject.models
import_submodules(myproject.models)

I was writing a small personal library and adding new modules all the time so I wrote a shell script to look for scripts and create the __init__.py's. The script is executed just outside of the main directory for my package, pylux.
I know it probably isn't the answer you're looking for, but it servered its purpose for me and it might be useful to someone else, too.
#!/bin/bash
echo 'Traversing folder hierarchy...'
CWD=`pwd`
for directory in `find pylux -type d -exec echo {} \;`;
do
cd $directory
#echo Entering $directory
echo -n "" > __init__.py
for subdirectory in `find . -type d -maxdepth 1 -mindepth 1`;
do
subdirectory=`echo $subdirectory | cut -b 3-`
#echo -n ' ' ...$subdirectory
#echo -e '\t->\t' import $subdirectory
echo import $subdirectory >> __init__.py
done
for pyfile in *.py ;
do
if [ $pyfile = $(echo __init__.py) ]; then
continue
fi
#echo -n ' ' ...$pyfile
#echo -e '\t->\t' import `echo $pyfile | cut -d . -f 1`
echo import `echo $pyfile | cut -d . -f 1` >> __init__.py
done
cd $CWD
done
for directory in `find pylux -type d -exec echo {} \;`;
do
echo $directory/__init__.py:
cat $directory/__init__.py | awk '{ print "\t"$0 }'
done

I've played around with Joe Kington's Answer and have built a solution that uses globals and get/setattr and thus doesn't need eval. A slight modification is that instead of directly using the packages __path__ for walk_packages, I use the packages parent directory and then only import modules starting with __name__ + ".". This was done to reliably get all subpackages from walk_packages - in my use case I had a subpackage named test which caused pkgutil to iterate over the test package from python's library; furthermore, using __path__ would not recurse into the packages subdirectories. All these issues were observed using jython and python2.5, the code below is only tested in jython thus far.
Also note that OPs question only talks about importing all modules from a package, this code recursively imports all packages too.
from pkgutil import walk_packages
from os import path
__all__ = []
__pkg_prefix = "%s." % __name__
__pkg_path = path.abspath(__path__[0]).rsplit("/", 1)[0] #parent directory
for loader, modname, _ in walk_packages([__pkg_path]):
if modname.startswith(__pkg_prefix):
#load the module / package
module = loader.find_module(modname).load_module(modname)
modname = modname[len(__pkg_prefix):] #strip package prefix from name
#append all toplevel modules and packages to __all__
if not "." in modname:
__all__.append(modname)
globals()[modname] = module
#set everything else as an attribute of their parent package
else:
#get the toplevel package from globals()
pkg_name, rest = modname.split(".", 1)
pkg = globals()[pkg_name]
#recursively get the modules parent package via getattr
while "." in rest:
subpkg, rest = rest.split(".", 1)
pkg = getattr(pkg, subpkg)
#set the module (or package) as an attribute of its parent package
setattr(pkg, rest, module)
As a future improvement I'll try to make this dynamic with a __getattr__ hook on the package, so the actual modules are only imported when they are accessed...

This works nicely for me in Python 3.3. Note that this works only for submodules which are in files in the same directory as the __init__.py. With some work however it can be enhanced for supporting submodules in directories too.
from glob import iglob
from os.path import basename, relpath, sep, splitext
def import_submodules(__path__to_here):
"""Imports all submodules.
Import this function in __init__.py and put this line to it:
__all__ = import_submodules(__path__)"""
result = []
for smfile in iglob(relpath(__path__to_here[0]) + "/*.py"):
submodule = splitext(basename(smfile))[0]
importstr = ".".join(smfile.split(sep)[:-1])
if not submodule.startswith("_"):
__import__(importstr + "." + submodule)
result.append(submodule)
return result

Related

How do you recursively get all submodules in a python package?

Problem
I have a folder structure like this:
- modules
- root
- abc
hello.py
__init__.py
- xyz
hi.py
__init__.py
blah.py
__init__.py
foo.py
bar.py
__init_.py
Here is the same thing in string format:
"modules",
"modues/__init__.py",
"modules/foo.py",
"modules/bar.py",
"modules/root",
"modules/root/__init__.py",
"modules/root/blah,py",
"modules/root/abc",
"modules/root/abc/__init__.py",
"modules/root/abc/hello.py",
"modules/root/xyz",
"modules/root/xyz/__init__.py",
"modules/root/xyz/hi.py"
I am trying to print out all the modules in the python import style format.
An example output would like this:
modules.foo
modules.bar
modules.root.blah
modules.root.abc.hello
modules.root.xyz.hi
How can I do this is in python(if possible without third party libraries) easily?
What I tried
Sample Code
import pkgutil
import modules
absolute_modules = []
def find_modules(module_path):
for package in pkgutil.walk_packages(module_path):
print(package)
if package.ispkg:
find_modules([package.name])
else:
absolute_modules.append(package.name)
if __name__ == "__main__":
find_modules(modules.__path__)
for module in absolute_modules:
print(module)
However, this code will only print out 'foo' and 'bar'. But not 'root' and it's sub packages. I'm also having trouble figuring out how to convert this to preserve it's absolute import style. The current code only gets the package/module name and not the actual absolute import.
This uses setuptools.find_packages (for the packages) and pkgutil.iter_modules for their submodules. Python2 is supported as well. No need for recursion, it's all handled by these two functions used together.
import sys
from setuptools import find_packages
from pkgutil import iter_modules
def find_modules(path):
modules = set()
for pkg in find_packages(path):
modules.add(pkg)
pkgpath = path + '/' + pkg.replace('.', '/')
if sys.version_info.major == 2 or (sys.version_info.major == 3 and sys.version_info.minor < 6):
for _, name, ispkg in iter_modules([pkgpath]):
if not ispkg:
modules.add(pkg + '.' + name)
else:
for info in iter_modules([pkgpath]):
if not info.ispkg:
modules.add(pkg + '.' + info.name)
return modules
So I finally figured out how to do this cleanly and get pkgutil to take care of all the edge case for you. This code was based off python's help() function which only displays top level modules and packages.
import importlib
import pkgutil
import sys
import modules
def find_abs_modules(module):
path_list = []
spec_list = []
for importer, modname, ispkg in pkgutil.walk_packages(module.__path__):
import_path = f"{module.__name__}.{modname}"
if ispkg:
spec = pkgutil._get_spec(importer, modname)
importlib._bootstrap._load(spec)
spec_list.append(spec)
else:
path_list.append(import_path)
for spec in spec_list:
del sys.modules[spec.name]
return path_list
if __name__ == "__main__":
print(sys.modules)
print(find_abs_modules(modules))
print(sys.modules)
This will work even for builtin packages.
The below code will give you the relative package module from the codes current working directory.
import os
import re
for root,dirname,filename in os.walk(os.getcwd()):
pth_build=""
if os.path.isfile(root+"/__init__.py"):
for i in filename:
if i <> "__init__.py" and i <> "__init__.pyc":
if i.split('.')[1] == "py":
slot = list(set(root.split('\\')) -set(os.getcwd().split('\\')))
pth_build = slot[0]
del slot[0]
for j in slot:
pth_build = pth_build+"."+j
print pth_build +"."+ i.split('.')[0]
This code will display:
modules.foo
modules.bar
modules.root.blah
modules.root.abc.hello
modules.root.xyz.hi
If you run it outside the modules folder.

Easily importing all third-party packages and scripts as a common package?

I want to add an external package to my program which should include all recipes and third-party packages it uses. I don't want to force anyone (including myself) to install those packages, not to mention the version incopatibilities. I just
want to put them into their own subfolders and use them. They,
of course, come from various sources.
The folder structure should look like this:
| main.py
|---[external]
|---[networkx]
| | ...
|---[xlrd]
| | ...
| __init__.py
| recipe1.py
| recipe2.py
I want to reach the packages and recipes in the following ways in my program:
import external
import external.xlrd
from external import networkx as nx
import external.recipe1
from external import recipe2 as magic
from external import *
Any package however may contain absolute imports which can result ImportError assuming an external has an empty __init__.py:
>>> import external.networkx
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".\external\networkx\__init__.py", line 46, in <module>
from networkx import release
ImportError: No module named 'networkx'
Is there an easy way to make such an external package?
I made a function which does this. All you need to do is to put this line to the __init__.py of the external folder:
__all__ = import_all(__path__)
This function can use generally to import all submodules, even if they are nested, just put this to all __init__.py involved.
If you know a cleaner solution to the question, please share it with us! I won't accept my own answer.
from os import listdir
from os.path import abspath, basename, exists, isdir, join, \
relpath, sep, splitext
from sys import path
def import_all(path__here):
"""Imports all subpackages and submodules, excluding .py
files starting with an underscore. The directories have to
have an __init__.py to get imported.
Add this line to the __init__.py:
__all__ = import_all(__path__)"""
result, packagepath = [], relpath(path__here[0])
for e in listdir(packagepath):
mod_path = join(packagepath, e)
mod_name = splitext(basename(e))[0]
file_to_import = (e.endswith(".py")
and not mod_name.startswith("_"))
dir_to_import = (isdir(mod_path)
and exists(join(mod_path, "__init__.py")))
if not file_to_import and not dir_to_import:
continue
im_str = ".".join(mod_path.split(sep)[:-1] + [mod_name])
try:
__import__(im_str)
except ImportError as err:
# In case of a subpackage countains absolute imports
# assuming it is in the root, we put it into the
# system path and try again.
if abspath(packagepath) not in path:
path.insert(0, abspath(packagepath))
__import__(im_str)
else:
raise err from None
result.append(mod_name)
return result

Python pkgutil.walk_packages not returning subpackages

I have a package layout:
scenarios/
__init__.py
X/
__init__.py
Y/
__init__.py
Z/
__init__.py
I have executed
import scenarios
pkgutil.walk_packages(scenarios.__path__, scenarios.__name__ + '.')
But this generates a list only including the packages X and Z, Y is missing. What can I use to get all the sub directories?
Thanks
Here is a theory: The walk_packages function attempts to import each module listed. When it gets to the sub-package "Y" it attempts to import it, but there is an error. By default, this error is suppressed. A sideeffect is that the walk_packages function doesn't recurse into Y. You can test this theory using the "onerror" keyword argument. For example:
import sys, pkgutil
from traceback import print_tb
def onerror(name):
print("Error importing module %s" % name)
type, value, traceback = sys.exc_info()
print_tb(traceback)
import scenarios
pkgutil.walk_packages(scenarios.__path__, scenarios.__name__ + '.', onerror=onerror)
This works in Python 3.6 at least, for what it's worth. Set up the test scenario:
mkdir -p scenarios/X/Y scenarios/Z
find scenarios -type d -exec touch {}/__init__.py \;
Now run a version of your example code:
import pkgutil
import scenarios
for module_info in pkgutil.walk_packages(scenarios.__path__, scenarios.__name__ + '.'):
print(module_info.name)
Which prints:
scenarios.X
scenarios.X.Y
scenarios.Z

Python: Importing files based on which files the user wants [duplicate]

This question already has answers here:
How to import a module in Python with importlib.import_module
(3 answers)
Closed 8 years ago.
I have the following directory structure
+ code
|
--+ plugins
|
-- __init__.py
-- test_plugin.py (has a class TestPlugin)
-- another_test_plugin.py (has a class AnotherTestPlugin)
--+ load.py
--+ __init__.py
In load.py, I want to be able to initialize only those classes that the user specifies. For example, lets say I do something like
$ python load.py -c test_plugin # Should only import test_plugin.py and initialize an object of the TestPlugin class
I am having trouble trying to use the "imp" module to do it. It keeps on saying "No such file or directory". My understanding is that it is somehow not understanding the path properly. Can someone help me out with this?
ok, your problem is a path related problem. You expect that the script is being run in the same directory as where load.py is, where it is not the case.
what you have to do is something like:
import imp, os, plugins
path = os.path.dirname(plugins.__file__)
imp.load_source('TestPlugin', os.path.join(path, 'test_plugin.py')
where plugins is the module containing all your plugins (i.e. just the empty __init__.py), that will help you get the full path to your plugin modules' files.
Another solution, if you want a "plugins" discovery tool:
import imp, os
import glob
def load_plugins(path):
"""
Assuming `path` is the only directory in which you store your plugins,
and assuming each name follows the syntax:
plugin_file.py -> PluginFile
Please note that we don't import files starting with an underscore.
"""
plugins = {}
plugin_files = glob.glob(path + os.sep + r'[!_]*.py')
for plugin_path in plugin_files:
module_name, ext = os.path.splitext(plugin_path)
module_name = os.path.basename(module_name)
class_name = module_name.title().replace('_', '')
loaded_module = imp.load_source(class_name, plugin_path) # we import the plugin
plugins[module_name] = getattr(loaded_module, class_name)
return plugins
plugins = load_plugins(your_path_here)
plugin_name = sys.argv[3]
plugin = plugins.get(plugin_name)
if not plugin:
# manage a not existing plugin
else:
plugin_instance = plugin() # creates an instance of your plugin
This way, you can also specify different names by changing your keys, e.g., 'test_plugins' => 'tp'. You don't have to initialize your plugins, but you can still run this function whenever you want to load your plugins at runtime.
exec('import ' + sys.argv[2])
obj = test_plugin.TestPlugin()
Here sys.argv[2] is 'test_plugin' string from command line arguments.
EDIT: Another way to avoid using exec:
import importlib
mod = importlib.import_module(sys.argv[2])

changing namespace of current python script (class with module name)

i have a 2.6 python script and library in the following directory structure:
+ bin
\- foo.py
+ lib
\+ foo
\- bar.py
i would like users to run bin/foo.py to instantiate the classes within lib/foo.py. to achieve this, in my bin/foo.py script i have the following code:
from __future__ import absolute_import
import foo
klass = foo.bar.Klass()
however, this results in:
AttributeError: 'module' object has no attribute 'bar'
ie it thinks that foo is itself rather than the library foo - renaming bin/foo.py to bin/foo-script.py works as expected.
is there a way i can keep the bin/foo.py script and import lib/foo.py?
The current directory is on the path by default, so you need to remove that before you import the other foo module:
import sys
sys.path = [dir for dir in sys.path if dir != '']
Alternatively, prepend the lib directory so that it takes precedence:
import sys
sys.path = ['../lib'] + sys.path
If you just write import foo, it will definitely load the foo module in the current scope. Assuming lib and foo as packages, won't you need to write something like this in order to make it work?
import lib.foo.bar as foobar
klass = foobar.Klass()

Categories

Resources