Skip to content

Commit

Permalink
Merge pull request #429 from MarcoGorelli/universal_newlines_to_text
Browse files Browse the repository at this point in the history
upgrade subprocess.run(universal_newlines=True) to subprocess.run(text=True) in --py37-plus
  • Loading branch information
asottile committed May 1, 2021
2 parents 9979122 + f0e9373 commit 62f43a2
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 0 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@ _note_: `pyupgrade` is intentionally timid and will not create an f-string
if it would make the expression longer or if the substitution parameters are
anything but simple names or dotted names (as this can decrease readability).


### `subprocess.run`: replace `universal_newlines` with `text`

Availability:
- `--py37-plus` is passed on the commandline.

```diff
-output = subprocess.run(['foo'], universal_newlines=True)
+output = subprocess.run(['foo'], text=True)
```


### remove parentheses from `@functools.lru_cache()`

Availability:
Expand Down
1 change: 1 addition & 0 deletions pyupgrade/_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class State(NamedTuple):
'select',
'six',
'socket',
'subprocess',
'sys',
'typing',
))
Expand Down
61 changes: 61 additions & 0 deletions pyupgrade/_plugins/universal_newlines_to_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import ast
import functools
from typing import Iterable
from typing import List
from typing import Tuple

from tokenize_rt import Offset
from tokenize_rt import Token
from tokenize_rt import tokens_to_src

from pyupgrade._ast_helpers import ast_to_offset
from pyupgrade._ast_helpers import is_name_attr
from pyupgrade._data import register
from pyupgrade._data import State
from pyupgrade._data import TokenFunc
from pyupgrade._token_helpers import find_open_paren
from pyupgrade._token_helpers import parse_call_args


def _replace_universal_newlines_with_text(
i: int,
tokens: List[Token],
*,
arg_idx: int,
) -> None:
j = find_open_paren(tokens, i)
func_args, _ = parse_call_args(tokens, j)
src = tokens_to_src(tokens[slice(*func_args[arg_idx])])
new_src = src.replace('universal_newlines', 'text', 1)
tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_src)]


@register(ast.Call)
def visit_Call(
state: State,
node: ast.Call,
parent: ast.AST,
) -> Iterable[Tuple[Offset, TokenFunc]]:
if (
state.settings.min_version >= (3, 7) and
is_name_attr(
node.func,
state.from_imports,
'subprocess',
('run',),
)
):
kwarg_idx = next(
(
n
for n, keyword in enumerate(node.keywords)
if keyword.arg == 'universal_newlines'
),
None,
)
if kwarg_idx is not None:
func = functools.partial(
_replace_universal_newlines_with_text,
arg_idx=len(node.args) + kwarg_idx,
)
yield ast_to_offset(node), func
77 changes: 77 additions & 0 deletions tests/features/universal_newlines_to_text_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import pytest

from pyupgrade._data import Settings
from pyupgrade._main import _fix_plugins


@pytest.mark.parametrize(
('s', 'version'),
(
pytest.param(
'import subprocess\n'
'subprocess.run(["foo"], universal_newlines=True)\n',
(3,),
id='not Python3.7+',
),
pytest.param(
'from foo import run\n'
'run(["foo"], universal_newlines=True)\n',
(3, 7),
id='run imported, but not from subprocess',
),
pytest.param(
'from subprocess import run\n'
'run(["foo"], shell=True)\n',
(3, 7),
id='universal_newlines not used',
),
),
)
def test_fix_universal_newlines_to_text_noop(s, version):
assert _fix_plugins(s, settings=Settings(min_version=version)) == s


@pytest.mark.parametrize(
('s', 'expected'),
(
pytest.param(
'import subprocess\n'
'subprocess.run(["foo"], universal_newlines=True)\n',
'import subprocess\n'
'subprocess.run(["foo"], text=True)\n',
id='subprocess.run attribute',
),
pytest.param(
'from subprocess import run\n'
'run(["foo"], universal_newlines=True)\n',
'from subprocess import run\n'
'run(["foo"], text=True)\n',
id='run imported from subprocess',
),
pytest.param(
'from subprocess import run\n'
'run(["foo"], universal_newlines=universal_newlines)\n',
'from subprocess import run\n'
'run(["foo"], text=universal_newlines)\n',
id='universal_newlines appears as value',
),
pytest.param(
'from subprocess import run\n'
'run(["foo"], *foo, universal_newlines=universal_newlines)\n',
'from subprocess import run\n'
'run(["foo"], *foo, text=universal_newlines)\n',
id='with starargs',
),
),
)
def test_fix_universal_newlines_to_text(s, expected):
ret = _fix_plugins(s, settings=Settings(min_version=(3, 7)))
assert ret == expected

0 comments on commit 62f43a2

Please sign in to comment.