Skip to content

miki725/importanize

Repository files navigation

Importanize (import organize)

image

image

image

image

Utility for organizing Python imports using PEP8 or custom rules

Installing

You can install importanize using pip:

pip install importanize

Why?

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).

Example

Before

$ 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

After

$ 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 .. into from .. 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

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:

  1. standard library imports
  2. related third party imports
  3. local application/library specific imports

Configuration

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.

Configuration Options

groups

List of import groups. importanize will use these group definitions to organize imports and will output import groups in the same order as defined. Supported group types are:

  • stdlib - standard library imports including __future__
  • sitepackages - imports coming from the site-packages directory
  • local - local imports which start with ".". for example from .foo import bar
  • packages - if this group is specified, additional key packages is required within import group definition which should list all Python packages (root level) which should be included in that group:

    [importanize]
    groups=
      packages:foo,bar

    or:

    {
      "type": "packages",
      "packages": ["foo", "bar"]
    }
  • remaining - all remaining imports which did not satisfy requirements of all other groups will go to this group.

Can only be specified in configuration file.

formatter

Select how to format long multiline imports. Supported formatters:

  • grouped (default):

    from package.subpackage.module.submodule import (
        CONSTANT,
        Klass,
        bar,
        foo,
        rainbows,
    )
  • inline-grouped:

    from package.subpackage.module.submodule import (CONSTANT,
                                                     Klass,
                                                     bar,
                                                     foo,
                                                     rainbows)
  • lines:

    from package.subpackage.module.submodule import CONSTANT
    from package.subpackage.module.submodule import Klass
    from package.subpackage.module.submodule import bar
    from package.subpackage.module.submodule import foo
    from package.subpackage.module.submodule import rainbows

Can be specified in CLI with -f or --formatter parameter:

importanize --formatter=grouped
length

Line length after which the formatter will split imports.

Can be specified in CLI with -l or --length parameter:

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 after_imports_new_lines.

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 stdin pipe. This is on purpose to allow to importanize selected text in editors such as vim.

cat test.py | importanize
allow_plugins

Whether to allow plugins:

[importanize]
allow_plugins=True

or:

{
    "allow_plugins": true
}

Can also be specified with --plugins/--no-plugins parameter.

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

Default Configuration

As mentioned previously default configuration attempts to mimic PEP8. Specific configuration is:

[importanize]
groups=
  stdlib
  sitepackages
  remainder
  local

Configuration Styles

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"}
  ]
}

CI Mode

Sometimes it is useful to check if imports are already organized in a file:

importanize --ci

Diff

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,
+)

List All Imports

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
-----
...

Pipe Support

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,
)

Pre-Commit

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]

Testing

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

Plugins

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.

Bundled Plugins

Unused Imports

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 from plugins configuration:

    plugins=
      # other plugins except unused_imports
  • add # noqa comment to unused imports to not remove them

Separate Libraries

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