diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 436600b..1a0ab8f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,13 +1,33 @@ -name: main +name: tests on: push: - branches: [main, test-me-*] - tags: '*' + branches: ["main"] pull_request: + branches: ["main"] + workflow_dispatch: jobs: - main: - uses: asottile/workflows/.github/workflows/tox.yml@v1.5.0 - with: - env: '["py38", "py39"]' + tests: + name: "Python ${{ matrix.python-version }} on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: "actions/checkout@v4" + - uses: "actions/setup-python@v5" + with: + python-version: "${{ matrix.python-version }}" + - name: "Install" + run: "pip install --editable . pytest pytest-cov covdefaults" + - name: "Run tests for ${{ matrix.python-version }} on ${{ matrix.os }}" + run: pytest --cov=reorder_python_imports + - name: Upload coverage to Codecov + uses: "codecov/codecov-action@v3" + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index dc5942e..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.egg-info -*.py[co] -.coverage -.tox -dist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 937696b..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,42 +0,0 @@ -repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: debug-statements - - id: double-quote-string-fixer - - id: name-tests-test - - id: requirements-txt-fixer -- repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.5.0 - hooks: - - id: setup-cfg-fmt -- repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 - hooks: - - id: reorder-python-imports - args: [--py38-plus, --add-import, 'from __future__ import annotations'] -- repo: https://github.com/asottile/add-trailing-comma - rev: v3.1.0 - hooks: - - id: add-trailing-comma -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 - hooks: - - id: pyupgrade - args: [--py38-plus] -- repo: https://github.com/hhatto/autopep8 - rev: v2.1.0 - hooks: - - id: autopep8 -- repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 - hooks: - - id: flake8 -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 - hooks: - - id: mypy - additional_dependencies: [types-all] diff --git a/README.md b/README.md index 7b539f2..1ae7ea5 100644 --- a/README.md +++ b/README.md @@ -1,298 +1,26 @@ -[![build status](https://github.com/asottile/reorder-python-imports/actions/workflows/main.yml/badge.svg)](https://github.com/asottile/reorder-python-imports/actions/workflows/main.yml) -[![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-black +============================ -reorder-python-imports -====================== - -Tool for automatically reordering python imports. Similar to `isort` but -uses static analysis more. - - -## Installation - -```bash -pip install reorder-python-imports -``` - - -## Console scripts - -Consult `reorder-python-imports --help` for the full set of options. - -`reorder-python-imports` takes filenames as positional arguments - -Common options: - -- `--py##-plus`: [see below](#removing-obsolete-__future__-imports). -- `--add-import` / `--remove-import`: [see below](#adding--removing-imports). -- `--replace-import`: [see below](#replacing-imports). -- `--application-directories`: by default, `reorder-python-imports` assumes - your project is rooted at `.`. If this isn't true, tell it where your - import roots live. For example, when using the popular `./src` layout you'd - use `--application-directories=.:src` (note: multiple paths are separated - using a `:`). -- `--unclassifiable-application-module`: (may be specified multiple times) - modules names that are considered application modules. this setting is - intended to be used for things like C modules which may not always appear on - the filesystem. - -## As a pre-commit hook - -See [pre-commit](https://github.com/pre-commit/pre-commit) for instructions - -Sample `.pre-commit-config.yaml` - -```yaml -- repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 - hooks: - - id: reorder-python-imports -``` - -## What does it do? - -### Separates imports into three sections - -```python -import sys -import pyramid -import reorder_python_imports -``` - -becomes (stdlib, third party, first party) - -```python -import sys - -import pyramid +Fork of [reorder-python-imports](https://github.com/asottile/reorder-python-imports) for interoperability with [black](https://github.com/psf/black). -import reorder_python_imports ``` - -### `import` imports before `from` imports - -```python -from os import path -import sys -``` - -becomes - -```python -import sys -from os import path -``` - -### Splits `from` imports - -```python -from os.path import abspath, exists -``` - -becomes - -```python -from os.path import abspath -from os.path import exists -``` - -### Removes duplicate imports - -```python -import os -import os.path -import sys -import sys -``` - -becomes - -```python -import os.path -import sys +pip install reorder-python-imports-black ``` -## Using `# noreorder` - -Lines containing and after lines which contain a `# noreorder` comment will -be ignored. Additionally any imports that appear after non-whitespace -non-comment lines will be ignored. +Black wants a newline between the module docstring and the imports, upstream `reorder-python-imports` does not. +Neither project wants to change, if you want to use both in your CI they will get into a fight. -For instance, these will not be changed: +The incompatibility is detailed more [here](https://github.com/psf/black/issues/4175). -```python -import sys +Why fork? +========= -try: # not import, not whitespace - import foo -except ImportError: - pass -``` - - -```python -import sys +Someone already attempted to PR a fix in [reorder-python-imports #370](https://github.com/asottile/reorder-python-imports/pull/370), but it was promptly closed. -import reorder_python_imports +The upstream maintainer closes/locks issues about the incompatibility, appears uninterested ([#366](https://github.com/asottile/reorder-python-imports/issues/366), [#367](https://github.com/asottile/reorder-python-imports/issues/367), [#373](https://github.com/asottile/reorder-python-imports/issues/373), [#375](https://github.com/asottile/reorder-python-imports/issues/375)...) -import matplotlib # noreorder -matplotlib.use('Agg') -import matplotlib.pyplot as plt -``` - -```python -# noreorder -import sys -import pyramid -import reorder_python_imports -``` - -## why this style? - -The style chosen by `reorder-python-imports` has a single aim: reduce merge -conflicts. - -By having a single import per line, multiple contributors can -add / remove imports from a single module without resulting in a conflict. - -Consider the following example which causes a merge conflict: - -```diff -# developer 1 --from typing import Dict, List -+from typing import Any, Dict, List -``` - -```diff -# developer 2 --from typing import Dict, List -+from typing import Dict, List, Tuple -``` - -no conflict with the style enforced by `reorder-python-imports`: - -```diff -+from typing import Any - from typing import Dict - from typing import List -+from typing import Tuple -``` - -## Adding / Removing Imports - -Let's say I want to enforce `absolute_import` across my codebase. I can use: -`--add-import 'from __future__ import absolute_import'`. - -```console -$ cat test.py -print('Hello world') -$ reorder-python-imports --add-import 'from __future__ import absolute_import' test.py -Reordering imports in test.py -$ cat test.py -from __future__ import absolute_import -print('Hello world') -``` - -Let's say I no longer care about supporting Python 2.5, I can remove -`from __future__ import with_statement` with -`--remove-import 'from __future__ import with_statement'` - -```console -$ cat test.py -from __future__ import with_statement -with open('foo.txt', 'w') as foo_f: - foo_f.write('hello world') -$ reorder-python-imports --remove-import 'from __future__ import with_statement' test.py -Reordering imports in test.py -$ cat test.py -with open('foo.txt', 'w') as foo_f: - foo_f.write('hello world') -``` - -## Replacing imports - -Imports can be replaced with others automatically (if they provide the same -names). This can be useful for factoring out compatibility libraries such -as `six` (see below for automated `six` rewriting). - -This rewrite avoids `NameError`s as such it only occurs when: - -- the imported symbol is the same before and after -- the import is a `from` import - -The argument is specified as `orig.mod=new.mod` or with an optional -checked attribute `orig.mod=new.mod:attr`. The checked attribute is useful -for renaming some imports from a module instead of a full module. - -For example: - -```bash -# full module move ---replace-import six.moves.queue=queue -# specific attribute move ---replace-import six.moves=io:StringIO -``` - -## Removing obsolete `__future__` imports - -The cli provides a few options to help "burn the bridges" with old python -versions by removing `__future__` imports automatically. Each option implies -all older versions. - -- `--py22-plus`: `nested_scopes` -- `--py23-plus`: `generators` -- `--py26-plus`: `with_statement` -- `--py3-plus`: `division`, `absolute_import`, `print_function`, - `unicode_literals` -- `--py37-plus`: `generator_stop` - -## Removing / rewriting obsolete `six` imports - -With `--py3-plus`, `reorder-python-imports` will also remove / rewrite imports -from `six`. Rewrites follow the same rules as -[replacing imports](#replacing-imports) above. - -For example: - -```diff -+import queue -+from io import StringIO -+from urllib.parse import quote_plus -+ - import six.moves.urllib.parse --from six.moves import queue --from six.moves import range --from six.moves import StringIO --from six.moves.urllib.parse import quote_plus -``` - -## Rewriting mock imports - -With `--py3-plus`, `reorder-python-imports` will also rewrite various `mock` imports: - -```diff --from mock import patch -+from unittest.mock import patch -``` - -## Rewriting `mypy_extensions` and `typing_extension` imports - -With `--py36-plus` and higher, `reorder-python-imports` will also rewrite -`mypy_extensions` and `typing_extensions` imports ported to `typing`. - -```diff --from mypy_extensions import TypedDict -+from typing import TypedDict -``` - -## Rewriting pep 585 typing imports - -With `--py39-plus` and higher, `reorder-python-imports` will replace imports -which were moved out of the typing module in [pep 585]. - -```diff --from typing import Sequence -+from collections.abc import Sequence -``` +Any other differences? +====================== -[pep 585]: https://www.python.org/dev/peps/pep-0585/ +This package also adds an entry point `rpi`, because it was a drag typing out `reorder-python-imports`. +The original console script remains, though, and they do the same thing. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f0145e4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,73 @@ +[build-system] +requires = [ + "setuptools>=61.2", +] +build-backend = "setuptools.build_meta" + +[project] +name = "reorder-python-imports-black" +version = "3.12.0" +description = "Tool for reordering python imports" +authors = [ + { name = "Anthony Sottile", email = "asottile@umich.edu" }, + { name = "Wim Jeantine-Glenn", email = "wim.glenn@gmail.com" } +] +classifiers = [ + "License :: OSI Approved :: MIT License", +] +requires-python = ">=3.8" +dependencies = [ + "classify-imports>=4.1", +] + +[project.readme] +file = "README.md" +content-type = "text/markdown" + +[project.license] +text = "MIT" + +[project.urls] +Upstream = "https://github.com/asottile/reorder-python-imports" +Homepage = "https://github.com/wimglenn/reorder-python-imports-black" + +[project.scripts] +reorder-python-imports = "reorder_python_imports:main" +rpi = "reorder_python_imports:main" + +[tool.setuptools] +py-modules = [ + "reorder_python_imports", +] +license-files = [ + "LICENSE", +] +include-package-data = false + +[tool.distutils.bdist_wheel] +universal = true + +[tool.coverage.run] +plugins = [ + "covdefaults", +] + +[tool.mypy] +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_untyped_defs = true +warn_redundant_casts = true +warn_unused_ignores = true + +[[tool.mypy.overrides]] +module = [ + "testing.*", +] +disallow_untyped_defs = false + +[[tool.mypy.overrides]] +module = [ + "tests.*", +] +disallow_untyped_defs = false diff --git a/reorder_python_imports.py b/reorder_python_imports.py index d66dc4b..3de766f 100644 --- a/reorder_python_imports.py +++ b/reorder_python_imports.py @@ -94,10 +94,10 @@ def partition_source(src: str) -> tuple[str, list[str], str, str]: pre_import = False chunks.append((CodeType.IMPORT, s)) elif token_type is Tok.NEWLINE: - if s.isspace(): - tp = CodeType.NON_CODE - elif pre_import: + if pre_import: tp = CodeType.PRE_IMPORT_CODE + elif s.isspace(): + tp = CodeType.NON_CODE else: tp = CodeType.CODE diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 0c5a37e..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,3 +0,0 @@ -covdefaults -coverage -pytest diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 72062d9..0000000 --- a/setup.cfg +++ /dev/null @@ -1,47 +0,0 @@ -[metadata] -name = reorder_python_imports -version = 3.12.0 -description = Tool for reordering python imports -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/asottile/reorder-python-imports -author = Anthony Sottile -author_email = asottile@umich.edu -license = MIT -license_files = LICENSE -classifiers = - License :: OSI Approved :: MIT License - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: Implementation :: CPython - Programming Language :: Python :: Implementation :: PyPy - -[options] -py_modules = reorder_python_imports -install_requires = - classify-imports>=4.1 -python_requires = >=3.8 - -[options.entry_points] -console_scripts = - reorder-python-imports = reorder_python_imports:main - -[bdist_wheel] -universal = True - -[coverage:run] -plugins = covdefaults - -[mypy] -check_untyped_defs = true -disallow_any_generics = true -disallow_incomplete_defs = true -disallow_untyped_defs = true -warn_redundant_casts = true -warn_unused_ignores = true - -[mypy-testing.*] -disallow_untyped_defs = false - -[mypy-tests.*] -disallow_untyped_defs = false diff --git a/setup.py b/setup.py deleted file mode 100644 index 3d93aef..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -from setuptools import setup -setup() diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/reorder_python_imports_test.py b/tests/reorder_python_imports_test.py index c61c39a..e7bd49c 100644 --- a/tests/reorder_python_imports_test.py +++ b/tests/reorder_python_imports_test.py @@ -47,7 +47,7 @@ def test_tokenize_can_match_strings(s): @pytest.mark.parametrize( 's', ( - pytest.param('', id='trivial'), + pytest.param('\n', id='trivial'), pytest.param('#!/usr/bin/env python\n', id='shebang'), pytest.param('# -*- coding: UTF-8 -*-\n', id='source encoding'), pytest.param(' # coding: UTF-8\n', id='source encoding indented'), @@ -190,7 +190,7 @@ def test_partition_source_imports_only(s, expected): assert nl == '\n' -def test_partition_source_before_removes_newlines(): +def test_partition_source_before_leaves_newlines(): before, imports, after, nl = partition_source( '# comment here\n' '\n' @@ -198,6 +198,7 @@ def test_partition_source_before_removes_newlines(): ) assert before == ( '# comment here\n' + '\n' '# another comment here\n' ) assert imports == [] diff --git a/tests/test_black_interoperability.py b/tests/test_black_interoperability.py new file mode 100644 index 0000000..d76aab9 --- /dev/null +++ b/tests/test_black_interoperability.py @@ -0,0 +1,15 @@ +from textwrap import dedent + +from reorder_python_imports import fix_file_contents +from reorder_python_imports import Replacements + + +def test_leaves_newline_between_docstring_and_imports(): + contents = dedent('''\ + """module docstring""" + + import foo + ''') + expected = contents + actual = fix_file_contents(contents, to_remove=set(), to_replace=Replacements.make([])) + assert actual == expected diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 5370c94..0000000 --- a/tox.ini +++ /dev/null @@ -1,17 +0,0 @@ -[tox] -envlist = py,pre-commit - -[testenv] -deps = -rrequirements-dev.txt -commands = - coverage erase - coverage run -m pytest {posargs:tests} - coverage report - -[testenv:pre-commit] -skip_install = true -deps = pre-commit -commands = pre-commit run --all-files --show-diff-on-failure - -[pep8] -ignore = E265,E501,W504