diff --git a/.gitignore b/.gitignore index c447675..dc5942e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,4 @@ *.py[co] .coverage .tox -/.mypy_cache -/.pytest_cache -/venv* -coverage-html dist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 521a49b..5244a5d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,49 +1,43 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - - id: check-docstring-first - id: check-yaml - id: debug-statements - id: double-quote-string-fixer - id: name-tests-test - id: requirements-txt-fixer -- repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 - hooks: - - id: flake8 - additional_dependencies: ['flake8-typing-imports==1.12.0'] -- repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v1.6.0 +- repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.20.1 hooks: - - id: autopep8 + - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports args: [--py37-plus, --add-import, 'from __future__ import annotations'] -- repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 - hooks: - - id: pyupgrade - args: [--py37-plus] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.1 + rev: v2.2.3 hooks: - id: add-trailing-comma args: [--py36-plus] -- repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.0 +- repo: https://github.com/asottile/pyupgrade + rev: v2.32.0 hooks: - - id: setup-cfg-fmt -- repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.9.0 + - id: pyupgrade + args: [--py37-plus] +- repo: https://github.com/pre-commit/mirrors-autopep8 + rev: v1.6.0 + hooks: + - id: autopep8 +- repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - - id: python-use-type-annotations + - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 + rev: v0.942 hooks: - id: mypy additional_dependencies: [types-all] diff --git a/README.md b/README.md index 12b1ca1..3049a2a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.reorder_python_imports?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=28&branchName=master) -[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/28/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=28&branchName=master) -[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/reorder_python_imports/master.svg)](https://results.pre-commit.ci/latest/github/asottile/reorder_python_imports/master) +[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.reorder_python_imports?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=28&branchName=main) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/28/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=28&branchName=main) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/reorder_python_imports/main.svg)](https://results.pre-commit.ci/latest/github/asottile/reorder_python_imports/main) reorder_python_imports ====================== @@ -43,7 +43,7 @@ Sample `.pre-commit-config.yaml` ```yaml - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports ``` diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 131e765..ada0df8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,6 +1,6 @@ trigger: branches: - include: [master, test-me-*] + include: [main, test-me-*] tags: include: ['*'] diff --git a/reorder_python_imports.py b/reorder_python_imports.py index 503804f..445b31a 100644 --- a/reorder_python_imports.py +++ b/reorder_python_imports.py @@ -4,7 +4,6 @@ import ast import collections import enum -import functools import io import os import sys @@ -245,6 +244,20 @@ def _inner() -> Generator[CodePartition, None, None]: new_src = import_obj.to_text() yield partition._replace(src=new_src) break + # from a.b.c import d => from c import d + elif ( + (mod_parts + [symbol] == orig_mod) and + not attr and + len(new_mod) > 1 and + (asname or symbol == new_mod[-1]) + ): + import_obj.ast_obj.module = '.'.join(new_mod[:-1]) + import_obj.ast_obj.names = [ + ast.alias(name=new_mod[-1], asname=asname), + ] + new_src = import_obj.to_text() + yield partition._replace(src=new_src) + break # from x.y import z => import z elif ( not attr and @@ -368,21 +381,6 @@ def apply_import_sorting( return pre_import_code + new_imports + rest -def _get_steps( - imports_to_add: tuple[str, ...], - imports_to_remove: tuple[str, ...], - imports_to_replace: Iterable[ImportToReplace], - **sort_kwargs: Any, -) -> Generator[Step, None, None]: - yield combine_trailing_code_chunks - yield functools.partial(add_imports, to_add=imports_to_add) - yield separate_comma_imports - yield functools.partial(remove_imports, to_remove=imports_to_remove) - yield functools.partial(replace_imports, to_replace=imports_to_replace) - yield remove_duplicated_imports - yield functools.partial(apply_import_sorting, **sort_kwargs) - - def _most_common_line_ending(s: str) -> str: # initialize in case there's no line endings at all counts = collections.Counter({'\n': 0}) @@ -406,13 +404,14 @@ def fix_file_contents( contents = contents.replace('\r\n', '\n').replace('\r', '\n') partitioned = partition_source(contents) - for step in _get_steps( - imports_to_add, - imports_to_remove, - imports_to_replace, - **sort_kwargs, - ): - partitioned = step(partitioned) + partitioned = combine_trailing_code_chunks(partitioned) + partitioned = add_imports(partitioned, to_add=imports_to_add) + partitioned = separate_comma_imports(partitioned) + partitioned = remove_imports(partitioned, to_remove=imports_to_remove) + partitioned = replace_imports(partitioned, to_replace=imports_to_replace) + partitioned = remove_duplicated_imports(partitioned) + partitioned = apply_import_sorting(partitioned, **sort_kwargs) + return _partitions_to_src(partitioned).replace('\n', nl) diff --git a/setup.cfg b/setup.cfg index f6bd373..c0ea600 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = reorder_python_imports -version = 3.0.1 +version = 3.1.0 description = Tool for reordering python imports long_description = file: README.md long_description_content_type = text/markdown diff --git a/tests/reorder_python_imports_test.py b/tests/reorder_python_imports_test.py index 9fd49c8..a476762 100644 --- a/tests/reorder_python_imports_test.py +++ b/tests/reorder_python_imports_test.py @@ -524,6 +524,54 @@ def test_replace_module_imported_asname(): assert ret == 'import queue as Queue\n' +def test_replace_module_imported_with_nested_replacement(): + ret = fix_file_contents( + 'from six.moves.urllib import parse\n', + imports_to_replace=[ + (['six', 'moves', 'urllib', 'parse'], ['urllib', 'parse'], ''), + ], + ) + assert ret == 'from urllib import parse\n' + + +def test_replace_module_imported_with_nested_replacement_asname(): + ret = fix_file_contents( + 'from six.moves.urllib import parse as urllib_parse\n', + imports_to_replace=[ + (['six', 'moves', 'urllib', 'parse'], ['urllib', 'parse'], ''), + ], + ) + assert ret == 'from urllib import parse as urllib_parse\n' + + +def test_replace_module_imported_with_nested_renamed_replacement_asname(): + ret = fix_file_contents( + 'from six.moves.urllib import parse as urllib_parse\n', + imports_to_replace=[ + (['six', 'moves', 'urllib', 'parse'], ['urllib', 'parse2'], ''), + ], + ) + assert ret == 'from urllib import parse2 as urllib_parse\n' + + +def test_replace_module_skips_attr_specific_rules(): + ret = fix_file_contents( + 'from libone import util\n', + imports_to_replace=[ + (['libone', 'util'], ['libtwo', 'util'], 'is_valid'), + ], + ) + assert ret == 'from libone import util\n' + + +def test_replace_module_skips_nonmatching_rules(): + ret = fix_file_contents( + 'from libthree import util\n', + imports_to_replace=[(['libone', 'util'], ['libtwo', 'util'], '')], + ) + assert ret == 'from libthree import util\n' + + cases = pytest.mark.parametrize( ('s', 'expected'), (