Utility for organizing Python imports using PEP8 or custom rules
- Free software: MIT license
- GitHub: https://github.com/miki725/importanize
You can install importanize
using pip:
pip install importanize
I think imports are important in Python. There are some tools to reformat code
(black is amazing). However they
usually dont organize imports very well following PEP8 or custom rules. Top
import organizers are isort and
zimports. importanize
is
similar to them in a sense that it too organizes imports using either PEP8
or custom rules except it also preserves any comments surrounding imports.
In addition it supports some nifty features like full pipe support (yes you
can run :'<,'>!importanize
- your welcome my fellow vim
users :D) or
it can natively output a diff between original and organized file(s).
$ cat tests/test_data/input_readme.py
from __future__ import unicode_literals, print_function
import os.path as ospath # ospath is great
from package.subpackage.module.submodule import CONSTANT, Klass, foo, bar, rainbows
# UTC all the things
import datetime # pytz
from .module import foo, bar # baz
from ..othermodule import rainbows
$ cat tests/test_data/input_readme.py | importanize
from __future__ import print_function, unicode_literals
# UTC all the things
import datetime # pytz
from os import path as ospath # ospath is great
from package.subpackage.module.submodule import (
CONSTANT,
Klass,
bar,
foo,
rainbows,
)
from ..othermodule import rainbows
from .module import bar, foo # baz⏎
importanize
did:
- alphabetical sort, even inside import line (look at
__future__
) - normalized
import .. as ..
intofrom .. import .. as ..
- broke long import (>80 chars) which has more than one import into multiple lines
- reordered some imports (e.g. local imports
..
should be before.
)
Using importanize
is super easy. Just run:
importanize file_to_organize.py
That will re-format all imports in the given file.
As part of the default configuration, importanize
will try
it's best to organize imports to follow PEP8 however that is a rather
challenging task, since it is difficult to determine all import groups
as suggested by PEP8:
- standard library imports
- related third party imports
- local application/library specific imports
To help importanize
distinguish between different import groups in most
cases it would be recommended to use custom config file:
importanize file_to_organize.py --config=config.json
Alternatively importanize
attempts to find configuration in a couple of
default files:
.importanizerc
importanize.ini
importanize.json
setup.cfg
tox.ini
As a matter of fact you can see the config file for the importanize repository itself at setup.cfg.
Additionally multiple configurations are supported within a single repository via sub-configurations. Simply place any of supported config files (see above) within a sub-folder and all imports will be reconfigured under that folder with the subconfiguration.
groups : | List of import groups.
Can only be specified in configuration file. |
---|---|
formatter : | Select how to format long multiline imports. Supported formatters:
Can be specified in CLI with importanize --formatter=grouped |
length : | Line length after which the formatter will split imports. Can be specified in CLI with importanize --length=120 |
exclude : | List of glob patterns of files which should be excluded from organizing: [importanize]
exclude=
path/to/file
path/to/files/ignore_*.py or: {
"exclude": [
"path/to/file",
"path/to/files/ignore_*.py"
]
} Can only be specified in configuration file. |
after_imports_normalize_new_lines : | Boolean whether to adjust number of new lines after imports. By
default this option is enabled. Enabling this option will disable
Can only be specified in configuration file. |
after_imports_new_lines : | Number of lines to be included after imports. Can only be specified in configuration file. |
add_imports : | List of imports to add to every file: [importanize]
add_imports=
from __future__ import absolute_import, print_function, unicode_literals or: {
"add_imports": [
"from __future__ import absolute_import, print_function, unicode_literals"
]
} Can only be specified in configuration file. Note that this option is ignored when input is provided via cat test.py | importanize |
allow_plugins : | Whether to allow plugins: [importanize]
allow_plugins=True or: {
"allow_plugins": true
} Can also be specified with importanize --no-plugins Note that this configuration is only global and is not honored in subconfigurations. |
plugins : | If plugins are allowed, which plugins to use. If not specified all by default enabled plugins will be used. [importanize]
plugins=
unused_imports or: {
"plugins": ["unused_imports"]
} Note that this configuration is only global and is not honored in subconfigurations. |
To view all additional run-time options you can use --help
parameter:
importanize --help
As mentioned previously default configuration attempts to mimic PEP8. Specific configuration is:
[importanize]
groups=
stdlib
sitepackages
remainder
local
Configuration file can either be ini
or json
file. Previously json
was the only supported style however since ini
is easier to read and can
be combined with other configurations like flake8
in setup.cfg
, going
forward it is the preferred configuration format.
The following configurations are identical:
[importanize]
formatter=grouped
groups=
stdlib
sitepackages
remainder
packages:my_favorite_package,least_favorite_package
local
and:
{
"formatter": "grouped",
"groups": [
{"type": "stdlib"},
{"type": "sitepackages"},
{"type": "remainder"},
{"type": "packages",
"packages": ["my_favorite_package", "least_favorite_package"]},
{"type": "local"}
]
}
Sometimes it is useful to check if imports are already organized in a file:
importanize --ci
It is possible to directly see the diff between original and organized file
$ importanize --print --diff --no-subconfig --no-plugins tests/test_data/input_readme_diff.py
--- original/tests/test_data/input_readme_diff.py
+++ importanized/tests/test_data/input_readme_diff.py
@@ -1 +1,9 @@
-from package.subpackage.module.submodule import CONSTANT, Klass, foo, bar, rainbows
+from __future__ import absolute_import, print_function, unicode_literals
+
+from package.subpackage.module.submodule import (
+ CONSTANT,
+ Klass,
+ bar,
+ foo,
+ rainbows,
+)
All found imports can be aggregated with --list
parameter:
$ importanize --list .
stdlib
------
from __future__ import absolute_import, print_function, unicode_literals
import abc
...
sitepackages
------------
import click
...
remainder
---------
packages
--------
import importanize
...
local
-----
...
Pipes for both stdin
and stdout
are supported and are auto-detected:
$ cat tests/test_data/input_readme_diff.py | importanize
from package.subpackage.module.submodule import (
CONSTANT,
Klass,
bar,
foo,
rainbows,
)
$ importanize --no-header --no-subconfig --no-plugins tests/test_data/input_readme_diff.py | cat
from __future__ import absolute_import, print_function, unicode_literals
from package.subpackage.module.submodule import (
CONSTANT,
Klass,
bar,
foo,
rainbows,
)
As mentioned above note that stdin
did not honor add_imports
which
allows to use importanize on selected lines in editors such as vim
.
To facilitate that feature even further, if selected lines are not module
level (e.g. inside function), any whitespace prefix will be honored:
$ python -c "print(' import sys\n import os')" | importanize
import os
import sys
Just in case pipes are incorrectly detected auto-detection can be disabled
with --no-auto-pipe
which will require to explicitly use --print
,
--no-header
and/or -
file path:
$ cat tests/test_data/input_readme_diff.py | importanize --no-auto-pipe --print -
from package.subpackage.module.submodule import (
CONSTANT,
Klass,
bar,
foo,
rainbows,
)
Importanize integrates with pre-commit. You can use the following config
repos:
- repo: https://github.com/miki725/importanize/
rev: 'master'
hooks:
- id: importanize
args: [--verbose]
To run the tests you need to install testing requirements first:
make install
Then to run tests, you can use nosetests
or simply use Makefile command:
nosetests -sv
# or
make test
There is rudimentarry support for plugins. Currently plugin interface is
limited but allows for some useful operations. Plugins can be dynamically
registered via pluggy however
importanize
ships with some bundled-in plugins at importanize/contrib
.
To create a plugin simply implement ImportanizePlugin
class.
Note that example below does not implement all supported methods.
from importanize.plugins import ImportanizePlugin, hookimpl
class MyPlugin(ImportanizePlugin):
version = '0.1'
@hookimpl
def should_include_statement(self, group, statement):
return True
plugin = MyPlugin()
Then register the plugin in setup.py
:
setup(
...
entry_points={
"importanize": ["my_plugin = my_plugin:plugin"],
},
)
All installed plugins are listed as part of importanize --version
command.
Uses pyflakes
to remove unused imports:
$ importanize tests/test_data/input_unused_imports.py --print --diff --no-subconfig
--- original/tests/test_data/input_unused_imports.py
+++ importanized/tests/test_data/input_unused_imports.py
@@ -1,5 +1,5 @@
+from __future__ import absolute_import, print_function, unicode_literals
import os
-import sys
os.path.exists('.')
This plugin is enabled by default. To disable removing unused imports you can either:
disable all plugins via
allow_plugins
:allow_plugins=false
disable
unused_imports
specific plugin by omitting it fromplugins
configuration:plugins= # other plugins except unused_imports
add
# noqa
comment to unused imports to not remove them
Splits all libraries into independant blocks within import groups:
$ importanize tests/test_data/input_separate_libs.py --print --diff --no-subconfig -c tests/test_data/subconfig/separate_libs.ini
--- original/tests/test_data/input_separate_libs.py
+++ importanized/tests/test_data/input_separate_libs.py
@@ -2,6 +2,7 @@
import sys
import click
+
import pluggy
from . import foo
This plugin is not enabled by default. To enable add separate_libs
to
plugins
configuration:
plugins=
separate_libs