././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1657542385.1894023 pyupgrade-2.37.1/0000755000175000017500000000000000000000000015277 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1590210736.0 pyupgrade-2.37.1/LICENSE0000644000175000017500000000204300000000000016303 0ustar00asottileasottile00000000000000Copyright (c) 2017 Anthony Sottile Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1657542385.1894023 pyupgrade-2.37.1/PKG-INFO0000644000175000017500000003703600000000000016405 0ustar00asottileasottile00000000000000Metadata-Version: 2.1 Name: pyupgrade Version: 2.37.1 Summary: A tool to automatically upgrade syntax for newer versions. Home-page: https://github.com/asottile/pyupgrade Author: Anthony Sottile Author-email: asottile@umich.edu License: MIT Platform: UNKNOWN Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE [![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.pyupgrade?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=2&branchName=main) [![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/2/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=2&branchName=main) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/pyupgrade/main.svg)](https://results.pre-commit.ci/latest/github/asottile/pyupgrade/main) pyupgrade ========= A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language. ## Installation `pip install pyupgrade` ## 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/pyupgrade rev: v2.37.1 hooks: - id: pyupgrade ``` ## Implemented features ### Set literals ```diff -set(()) +set() -set([]) +set() -set((1,)) +{1} -set((1, 2)) +{1, 2} -set([1, 2]) +{1, 2} -set(x for x in y) +{x for x in y} -set([x for x in y]) +{x for x in y} ``` ### Dictionary comprehensions ```diff -dict((a, b) for a, b in y) +{a: b for a, b in y} -dict([(a, b) for a, b in y]) +{a: b for a, b in y} ``` ### Generator expressions for some built-in functions (pep 289) ```diff -min([i for i in range(3)]) +min(i for i in range(3)) -max([i for i in range(3)]) +max(i for i in range(3)) -sum([i for i in range(3)]) +sum(i for i in range(3)) -''.join([str(i) for i in range(3)]) +''.join(str(i) for i in range(3)) ``` ### Python2.7+ Format Specifiers ```diff -'{0} {1}'.format(1, 2) +'{} {}'.format(1, 2) -'{0}' '{1}'.format(1, 2) +'{}' '{}'.format(1, 2) ``` ### printf-style string formatting Availability: - Unless `--keep-percent-format` is passed. ```diff -'%s %s' % (a, b) +'{} {}'.format(a, b) -'%r %2f' % (a, b) +'{!r} {:2f}'.format(a, b) -'%(a)s %(b)s' % {'a': 1, 'b': 2} +'{a} {b}'.format(a=1, b=2) ``` ### Unicode literals Availability: - File imports `from __future__ import unicode_literals` - `--py3-plus` is passed on the commandline. ```diff -u'foo' +'foo' -u"foo" +'foo' -u'''foo''' +'''foo''' ``` ### Invalid escape sequences ```diff # strings with only invalid sequences become raw strings -'\d' +r'\d' # strings with mixed valid / invalid sequences get escaped -'\n\d' +'\n\\d' # `ur` is not a valid string prefix in python3 -u'\d' +u'\\d' # this fixes a syntax error in python3.3+ -'\N' +r'\N' # note: pyupgrade is timid in one case (that's usually a mistake) # in python2.x `'\u2603'` is the same as `'\\u2603'` without `unicode_literals` # but in python3.x, that's our friend ☃ ``` ### `is` / `is not` comparison to constant literals In python3.8+, comparison to literals becomes a `SyntaxWarning` as the success of those comparisons is implementation specific (due to common object caching). ```diff -x is 5 +x == 5 -x is not 5 +x != 5 -x is 'foo' +x == 'foo' ``` ### `ur` string literals `ur'...'` literals are not valid in python 3.x ```diff -ur'foo' +u'foo' -ur'\s' +u'\\s' # unicode escapes are left alone -ur'\u2603' +u'\u2603' -ur'\U0001f643' +u'\U0001f643' ``` ### `.encode()` to bytes literals ```diff -'foo'.encode() +b'foo' -'foo'.encode('ascii') +b'foo' -'foo'.encode('utf-8') +b'foo' -u'foo'.encode() +b'foo' -'\xa0'.encode('latin1') +b'\xa0' ``` ### Long literals ```diff -5L +5 -5l +5 -123456789123456789123456789L +123456789123456789123456789 ``` ### Octal literals ```diff -0755 +0o755 -05 +5 ``` ### extraneous parens in `print(...)` A fix for [python-modernize/python-modernize#178] ```diff # ok: printing an empty tuple print(()) # ok: printing a tuple print((1,)) # ok: parenthesized generator argument sum((i for i in range(3)), []) # fixed: -print(("foo")) +print("foo") ``` [python-modernize/python-modernize#178]: https://github.com/python-modernize/python-modernize/issues/178 ### unittest deprecated aliases Rewrites [deprecated unittest method aliases](https://docs.python.org/3/library/unittest.html#deprecated-aliases) to their non-deprecated forms. Availability: - More deprecated aliases are rewritten with `--py3-plus` ```diff from unittest import TestCase class MyTests(TestCase): def test_something(self): - self.failUnlessEqual(1, 1) + self.assertEqual(1, 1) - self.assertEquals(1, 1) + self.assertEqual(1, 1) ``` ### `super()` calls Availability: - `--py3-plus` is passed on the commandline. ```diff class C(Base): def f(self): - super(C, self).f() + super().f() ``` ### "new style" classes Availability: - `--py3-plus` is passed on the commandline. #### rewrites class declaration ```diff -class C(object): pass +class C: pass -class C(B, object): pass +class C(B): pass ``` #### removes `__metaclass__ = type` declaration ```diff class C: - __metaclass__ = type ``` ### forced `str("native")` literals Availability: - `--py3-plus` is passed on the commandline. ```diff -str() +'' -str("foo") +"foo" ``` ### `.encode("utf-8")` Availability: - `--py3-plus` is passed on the commandline. ```diff -"foo".encode("utf-8") +"foo".encode() ``` ### `# coding: ...` comment Availability: - `--py3-plus` is passed on the commandline. as of [PEP 3120], the default encoding for python source is UTF-8 ```diff -# coding: utf-8 x = 1 ``` [PEP 3120]: https://www.python.org/dev/peps/pep-3120/ ### `__future__` import removal Availability: - by default removes `nested_scopes`, `generators`, `with_statement` - `--py3-plus` will also remove `absolute_import` / `division` / `print_function` / `unicode_literals` - `--py37-plus` will also remove `generator_stop` ```diff -from __future__ import with_statement ``` ### Remove unnecessary py3-compat imports Availability: - `--py3-plus` is passed on the commandline. ```diff -from io import open -from six.moves import map -from builtins import object # python-future ``` ### import replacements Availability: - `--py3-plus` (and others) will replace imports see also [reorder-python-imports](https://github.com/asottile/reorder_python_imports#removing--rewriting-obsolete-six-imports) some examples: ```diff -from collections import deque, Mapping +from collections import deque +from collections.abc import Mapping ``` ```diff -from typing import Sequence +from collections.abc import Sequence ``` ```diff -from typing_extensions import Concatenate +from typing import Concatenate ``` ### rewrite `mock` imports Availability: - `--py3-plus` is passed on the commandline. - [Unless `--keep-mock` is passed on the commandline](https://github.com/asottile/pyupgrade/issues/314). ```diff -from mock import patch +from unittest.mock import patch ``` ### `yield` => `yield from` Availability: - `--py3-plus` is passed on the commandline. ```diff def f(): - for x in y: - yield x + yield from y - for a, b in c: - yield (a, b) + yield from c ``` ### Python2 and old Python3.x blocks Availability: - `--py3-plus` is passed on the commandline. ```diff import sys -if sys.version_info < (3,): # also understands `six.PY2` (and `not`), `six.PY3` (and `not`) - print('py2') -else: - print('py3') +print('py3') ``` Availability: - `--py36-plus` will remove Python <= 3.5 only blocks - `--py37-plus` will remove Python <= 3.6 only blocks - so on and so forth ```diff # using --py36-plus for this example import sys -if sys.version_info < (3, 6): - print('py3.5') -else: - print('py3.6+') +print('py3.6+') -if sys.version_info <= (3, 5): - print('py3.5') -else: - print('py3.6+') +print('py3.6+') -if sys.version_info >= (3, 6): - print('py3.6+') -else: - print('py3.5') +print('py3.6+') ``` Note that `if` blocks without an `else` will not be rewritten as it could introduce a syntax error. ### remove `six` compatibility code Availability: - `--py3-plus` is passed on the commandline. ```diff -six.text_type +str -six.binary_type +bytes -six.class_types +(type,) -six.string_types +(str,) -six.integer_types +(int,) -six.unichr +chr -six.iterbytes +iter -six.print_(...) +print(...) -six.exec_(c, g, l) +exec(c, g, l) -six.advance_iterator(it) +next(it) -six.next(it) +next(it) -six.callable(x) +callable(x) -six.moves.range(x) +range(x) -six.moves.xrange(x) +range(x) -from six import text_type -text_type +str -@six.python_2_unicode_compatible class C: def __str__(self): return u'C()' -class C(six.Iterator): pass +class C: pass -class C(six.with_metaclass(M, B)): pass +class C(B, metaclass=M): pass -@six.add_metaclass(M) -class C(B): pass +class C(B, metaclass=M): pass -isinstance(..., six.class_types) +isinstance(..., type) -issubclass(..., six.integer_types) +issubclass(..., int) -isinstance(..., six.string_types) +isinstance(..., str) -six.b('...') +b'...' -six.u('...') +'...' -six.byte2int(bs) +bs[0] -six.indexbytes(bs, i) +bs[i] -six.int2byte(i) +bytes((i,)) -six.iteritems(dct) +dct.items() -six.iterkeys(dct) +dct.keys() -six.itervalues(dct) +dct.values() -next(six.iteritems(dct)) +next(iter(dct.items())) -next(six.iterkeys(dct)) +next(iter(dct.keys())) -next(six.itervalues(dct)) +next(iter(dct.values())) -six.viewitems(dct) +dct.items() -six.viewkeys(dct) +dct.keys() -six.viewvalues(dct) +dct.values() -six.create_unbound_method(fn, cls) +fn -six.get_unbound_function(meth) +meth -six.get_method_function(meth) +meth.__func__ -six.get_method_self(meth) +meth.__self__ -six.get_function_closure(fn) +fn.__closure__ -six.get_function_code(fn) +fn.__code__ -six.get_function_defaults(fn) +fn.__defaults__ -six.get_function_globals(fn) +fn.__globals__ -six.raise_from(exc, exc_from) +raise exc from exc_from -six.reraise(tp, exc, tb) +raise exc.with_traceback(tb) -six.reraise(*sys.exc_info()) +raise -six.assertCountEqual(self, a1, a2) +self.assertCountEqual(a1, a2) -six.assertRaisesRegex(self, e, r, fn) +self.assertRaisesRegex(e, r, fn) -six.assertRegex(self, s, r) +self.assertRegex(s, r) # note: only for *literals* -six.ensure_binary('...') +b'...' -six.ensure_str('...') +'...' -six.ensure_text('...') +'...' ``` ### `open` alias Availability: - `--py3-plus` is passed on the commandline. ```diff -with io.open('f.txt') as f: +with open('f.txt') as f: ... ``` ### redundant `open` modes Availability: - `--py3-plus` is passed on the commandline. ```diff -open("foo", "U") +open("foo") -open("foo", "Ur") +open("foo") -open("foo", "Ub") +open("foo", "rb") -open("foo", "rUb") +open("foo", "rb") -open("foo", "r") +open("foo") -open("foo", "rt") +open("foo") -open("f", "r", encoding="UTF-8") +open("f", encoding="UTF-8") ``` ### `OSError` aliases Availability: - `--py3-plus` is passed on the commandline. ```diff # also understands: # - IOError # - WindowsError # - mmap.error and uses of `from mmap import error` # - select.error and uses of `from select import error` # - socket.error and uses of `from socket import error` def throw(): - raise EnvironmentError('boom') + raise OSError('boom') def catch(): try: throw() - except EnvironmentError: + except OSError: handle_error() ``` ### `typing.Text` str alias Availability: - `--py3-plus` is passed on the commandline. ```diff -def f(x: Text) -> None: +def f(x: str) -> None: ... ``` ### Unpacking list comprehensions Availability: - `--py3-plus` is passed on the commandline. ```diff -foo, bar, baz = [fn(x) for x in items] +foo, bar, baz = (fn(x) for x in items) ``` ### Rewrite `xml.etree.cElementTree` to `xml.etree.ElementTree` Availability: - `--py3-plus` is passed on the commandline. ```diff -import xml.etree.cElementTree as ET +import xml.etree.ElementTree as ET -from xml.etree.cElementTree import XML +from xml.etree.ElementTree import XML ``` ### Rewrite `type` of primitive Availability: - `--py3-plus` is passed on the commandline. ```diff -type('') +str -type(b'') +bytes -type(0) +int -type(0.) +float ``` ### `typing.NamedTuple` / `typing.TypedDict` py36+ syntax Availability: - `--py36-plus` is passed on the commandline. ```diff -NT = typing.NamedTuple('NT', [('a', int), ('b', Tuple[str, ...])]) +class NT(typing.NamedTuple): + a: int + b: Tuple[str, ...] -D1 = typing.TypedDict('D1', a=int, b=str) +class D1(typing.TypedDict): + a: int + b: str -D2 = typing.TypedDict('D2', {'a': int, 'b': str}) +class D2(typing.TypedDict): + a: int + b: str ``` ### f-strings Availability: - `--py36-plus` is passed on the commandline. ```diff -'{foo} {bar}'.format(foo=foo, bar=bar) +f'{foo} {bar}' -'{} {}'.format(foo, bar) +f'{foo} {bar}' -'{} {}'.format(foo.bar, baz.womp) +f'{foo.bar} {baz.womp}' -'{} {}'.format(f(), g()) +f'{f()} {g()}' -'{x}'.format(**locals()) +f'{x}' ``` _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 sufficiently complicated (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) ``` ### `subprocess.run`: replace `stdout=subprocess.PIPE, stderr=subprocess.PIPE` with `capture_output=True` Availability: - `--py37-plus` is passed on the commandline. ```diff -output = subprocess.run(['foo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +output = subprocess.run(['foo'], capture_output=True) ``` ### remove parentheses from `@functools.lru_cache()` Availability: - `--py38-plus` is passed on the commandline. ```diff import functools -@functools.lru_cache() +@functools.lru_cache def expensive(): ... ``` ### replace `@functools.lru_cache(maxsize=None)` with shorthand Availability: - `--py39-plus` is passed on the commandline. ```diff import functools -@functools.lru_cache(maxsize=None) +@functools.cache def expensive(): ... ``` ### pep 585 typing rewrites Availability: - File imports `from __future__ import annotations` - Unless `--keep-runtime-typing` is passed on the commandline. - `--py39-plus` is passed on the commandline. ```diff -def f(x: List[str]) -> None: +def f(x: list[str]) -> None: ... ``` ### remove unnecessary abspath Availability: - `--py39-plus` is passed on the commandline. ```diff from os.path import abspath -abspath(__file__) +__file__ ``` ### pep 604 typing rewrites Availability: - File imports `from __future__ import annotations` - Unless `--keep-runtime-typing` is passed on the commandline. - `--py310-plus` is passed on the commandline. ```diff -def f() -> Optional[str]: +def f() -> str | None: ... ``` ```diff -def f() -> Union[int, str]: +def f() -> int | str: ... ``` ### remove quoted annotations Availability: - File imports `from __future__ import annotations` ```diff -def f(x: 'queue.Queue[int]') -> C: +def f(x: queue.Queue[int]) -> C: ``` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542383.0 pyupgrade-2.37.1/README.md0000644000175000017500000003532200000000000016563 0ustar00asottileasottile00000000000000[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.pyupgrade?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=2&branchName=main) [![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/2/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=2&branchName=main) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/pyupgrade/main.svg)](https://results.pre-commit.ci/latest/github/asottile/pyupgrade/main) pyupgrade ========= A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language. ## Installation `pip install pyupgrade` ## 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/pyupgrade rev: v2.37.1 hooks: - id: pyupgrade ``` ## Implemented features ### Set literals ```diff -set(()) +set() -set([]) +set() -set((1,)) +{1} -set((1, 2)) +{1, 2} -set([1, 2]) +{1, 2} -set(x for x in y) +{x for x in y} -set([x for x in y]) +{x for x in y} ``` ### Dictionary comprehensions ```diff -dict((a, b) for a, b in y) +{a: b for a, b in y} -dict([(a, b) for a, b in y]) +{a: b for a, b in y} ``` ### Generator expressions for some built-in functions (pep 289) ```diff -min([i for i in range(3)]) +min(i for i in range(3)) -max([i for i in range(3)]) +max(i for i in range(3)) -sum([i for i in range(3)]) +sum(i for i in range(3)) -''.join([str(i) for i in range(3)]) +''.join(str(i) for i in range(3)) ``` ### Python2.7+ Format Specifiers ```diff -'{0} {1}'.format(1, 2) +'{} {}'.format(1, 2) -'{0}' '{1}'.format(1, 2) +'{}' '{}'.format(1, 2) ``` ### printf-style string formatting Availability: - Unless `--keep-percent-format` is passed. ```diff -'%s %s' % (a, b) +'{} {}'.format(a, b) -'%r %2f' % (a, b) +'{!r} {:2f}'.format(a, b) -'%(a)s %(b)s' % {'a': 1, 'b': 2} +'{a} {b}'.format(a=1, b=2) ``` ### Unicode literals Availability: - File imports `from __future__ import unicode_literals` - `--py3-plus` is passed on the commandline. ```diff -u'foo' +'foo' -u"foo" +'foo' -u'''foo''' +'''foo''' ``` ### Invalid escape sequences ```diff # strings with only invalid sequences become raw strings -'\d' +r'\d' # strings with mixed valid / invalid sequences get escaped -'\n\d' +'\n\\d' # `ur` is not a valid string prefix in python3 -u'\d' +u'\\d' # this fixes a syntax error in python3.3+ -'\N' +r'\N' # note: pyupgrade is timid in one case (that's usually a mistake) # in python2.x `'\u2603'` is the same as `'\\u2603'` without `unicode_literals` # but in python3.x, that's our friend ☃ ``` ### `is` / `is not` comparison to constant literals In python3.8+, comparison to literals becomes a `SyntaxWarning` as the success of those comparisons is implementation specific (due to common object caching). ```diff -x is 5 +x == 5 -x is not 5 +x != 5 -x is 'foo' +x == 'foo' ``` ### `ur` string literals `ur'...'` literals are not valid in python 3.x ```diff -ur'foo' +u'foo' -ur'\s' +u'\\s' # unicode escapes are left alone -ur'\u2603' +u'\u2603' -ur'\U0001f643' +u'\U0001f643' ``` ### `.encode()` to bytes literals ```diff -'foo'.encode() +b'foo' -'foo'.encode('ascii') +b'foo' -'foo'.encode('utf-8') +b'foo' -u'foo'.encode() +b'foo' -'\xa0'.encode('latin1') +b'\xa0' ``` ### Long literals ```diff -5L +5 -5l +5 -123456789123456789123456789L +123456789123456789123456789 ``` ### Octal literals ```diff -0755 +0o755 -05 +5 ``` ### extraneous parens in `print(...)` A fix for [python-modernize/python-modernize#178] ```diff # ok: printing an empty tuple print(()) # ok: printing a tuple print((1,)) # ok: parenthesized generator argument sum((i for i in range(3)), []) # fixed: -print(("foo")) +print("foo") ``` [python-modernize/python-modernize#178]: https://github.com/python-modernize/python-modernize/issues/178 ### unittest deprecated aliases Rewrites [deprecated unittest method aliases](https://docs.python.org/3/library/unittest.html#deprecated-aliases) to their non-deprecated forms. Availability: - More deprecated aliases are rewritten with `--py3-plus` ```diff from unittest import TestCase class MyTests(TestCase): def test_something(self): - self.failUnlessEqual(1, 1) + self.assertEqual(1, 1) - self.assertEquals(1, 1) + self.assertEqual(1, 1) ``` ### `super()` calls Availability: - `--py3-plus` is passed on the commandline. ```diff class C(Base): def f(self): - super(C, self).f() + super().f() ``` ### "new style" classes Availability: - `--py3-plus` is passed on the commandline. #### rewrites class declaration ```diff -class C(object): pass +class C: pass -class C(B, object): pass +class C(B): pass ``` #### removes `__metaclass__ = type` declaration ```diff class C: - __metaclass__ = type ``` ### forced `str("native")` literals Availability: - `--py3-plus` is passed on the commandline. ```diff -str() +'' -str("foo") +"foo" ``` ### `.encode("utf-8")` Availability: - `--py3-plus` is passed on the commandline. ```diff -"foo".encode("utf-8") +"foo".encode() ``` ### `# coding: ...` comment Availability: - `--py3-plus` is passed on the commandline. as of [PEP 3120], the default encoding for python source is UTF-8 ```diff -# coding: utf-8 x = 1 ``` [PEP 3120]: https://www.python.org/dev/peps/pep-3120/ ### `__future__` import removal Availability: - by default removes `nested_scopes`, `generators`, `with_statement` - `--py3-plus` will also remove `absolute_import` / `division` / `print_function` / `unicode_literals` - `--py37-plus` will also remove `generator_stop` ```diff -from __future__ import with_statement ``` ### Remove unnecessary py3-compat imports Availability: - `--py3-plus` is passed on the commandline. ```diff -from io import open -from six.moves import map -from builtins import object # python-future ``` ### import replacements Availability: - `--py3-plus` (and others) will replace imports see also [reorder-python-imports](https://github.com/asottile/reorder_python_imports#removing--rewriting-obsolete-six-imports) some examples: ```diff -from collections import deque, Mapping +from collections import deque +from collections.abc import Mapping ``` ```diff -from typing import Sequence +from collections.abc import Sequence ``` ```diff -from typing_extensions import Concatenate +from typing import Concatenate ``` ### rewrite `mock` imports Availability: - `--py3-plus` is passed on the commandline. - [Unless `--keep-mock` is passed on the commandline](https://github.com/asottile/pyupgrade/issues/314). ```diff -from mock import patch +from unittest.mock import patch ``` ### `yield` => `yield from` Availability: - `--py3-plus` is passed on the commandline. ```diff def f(): - for x in y: - yield x + yield from y - for a, b in c: - yield (a, b) + yield from c ``` ### Python2 and old Python3.x blocks Availability: - `--py3-plus` is passed on the commandline. ```diff import sys -if sys.version_info < (3,): # also understands `six.PY2` (and `not`), `six.PY3` (and `not`) - print('py2') -else: - print('py3') +print('py3') ``` Availability: - `--py36-plus` will remove Python <= 3.5 only blocks - `--py37-plus` will remove Python <= 3.6 only blocks - so on and so forth ```diff # using --py36-plus for this example import sys -if sys.version_info < (3, 6): - print('py3.5') -else: - print('py3.6+') +print('py3.6+') -if sys.version_info <= (3, 5): - print('py3.5') -else: - print('py3.6+') +print('py3.6+') -if sys.version_info >= (3, 6): - print('py3.6+') -else: - print('py3.5') +print('py3.6+') ``` Note that `if` blocks without an `else` will not be rewritten as it could introduce a syntax error. ### remove `six` compatibility code Availability: - `--py3-plus` is passed on the commandline. ```diff -six.text_type +str -six.binary_type +bytes -six.class_types +(type,) -six.string_types +(str,) -six.integer_types +(int,) -six.unichr +chr -six.iterbytes +iter -six.print_(...) +print(...) -six.exec_(c, g, l) +exec(c, g, l) -six.advance_iterator(it) +next(it) -six.next(it) +next(it) -six.callable(x) +callable(x) -six.moves.range(x) +range(x) -six.moves.xrange(x) +range(x) -from six import text_type -text_type +str -@six.python_2_unicode_compatible class C: def __str__(self): return u'C()' -class C(six.Iterator): pass +class C: pass -class C(six.with_metaclass(M, B)): pass +class C(B, metaclass=M): pass -@six.add_metaclass(M) -class C(B): pass +class C(B, metaclass=M): pass -isinstance(..., six.class_types) +isinstance(..., type) -issubclass(..., six.integer_types) +issubclass(..., int) -isinstance(..., six.string_types) +isinstance(..., str) -six.b('...') +b'...' -six.u('...') +'...' -six.byte2int(bs) +bs[0] -six.indexbytes(bs, i) +bs[i] -six.int2byte(i) +bytes((i,)) -six.iteritems(dct) +dct.items() -six.iterkeys(dct) +dct.keys() -six.itervalues(dct) +dct.values() -next(six.iteritems(dct)) +next(iter(dct.items())) -next(six.iterkeys(dct)) +next(iter(dct.keys())) -next(six.itervalues(dct)) +next(iter(dct.values())) -six.viewitems(dct) +dct.items() -six.viewkeys(dct) +dct.keys() -six.viewvalues(dct) +dct.values() -six.create_unbound_method(fn, cls) +fn -six.get_unbound_function(meth) +meth -six.get_method_function(meth) +meth.__func__ -six.get_method_self(meth) +meth.__self__ -six.get_function_closure(fn) +fn.__closure__ -six.get_function_code(fn) +fn.__code__ -six.get_function_defaults(fn) +fn.__defaults__ -six.get_function_globals(fn) +fn.__globals__ -six.raise_from(exc, exc_from) +raise exc from exc_from -six.reraise(tp, exc, tb) +raise exc.with_traceback(tb) -six.reraise(*sys.exc_info()) +raise -six.assertCountEqual(self, a1, a2) +self.assertCountEqual(a1, a2) -six.assertRaisesRegex(self, e, r, fn) +self.assertRaisesRegex(e, r, fn) -six.assertRegex(self, s, r) +self.assertRegex(s, r) # note: only for *literals* -six.ensure_binary('...') +b'...' -six.ensure_str('...') +'...' -six.ensure_text('...') +'...' ``` ### `open` alias Availability: - `--py3-plus` is passed on the commandline. ```diff -with io.open('f.txt') as f: +with open('f.txt') as f: ... ``` ### redundant `open` modes Availability: - `--py3-plus` is passed on the commandline. ```diff -open("foo", "U") +open("foo") -open("foo", "Ur") +open("foo") -open("foo", "Ub") +open("foo", "rb") -open("foo", "rUb") +open("foo", "rb") -open("foo", "r") +open("foo") -open("foo", "rt") +open("foo") -open("f", "r", encoding="UTF-8") +open("f", encoding="UTF-8") ``` ### `OSError` aliases Availability: - `--py3-plus` is passed on the commandline. ```diff # also understands: # - IOError # - WindowsError # - mmap.error and uses of `from mmap import error` # - select.error and uses of `from select import error` # - socket.error and uses of `from socket import error` def throw(): - raise EnvironmentError('boom') + raise OSError('boom') def catch(): try: throw() - except EnvironmentError: + except OSError: handle_error() ``` ### `typing.Text` str alias Availability: - `--py3-plus` is passed on the commandline. ```diff -def f(x: Text) -> None: +def f(x: str) -> None: ... ``` ### Unpacking list comprehensions Availability: - `--py3-plus` is passed on the commandline. ```diff -foo, bar, baz = [fn(x) for x in items] +foo, bar, baz = (fn(x) for x in items) ``` ### Rewrite `xml.etree.cElementTree` to `xml.etree.ElementTree` Availability: - `--py3-plus` is passed on the commandline. ```diff -import xml.etree.cElementTree as ET +import xml.etree.ElementTree as ET -from xml.etree.cElementTree import XML +from xml.etree.ElementTree import XML ``` ### Rewrite `type` of primitive Availability: - `--py3-plus` is passed on the commandline. ```diff -type('') +str -type(b'') +bytes -type(0) +int -type(0.) +float ``` ### `typing.NamedTuple` / `typing.TypedDict` py36+ syntax Availability: - `--py36-plus` is passed on the commandline. ```diff -NT = typing.NamedTuple('NT', [('a', int), ('b', Tuple[str, ...])]) +class NT(typing.NamedTuple): + a: int + b: Tuple[str, ...] -D1 = typing.TypedDict('D1', a=int, b=str) +class D1(typing.TypedDict): + a: int + b: str -D2 = typing.TypedDict('D2', {'a': int, 'b': str}) +class D2(typing.TypedDict): + a: int + b: str ``` ### f-strings Availability: - `--py36-plus` is passed on the commandline. ```diff -'{foo} {bar}'.format(foo=foo, bar=bar) +f'{foo} {bar}' -'{} {}'.format(foo, bar) +f'{foo} {bar}' -'{} {}'.format(foo.bar, baz.womp) +f'{foo.bar} {baz.womp}' -'{} {}'.format(f(), g()) +f'{f()} {g()}' -'{x}'.format(**locals()) +f'{x}' ``` _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 sufficiently complicated (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) ``` ### `subprocess.run`: replace `stdout=subprocess.PIPE, stderr=subprocess.PIPE` with `capture_output=True` Availability: - `--py37-plus` is passed on the commandline. ```diff -output = subprocess.run(['foo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +output = subprocess.run(['foo'], capture_output=True) ``` ### remove parentheses from `@functools.lru_cache()` Availability: - `--py38-plus` is passed on the commandline. ```diff import functools -@functools.lru_cache() +@functools.lru_cache def expensive(): ... ``` ### replace `@functools.lru_cache(maxsize=None)` with shorthand Availability: - `--py39-plus` is passed on the commandline. ```diff import functools -@functools.lru_cache(maxsize=None) +@functools.cache def expensive(): ... ``` ### pep 585 typing rewrites Availability: - File imports `from __future__ import annotations` - Unless `--keep-runtime-typing` is passed on the commandline. - `--py39-plus` is passed on the commandline. ```diff -def f(x: List[str]) -> None: +def f(x: list[str]) -> None: ... ``` ### remove unnecessary abspath Availability: - `--py39-plus` is passed on the commandline. ```diff from os.path import abspath -abspath(__file__) +__file__ ``` ### pep 604 typing rewrites Availability: - File imports `from __future__ import annotations` - Unless `--keep-runtime-typing` is passed on the commandline. - `--py310-plus` is passed on the commandline. ```diff -def f() -> Optional[str]: +def f() -> str | None: ... ``` ```diff -def f() -> Union[int, str]: +def f() -> int | str: ... ``` ### remove quoted annotations Availability: - File imports `from __future__ import annotations` ```diff -def f(x: 'queue.Queue[int]') -> C: +def f(x: queue.Queue[int]) -> C: ``` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1657542385.1854024 pyupgrade-2.37.1/pyupgrade/0000755000175000017500000000000000000000000017277 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623422590.0 pyupgrade-2.37.1/pyupgrade/__init__.py0000644000175000017500000000000000000000000021376 0ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/__main__.py0000644000175000017500000000017600000000000021375 0ustar00asottileasottile00000000000000from __future__ import annotations from pyupgrade._main import main if __name__ == '__main__': raise SystemExit(main()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_ast_helpers.py0000644000175000017500000000264500000000000022330 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import warnings from typing import Container from tokenize_rt import Offset def ast_parse(contents_text: str) -> ast.Module: # intentionally ignore warnings, we might be fixing warning-ridden syntax with warnings.catch_warnings(): warnings.simplefilter('ignore') return ast.parse(contents_text.encode()) def ast_to_offset(node: ast.expr | ast.stmt) -> Offset: return Offset(node.lineno, node.col_offset) def is_name_attr( node: ast.AST, imports: dict[str, set[str]], mods: tuple[str, ...], names: Container[str], ) -> bool: return ( isinstance(node, ast.Name) and node.id in names and any(node.id in imports[mod] for mod in mods) ) or ( isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and node.value.id in mods and node.attr in names ) def has_starargs(call: ast.Call) -> bool: return ( any(k.arg is None for k in call.keywords) or any(isinstance(a, ast.Starred) for a in call.args) ) def contains_await(node: ast.AST) -> bool: for node_ in ast.walk(node): if isinstance(node_, ast.Await): return True else: return False def is_async_listcomp(node: ast.ListComp) -> bool: return ( any(gen.is_async for gen in node.generators) or contains_await(node) ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_data.py0000644000175000017500000000626600000000000020733 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import collections import pkgutil from typing import Callable from typing import Iterable from typing import List from typing import NamedTuple from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade import _plugins if TYPE_CHECKING: from typing import Protocol else: Protocol = object Version = Tuple[int, ...] class Settings(NamedTuple): min_version: Version = (2, 7) keep_percent_format: bool = False keep_mock: bool = False keep_runtime_typing: bool = False class State(NamedTuple): settings: Settings from_imports: dict[str, set[str]] in_annotation: bool = False AST_T = TypeVar('AST_T', bound=ast.AST) TokenFunc = Callable[[int, List[Token]], None] ASTFunc = Callable[[State, AST_T, ast.AST], Iterable[Tuple[Offset, TokenFunc]]] RECORD_FROM_IMPORTS = frozenset(( '__future__', 'os.path', 'functools', 'mmap', 'select', 'six', 'six.moves', 'socket', 'subprocess', 'sys', 'typing', 'typing_extensions', )) FUNCS = collections.defaultdict(list) def register(tp: type[AST_T]) -> Callable[[ASTFunc[AST_T]], ASTFunc[AST_T]]: def register_decorator(func: ASTFunc[AST_T]) -> ASTFunc[AST_T]: FUNCS[tp].append(func) return func return register_decorator class ASTCallbackMapping(Protocol): def __getitem__(self, tp: type[AST_T]) -> list[ASTFunc[AST_T]]: ... def visit( funcs: ASTCallbackMapping, tree: ast.Module, settings: Settings, ) -> dict[Offset, list[TokenFunc]]: initial_state = State( settings=settings, from_imports=collections.defaultdict(set), ) nodes: list[tuple[State, ast.AST, ast.AST]] = [(initial_state, tree, tree)] ret = collections.defaultdict(list) while nodes: state, node, parent = nodes.pop() tp = type(node) for ast_func in funcs[tp]: for offset, token_func in ast_func(state, node, parent): ret[offset].append(token_func) if ( isinstance(node, ast.ImportFrom) and not node.level and node.module in RECORD_FROM_IMPORTS ): state.from_imports[node.module].update( name.name for name in node.names if not name.asname ) for name in reversed(node._fields): value = getattr(node, name) if name in {'annotation', 'returns'}: next_state = state._replace(in_annotation=True) else: next_state = state if isinstance(value, ast.AST): nodes.append((next_state, value, node)) elif isinstance(value, list): for value in reversed(value): if isinstance(value, ast.AST): nodes.append((next_state, value, node)) return ret def _import_plugins() -> None: plugins_path = _plugins.__path__ mod_infos = pkgutil.walk_packages(plugins_path, f'{_plugins.__name__}.') for _, name, _ in mod_infos: __import__(name, fromlist=['_trash']) _import_plugins() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657465213.0 pyupgrade-2.37.1/pyupgrade/_main.py0000644000175000017500000003563000000000000020743 0ustar00asottileasottile00000000000000from __future__ import annotations import argparse import ast import re import sys import tokenize from typing import Match from typing import Sequence from tokenize_rt import NON_CODING_TOKENS from tokenize_rt import parse_string_literal from tokenize_rt import reversed_enumerate from tokenize_rt import rfind_string_parts from tokenize_rt import src_to_tokens from tokenize_rt import Token from tokenize_rt import tokens_to_src from tokenize_rt import UNIMPORTANT_WS from pyupgrade._ast_helpers import ast_parse from pyupgrade._data import FUNCS from pyupgrade._data import Settings from pyupgrade._data import Version from pyupgrade._data import visit from pyupgrade._string_helpers import DotFormatPart from pyupgrade._string_helpers import is_codec from pyupgrade._string_helpers import parse_format from pyupgrade._string_helpers import unparse_parsed_string from pyupgrade._token_helpers import CLOSING from pyupgrade._token_helpers import OPENING from pyupgrade._token_helpers import remove_brace def inty(s: str) -> bool: try: int(s) return True except (ValueError, TypeError): return False def _fixup_dedent_tokens(tokens: list[Token]) -> None: """For whatever reason the DEDENT / UNIMPORTANT_WS tokens are misordered | if True: | if True: | pass | else: |^ ^- DEDENT |+----UNIMPORTANT_WS """ for i, token in enumerate(tokens): if token.name == UNIMPORTANT_WS and tokens[i + 1].name == 'DEDENT': tokens[i], tokens[i + 1] = tokens[i + 1], tokens[i] def _fix_plugins(contents_text: str, settings: Settings) -> str: try: ast_obj = ast_parse(contents_text) except SyntaxError: return contents_text callbacks = visit(FUNCS, ast_obj, settings) if not callbacks: return contents_text try: tokens = src_to_tokens(contents_text) except tokenize.TokenError: # pragma: no cover (bpo-2180) return contents_text _fixup_dedent_tokens(tokens) for i, token in reversed_enumerate(tokens): if not token.src: continue # though this is a defaultdict, by using `.get()` this function's # self time is almost 50% faster for callback in callbacks.get(token.offset, ()): callback(i, tokens) return tokens_to_src(tokens).lstrip() def _imports_future(contents_text: str, future_name: str) -> bool: try: ast_obj = ast_parse(contents_text) except SyntaxError: return False for node in ast_obj.body: # Docstring if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str): continue elif isinstance(node, ast.ImportFrom): if ( node.level == 0 and node.module == '__future__' and any(name.name == future_name for name in node.names) ): return True elif node.module == '__future__': continue else: return False else: return False return False # https://docs.python.org/3/reference/lexical_analysis.html ESCAPE_STARTS = frozenset(( '\n', '\r', '\\', "'", '"', 'a', 'b', 'f', 'n', 'r', 't', 'v', '0', '1', '2', '3', '4', '5', '6', '7', # octal escapes 'x', # hex escapes )) ESCAPE_RE = re.compile(r'\\.', re.DOTALL) NAMED_ESCAPE_NAME = re.compile(r'\{[^}]+\}') def _fix_escape_sequences(token: Token) -> Token: prefix, rest = parse_string_literal(token.src) actual_prefix = prefix.lower() if 'r' in actual_prefix or '\\' not in rest: return token is_bytestring = 'b' in actual_prefix def _is_valid_escape(match: Match[str]) -> bool: c = match.group()[1] return ( c in ESCAPE_STARTS or (not is_bytestring and c in 'uU') or ( not is_bytestring and c == 'N' and bool(NAMED_ESCAPE_NAME.match(rest, match.end())) ) ) has_valid_escapes = False has_invalid_escapes = False for match in ESCAPE_RE.finditer(rest): if _is_valid_escape(match): has_valid_escapes = True else: has_invalid_escapes = True def cb(match: Match[str]) -> str: matched = match.group() if _is_valid_escape(match): return matched else: return fr'\{matched}' if has_invalid_escapes and (has_valid_escapes or 'u' in actual_prefix): return token._replace(src=prefix + ESCAPE_RE.sub(cb, rest)) elif has_invalid_escapes and not has_valid_escapes: return token._replace(src=prefix + 'r' + rest) else: return token def _remove_u_prefix(token: Token) -> Token: prefix, rest = parse_string_literal(token.src) if 'u' not in prefix.lower(): return token else: new_prefix = prefix.replace('u', '').replace('U', '') return token._replace(src=new_prefix + rest) def _fix_ur_literals(token: Token) -> Token: prefix, rest = parse_string_literal(token.src) if prefix.lower() != 'ur': return token else: def cb(match: Match[str]) -> str: escape = match.group() if escape[1].lower() == 'u': return escape else: return '\\' + match.group() rest = ESCAPE_RE.sub(cb, rest) prefix = prefix.replace('r', '').replace('R', '') return token._replace(src=prefix + rest) def _fix_long(src: str) -> str: return src.rstrip('lL') def _fix_octal(s: str) -> str: if not s.startswith('0') or not s.isdigit() or s == len(s) * '0': return s elif len(s) == 2: return s[1:] else: return '0o' + s[1:] def _fix_extraneous_parens(tokens: list[Token], i: int) -> None: # search forward for another non-coding token i += 1 while tokens[i].name in NON_CODING_TOKENS: i += 1 # if we did not find another brace, return immediately if tokens[i].src != '(': return start = i depth = 1 while depth: i += 1 # found comma or yield at depth 1: this is a tuple / coroutine if depth == 1 and tokens[i].src in {',', 'yield'}: return elif tokens[i].src in OPENING: depth += 1 elif tokens[i].src in CLOSING: depth -= 1 end = i # empty tuple if all(t.name in NON_CODING_TOKENS for t in tokens[start + 1:i]): return # search forward for the next non-coding token i += 1 while tokens[i].name in NON_CODING_TOKENS: i += 1 if tokens[i].src == ')': remove_brace(tokens, end) remove_brace(tokens, start) def _remove_fmt(tup: DotFormatPart) -> DotFormatPart: if tup[1] is None: return tup else: return (tup[0], '', tup[2], tup[3]) def _fix_format_literal(tokens: list[Token], end: int) -> None: parts = rfind_string_parts(tokens, end) parsed_parts = [] last_int = -1 for i in parts: # f'foo {0}'.format(...) would get turned into a SyntaxError prefix, _ = parse_string_literal(tokens[i].src) if 'f' in prefix.lower(): return try: parsed = parse_format(tokens[i].src) except ValueError: # the format literal was malformed, skip it return # The last segment will always be the end of the string and not a # format, slice avoids the `None` format key for _, fmtkey, spec, _ in parsed[:-1]: if ( fmtkey is not None and inty(fmtkey) and int(fmtkey) == last_int + 1 and spec is not None and '{' not in spec ): last_int += 1 else: return parsed_parts.append([_remove_fmt(tup) for tup in parsed]) for i, parsed in zip(parts, parsed_parts): tokens[i] = tokens[i]._replace(src=unparse_parsed_string(parsed)) def _fix_encode_to_binary(tokens: list[Token], i: int) -> None: parts = rfind_string_parts(tokens, i - 2) if not parts: return # .encode() if ( i + 2 < len(tokens) and tokens[i + 1].src == '(' and tokens[i + 2].src == ')' ): victims = slice(i - 1, i + 3) latin1_ok = False # .encode('encoding') elif ( i + 3 < len(tokens) and tokens[i + 1].src == '(' and tokens[i + 2].name == 'STRING' and tokens[i + 3].src == ')' ): victims = slice(i - 1, i + 4) prefix, rest = parse_string_literal(tokens[i + 2].src) if 'f' in prefix.lower(): return encoding = ast.literal_eval(prefix + rest) if is_codec(encoding, 'ascii') or is_codec(encoding, 'utf-8'): latin1_ok = False elif is_codec(encoding, 'iso8859-1'): latin1_ok = True else: return else: return for part in parts: prefix, rest = parse_string_literal(tokens[part].src) escapes = set(ESCAPE_RE.findall(rest)) if ( not rest.isascii() or '\\u' in escapes or '\\U' in escapes or '\\N' in escapes or ('\\x' in escapes and not latin1_ok) or 'f' in prefix.lower() ): return for part in parts: prefix, rest = parse_string_literal(tokens[part].src) prefix = 'b' + prefix.replace('u', '').replace('U', '') tokens[part] = tokens[part]._replace(src=prefix + rest) del tokens[victims] def _build_import_removals() -> dict[Version, dict[str, tuple[str, ...]]]: ret = {} future: tuple[tuple[Version, tuple[str, ...]], ...] = ( ((2, 7), ('nested_scopes', 'generators', 'with_statement')), ( (3,), ( 'absolute_import', 'division', 'print_function', 'unicode_literals', ), ), ((3, 6), ()), ((3, 7), ('generator_stop',)), ((3, 8), ()), ((3, 9), ()), ((3, 10), ()), ((3, 11), ()), ) prev: tuple[str, ...] = () for min_version, names in future: prev += names ret[min_version] = {'__future__': prev} # see reorder_python_imports for k, v in ret.items(): if k >= (3,): v.update({ 'builtins': ( 'ascii', 'bytes', 'chr', 'dict', 'filter', 'hex', 'input', 'int', 'list', 'map', 'max', 'min', 'next', 'object', 'oct', 'open', 'pow', 'range', 'round', 'str', 'super', 'zip', '*', ), 'io': ('open',), 'six': ('callable', 'next'), 'six.moves': ('filter', 'input', 'map', 'range', 'zip'), }) return ret IMPORT_REMOVALS = _build_import_removals() def _fix_tokens(contents_text: str, min_version: Version) -> str: remove_u = ( min_version >= (3,) or _imports_future(contents_text, 'unicode_literals') ) try: tokens = src_to_tokens(contents_text) except tokenize.TokenError: return contents_text for i, token in reversed_enumerate(tokens): if token.name == 'NUMBER': tokens[i] = token._replace(src=_fix_long(_fix_octal(token.src))) elif token.name == 'STRING': tokens[i] = _fix_ur_literals(tokens[i]) if remove_u: tokens[i] = _remove_u_prefix(tokens[i]) tokens[i] = _fix_escape_sequences(tokens[i]) elif token.src == '(': _fix_extraneous_parens(tokens, i) elif token.src == 'format' and i > 0 and tokens[i - 1].src == '.': _fix_format_literal(tokens, i - 2) elif token.src == 'encode' and i > 0 and tokens[i - 1].src == '.': _fix_encode_to_binary(tokens, i) elif ( min_version >= (3,) and token.utf8_byte_offset == 0 and token.line < 3 and token.name == 'COMMENT' and tokenize.cookie_re.match(token.src) ): del tokens[i] assert tokens[i].name == 'NL', tokens[i].name del tokens[i] return tokens_to_src(tokens).lstrip() def _fix_file(filename: str, args: argparse.Namespace) -> int: if filename == '-': contents_bytes = sys.stdin.buffer.read() else: with open(filename, 'rb') as fb: contents_bytes = fb.read() try: contents_text_orig = contents_text = contents_bytes.decode() except UnicodeDecodeError: print(f'{filename} is non-utf-8 (not supported)') return 1 contents_text = _fix_plugins( contents_text, settings=Settings( min_version=args.min_version, keep_percent_format=args.keep_percent_format, keep_mock=args.keep_mock, keep_runtime_typing=args.keep_runtime_typing, ), ) contents_text = _fix_tokens(contents_text, min_version=args.min_version) if filename == '-': print(contents_text, end='') elif contents_text != contents_text_orig: print(f'Rewriting {filename}', file=sys.stderr) with open(filename, 'w', encoding='UTF-8', newline='') as f: f.write(contents_text) if args.exit_zero_even_if_changed: return 0 else: return contents_text != contents_text_orig def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument('filenames', nargs='*') parser.add_argument('--exit-zero-even-if-changed', action='store_true') parser.add_argument('--keep-percent-format', action='store_true') parser.add_argument('--keep-mock', action='store_true') parser.add_argument('--keep-runtime-typing', action='store_true') parser.add_argument( '--py3-plus', '--py3-only', action='store_const', dest='min_version', default=(2, 7), const=(3,), ) parser.add_argument( '--py36-plus', action='store_const', dest='min_version', const=(3, 6), ) parser.add_argument( '--py37-plus', action='store_const', dest='min_version', const=(3, 7), ) parser.add_argument( '--py38-plus', action='store_const', dest='min_version', const=(3, 8), ) parser.add_argument( '--py39-plus', action='store_const', dest='min_version', const=(3, 9), ) parser.add_argument( '--py310-plus', action='store_const', dest='min_version', const=(3, 10), ) parser.add_argument( '--py311-plus', action='store_const', dest='min_version', const=(3, 11), ) args = parser.parse_args(argv) if args.min_version < (3,): print( 'WARNING: pyupgrade will default to --py3-plus in 3.x', file=sys.stderr, ) ret = 0 for filename in args.filenames: ret |= _fix_file(filename, args) return ret if __name__ == '__main__': raise SystemExit(main()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1657542385.1894023 pyupgrade-2.37.1/pyupgrade/_plugins/0000755000175000017500000000000000000000000021117 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623422590.0 pyupgrade-2.37.1/pyupgrade/_plugins/__init__.py0000644000175000017500000000000000000000000023216 0ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/abspath_file.py0000644000175000017500000000332100000000000024111 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_open_paren def _remove_abspath(i: int, tokens: list[Token]) -> None: paren_start = find_open_paren(tokens, i + 1) paren_end = find_closing_bracket(tokens, paren_start) while i <= paren_start: tokens[i] = Token('PLACEHOLDER', '') i += 1 tokens[paren_end] = Token('PLACEHOLDER', '') @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3, 9) and ( ( isinstance(node.func, ast.Name) and node.func.id == 'abspath' and node.func.id in state.from_imports['os.path'] ) or ( isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Attribute) and isinstance(node.func.value.value, ast.Name) and node.func.value.value.id == 'os' and node.func.value.attr == 'path' and node.func.attr == 'abspath' ) ) and len(node.args) == 1 and isinstance(node.args[0], ast.Name) and node.args[0].id == '__file__' ): yield ast_to_offset(node), _remove_abspath ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657465213.0 pyupgrade-2.37.1/pyupgrade/_plugins/collections_abc.py0000644000175000017500000000201400000000000024611 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._plugins.imports import REPLACE_EXACT from pyupgrade._token_helpers import replace_name COLLECTIONS_ABC_ATTRS = frozenset( attr for mod, attr in REPLACE_EXACT[(3,)] if mod == 'collections' ) @register(ast.Attribute) def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and isinstance(node.value, ast.Name) and node.value.id == 'collections' and node.attr in COLLECTIONS_ABC_ATTRS ): new_attr = f'collections.abc.{node.attr}' func = functools.partial(replace_name, name=node.attr, new=new_attr) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/default_encoding.py0000644000175000017500000000240200000000000024761 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import has_starargs from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._string_helpers import is_codec from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_open_paren def _fix_default_encoding(i: int, tokens: list[Token]) -> None: i = find_open_paren(tokens, i + 1) j = find_closing_bracket(tokens, i) del tokens[i + 1:j] @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, (ast.Str, ast.JoinedStr)) and node.func.attr == 'encode' and not has_starargs(node) and len(node.args) == 1 and isinstance(node.args[0], ast.Str) and is_codec(node.args[0].s, 'utf-8') ): yield ast_to_offset(node), _fix_default_encoding ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/dict_literals.py0000644000175000017500000000421100000000000024311 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from tokenize_rt import UNIMPORTANT_WS from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import immediately_paren from pyupgrade._token_helpers import remove_brace from pyupgrade._token_helpers import victims def _fix_dict_comp( i: int, tokens: list[Token], arg: ast.ListComp | ast.GeneratorExp, ) -> None: if not immediately_paren('dict', tokens, i): return dict_victims = victims(tokens, i + 1, arg, gen=True) elt_victims = victims(tokens, dict_victims.arg_index, arg.elt, gen=True) del dict_victims.starts[0] end_index = dict_victims.ends.pop() tokens[end_index] = Token('OP', '}') for index in reversed(dict_victims.ends): remove_brace(tokens, index) # See #6, Fix SyntaxError from rewriting dict((a, b)for a, b in y) if tokens[elt_victims.ends[-1] + 1].src == 'for': tokens.insert(elt_victims.ends[-1] + 1, Token(UNIMPORTANT_WS, ' ')) for index in reversed(elt_victims.ends): remove_brace(tokens, index) assert elt_victims.first_comma_index is not None tokens[elt_victims.first_comma_index] = Token('OP', ':') for index in reversed(dict_victims.starts + elt_victims.starts): remove_brace(tokens, index) tokens[i:i + 2] = [Token('OP', '{')] @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( isinstance(node.func, ast.Name) and node.func.id == 'dict' and len(node.args) == 1 and not node.keywords and isinstance(node.args[0], (ast.ListComp, ast.GeneratorExp)) and isinstance(node.args[0].elt, (ast.Tuple, ast.List)) and len(node.args[0].elt.elts) == 2 ): func = functools.partial(_fix_dict_comp, arg=node.args[0]) yield ast_to_offset(node.func), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/format_locals.py0000644000175000017500000000325500000000000024323 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import rfind_string_parts from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import find_token def _fix(i: int, tokens: list[Token]) -> None: dot_pos = find_token(tokens, i, '.') open_pos = find_open_paren(tokens, dot_pos) close_pos = find_closing_bracket(tokens, open_pos) for string_idx in rfind_string_parts(tokens, dot_pos - 1): tok = tokens[string_idx] tokens[string_idx] = tok._replace(src=f'f{tok.src}') del tokens[dot_pos:close_pos + 1] @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3, 6) and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Str) and node.func.attr == 'format' and len(node.args) == 0 and len(node.keywords) == 1 and node.keywords[0].arg is None and isinstance(node.keywords[0].value, ast.Call) and isinstance(node.keywords[0].value.func, ast.Name) and node.keywords[0].value.func.id == 'locals' and len(node.keywords[0].value.args) == 0 and len(node.keywords[0].value.keywords) == 0 ): yield ast_to_offset(node), _fix ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/fstrings.py0000644000175000017500000001001300000000000023323 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable 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 contains_await from pyupgrade._ast_helpers import has_starargs from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._string_helpers import parse_format from pyupgrade._string_helpers import unparse_parsed_string from pyupgrade._token_helpers import parse_call_args def _skip_unimportant_ws(tokens: list[Token], i: int) -> int: while tokens[i].name == 'UNIMPORTANT_WS': i += 1 return i def _to_fstring( src: str, tokens: list[Token], args: list[tuple[int, int]], ) -> str: params = {} i = 0 for start, end in args: start = _skip_unimportant_ws(tokens, start) if tokens[start].name == 'NAME': after = _skip_unimportant_ws(tokens, start + 1) if tokens[after].src == '=': # keyword argument params[tokens[start].src] = tokens_to_src( tokens[after + 1:end], ).strip() continue params[str(i)] = tokens_to_src(tokens[start:end]).strip() i += 1 parts = [] i = 0 for s, name, spec, conv in parse_format('f' + src): if name is not None: k, dot, rest = name.partition('.') name = ''.join((params[k or str(i)], dot, rest)) if not k: # named and auto params can be in different orders i += 1 parts.append((s, name, spec, conv)) return unparse_parsed_string(parts) def _fix_fstring(i: int, tokens: list[Token]) -> None: token = tokens[i] paren = i + 3 if tokens_to_src(tokens[i + 1:paren + 1]) != '.format(': return args, end = parse_call_args(tokens, paren) # if it spans more than one line, bail if tokens[end - 1].line != token.line: return args_src = tokens_to_src(tokens[paren:end]) if '\\' in args_src or '"' in args_src or "'" in args_src: return tokens[i] = token._replace(src=_to_fstring(token.src, tokens, args)) del tokens[i + 1:end] def _format_params(call: ast.Call) -> set[str]: params = {str(i) for i, arg in enumerate(call.args)} for kwd in call.keywords: # kwd.arg can't be None here because we exclude starargs assert kwd.arg is not None params.add(kwd.arg) return params @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version < (3, 6): return if ( isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Str) and node.func.attr == 'format' and not has_starargs(node) ): try: parsed = parse_format(node.func.value.s) except ValueError: return params = _format_params(node) seen = set() i = 0 for _, name, spec, _ in parsed: # timid: difficult to rewrite correctly if spec is not None and '{' in spec: break if name is not None: candidate, _, _ = name.partition('.') # timid: could make the f-string longer if candidate and candidate in seen: break # timid: bracketed elif '[' in name: break seen.add(candidate) key = candidate or str(i) # their .format() call is broken currently if key not in params: break if not candidate: i += 1 else: if ( state.settings.min_version >= (3, 7) or not contains_await(node) ): yield ast_to_offset(node), _fix_fstring ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/generator_expressions_pep289.py0000644000175000017500000000341400000000000027232 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import NON_CODING_TOKENS from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import is_async_listcomp from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_comprehension_opening_bracket from pyupgrade._token_helpers import replace_list_comp_brackets ALLOWED_FUNCS = frozenset(( 'bytearray', 'bytes', 'frozenset', 'list', 'max', 'min', 'sorted', 'sum', 'tuple', )) def _delete_list_comp_brackets(i: int, tokens: list[Token]) -> None: start = find_comprehension_opening_bracket(i, tokens) end = find_closing_bracket(tokens, start) tokens[end] = Token('PLACEHOLDER', '') tokens[start] = Token('PLACEHOLDER', '') j = end + 1 while j < len(tokens) and tokens[j].name in NON_CODING_TOKENS: j += 1 if tokens[j].name == 'OP' and tokens[j].src == ',': tokens[j] = Token('PLACEHOLDER', '') @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( isinstance(node.func, ast.Name) and node.func.id in ALLOWED_FUNCS and node.args and isinstance(node.args[0], ast.ListComp) and not is_async_listcomp(node.args[0]) ): if len(node.args) == 1 and not node.keywords: yield ast_to_offset(node.args[0]), _delete_list_comp_brackets else: yield ast_to_offset(node.args[0]), replace_list_comp_brackets ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/identity_equality.py0000644000175000017500000000271300000000000025242 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc LITERAL_TYPES = (ast.Str, ast.Num, ast.Bytes) def _fix_is_literal( i: int, tokens: list[Token], *, op: ast.Is | ast.IsNot, ) -> None: while tokens[i].src != 'is': i -= 1 if isinstance(op, ast.Is): tokens[i] = tokens[i]._replace(src='==') else: tokens[i] = tokens[i]._replace(src='!=') # since we iterate backward, the empty tokens keep the same length i += 1 while tokens[i].src != 'not': tokens[i] = Token('EMPTY', '') i += 1 tokens[i] = Token('EMPTY', '') @register(ast.Compare) def visit_Compare( state: State, node: ast.Compare, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: left = node.left for op, right in zip(node.ops, node.comparators): if ( isinstance(op, (ast.Is, ast.IsNot)) and ( isinstance(left, LITERAL_TYPES) or isinstance(right, LITERAL_TYPES) ) ): func = functools.partial(_fix_is_literal, op=op) yield ast_to_offset(right), func left = right ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542383.0 pyupgrade-2.37.1/pyupgrade/_plugins/imports.py0000644000175000017500000004562600000000000023203 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import bisect import collections import functools from typing import Iterable from typing import Mapping from typing import NamedTuple from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_end from pyupgrade._token_helpers import find_token from pyupgrade._token_helpers import has_space_before from pyupgrade._token_helpers import indented_amount # GENERATED VIA generate-imports # Using reorder-python-imports==3.8.1 REMOVALS = { (2, 7): {'__future__': {'generators', 'nested_scopes', 'with_statement'}}, (3,): { '__future__': { 'absolute_import', 'division', 'print_function', 'unicode_literals', }, 'builtins': { '*', 'ascii', 'bytes', 'chr', 'dict', 'filter', 'hex', 'input', 'int', 'isinstance', 'list', 'map', 'max', 'min', 'next', 'object', 'oct', 'open', 'pow', 'range', 'round', 'str', 'super', 'zip', }, 'io': {'open'}, 'six': {'callable', 'next'}, 'six.moves': {'filter', 'input', 'map', 'range', 'zip'}, }, (3, 7): {'__future__': {'generator_stop'}}, } REMOVALS[(3,)]['six.moves.builtins'] = REMOVALS[(3,)]['builtins'] REPLACE_EXACT = { (3,): { ('collections', 'AsyncGenerator'): 'collections.abc', ('collections', 'AsyncIterable'): 'collections.abc', ('collections', 'AsyncIterator'): 'collections.abc', ('collections', 'Awaitable'): 'collections.abc', ('collections', 'ByteString'): 'collections.abc', ('collections', 'Callable'): 'collections.abc', ('collections', 'Collection'): 'collections.abc', ('collections', 'Container'): 'collections.abc', ('collections', 'Coroutine'): 'collections.abc', ('collections', 'Generator'): 'collections.abc', ('collections', 'Hashable'): 'collections.abc', ('collections', 'ItemsView'): 'collections.abc', ('collections', 'Iterable'): 'collections.abc', ('collections', 'Iterator'): 'collections.abc', ('collections', 'KeysView'): 'collections.abc', ('collections', 'Mapping'): 'collections.abc', ('collections', 'MappingView'): 'collections.abc', ('collections', 'MutableMapping'): 'collections.abc', ('collections', 'MutableSequence'): 'collections.abc', ('collections', 'MutableSet'): 'collections.abc', ('collections', 'Reversible'): 'collections.abc', ('collections', 'Sequence'): 'collections.abc', ('collections', 'Set'): 'collections.abc', ('collections', 'Sized'): 'collections.abc', ('collections', 'ValuesView'): 'collections.abc', ('pipes', 'quote'): 'shlex', ('six', 'BytesIO'): 'io', ('six', 'StringIO'): 'io', ('six', 'wraps'): 'functools', ('six.moves', 'StringIO'): 'io', ('six.moves', 'UserDict'): 'collections', ('six.moves', 'UserList'): 'collections', ('six.moves', 'UserString'): 'collections', ('six.moves', 'filterfalse'): 'itertools', ('six.moves', 'getcwd'): 'os', ('six.moves', 'getcwdb'): 'os', ('six.moves', 'getoutput'): 'subprocess', ('six.moves', 'intern'): 'sys', ('six.moves', 'reduce'): 'functools', ('six.moves', 'zip_longest'): 'itertools', ('six.moves.urllib', 'error'): 'urllib', ('six.moves.urllib', 'parse'): 'urllib', ('six.moves.urllib', 'request'): 'urllib', ('six.moves.urllib', 'response'): 'urllib', ('six.moves.urllib', 'robotparser'): 'urllib', }, (3, 6): { ('typing_extensions', 'AsyncIterable'): 'typing', ('typing_extensions', 'AsyncIterator'): 'typing', ('typing_extensions', 'Awaitable'): 'typing', ('typing_extensions', 'ClassVar'): 'typing', ('typing_extensions', 'ContextManager'): 'typing', ('typing_extensions', 'Coroutine'): 'typing', ('typing_extensions', 'DefaultDict'): 'typing', ('typing_extensions', 'NewType'): 'typing', ('typing_extensions', 'TYPE_CHECKING'): 'typing', ('typing_extensions', 'Text'): 'typing', ('typing_extensions', 'Type'): 'typing', ('typing_extensions', 'get_type_hints'): 'typing', ('typing_extensions', 'overload'): 'typing', }, (3, 7): { ('mypy_extensions', 'NoReturn'): 'typing', ('typing_extensions', 'AsyncContextManager'): 'typing', ('typing_extensions', 'AsyncGenerator'): 'typing', ('typing_extensions', 'ChainMap'): 'typing', ('typing_extensions', 'Counter'): 'typing', ('typing_extensions', 'Deque'): 'typing', }, (3, 8): { ('mypy_extensions', 'TypedDict'): 'typing', ('typing_extensions', 'Final'): 'typing', ('typing_extensions', 'Literal'): 'typing', ('typing_extensions', 'OrderedDict'): 'typing', ('typing_extensions', 'Protocol'): 'typing', ('typing_extensions', 'SupportsIndex'): 'typing', ('typing_extensions', 'TypedDict'): 'typing', ('typing_extensions', 'final'): 'typing', ('typing_extensions', 'get_args'): 'typing', ('typing_extensions', 'get_origin'): 'typing', ('typing_extensions', 'runtime_checkable'): 'typing', }, (3, 9): { ('typing', 'AsyncGenerator'): 'collections.abc', ('typing', 'AsyncIterable'): 'collections.abc', ('typing', 'AsyncIterator'): 'collections.abc', ('typing', 'Awaitable'): 'collections.abc', ('typing', 'ByteString'): 'collections.abc', ('typing', 'ChainMap'): 'collections', ('typing', 'Collection'): 'collections.abc', ('typing', 'Container'): 'collections.abc', ('typing', 'Coroutine'): 'collections.abc', ('typing', 'Counter'): 'collections', ('typing', 'Generator'): 'collections.abc', ('typing', 'Hashable'): 'collections.abc', ('typing', 'ItemsView'): 'collections.abc', ('typing', 'Iterable'): 'collections.abc', ('typing', 'Iterator'): 'collections.abc', ('typing', 'KeysView'): 'collections.abc', ('typing', 'Mapping'): 'collections.abc', ('typing', 'MappingView'): 'collections.abc', ('typing', 'Match'): 're', ('typing', 'MutableMapping'): 'collections.abc', ('typing', 'MutableSequence'): 'collections.abc', ('typing', 'MutableSet'): 'collections.abc', ('typing', 'OrderedDict'): 'collections', ('typing', 'Pattern'): 're', ('typing', 'Reversible'): 'collections.abc', ('typing', 'Sequence'): 'collections.abc', ('typing', 'Sized'): 'collections.abc', ('typing', 'ValuesView'): 'collections.abc', ('typing.re', 'Match'): 're', ('typing.re', 'Pattern'): 're', ('typing_extensions', 'Annotated'): 'typing', }, (3, 10): { ('typing', 'Callable'): 'collections.abc', ('typing_extensions', 'Concatenate'): 'typing', ('typing_extensions', 'ParamSpec'): 'typing', ('typing_extensions', 'TypeAlias'): 'typing', ('typing_extensions', 'TypeGuard'): 'typing', }, } REPLACE_MODS = { 'six.moves.BaseHTTPServer': 'http.server', 'six.moves.CGIHTTPServer': 'http.server', 'six.moves.SimpleHTTPServer': 'http.server', 'six.moves._dummy_thread': '_dummy_thread', 'six.moves._thread': '_thread', 'six.moves.builtins': 'builtins', 'six.moves.cPickle': 'pickle', 'six.moves.collections_abc': 'collections.abc', 'six.moves.configparser': 'configparser', 'six.moves.copyreg': 'copyreg', 'six.moves.dbm_gnu': 'dbm.gnu', 'six.moves.dbm_ndbm': 'dbm.ndbm', 'six.moves.email_mime_base': 'email.mime.base', 'six.moves.email_mime_image': 'email.mime.image', 'six.moves.email_mime_multipart': 'email.mime.multipart', 'six.moves.email_mime_nonmultipart': 'email.mime.nonmultipart', 'six.moves.email_mime_text': 'email.mime.text', 'six.moves.html_entities': 'html.entities', 'six.moves.html_parser': 'html.parser', 'six.moves.http_client': 'http.client', 'six.moves.http_cookiejar': 'http.cookiejar', 'six.moves.http_cookies': 'http.cookies', 'six.moves.queue': 'queue', 'six.moves.reprlib': 'reprlib', 'six.moves.socketserver': 'socketserver', 'six.moves.tkinter': 'tkinter', 'six.moves.tkinter_colorchooser': 'tkinter.colorchooser', 'six.moves.tkinter_commondialog': 'tkinter.commondialog', 'six.moves.tkinter_constants': 'tkinter.constants', 'six.moves.tkinter_dialog': 'tkinter.dialog', 'six.moves.tkinter_dnd': 'tkinter.dnd', 'six.moves.tkinter_filedialog': 'tkinter.filedialog', 'six.moves.tkinter_font': 'tkinter.font', 'six.moves.tkinter_messagebox': 'tkinter.messagebox', 'six.moves.tkinter_scrolledtext': 'tkinter.scrolledtext', 'six.moves.tkinter_simpledialog': 'tkinter.simpledialog', 'six.moves.tkinter_tix': 'tkinter.tix', 'six.moves.tkinter_tkfiledialog': 'tkinter.filedialog', 'six.moves.tkinter_tksimpledialog': 'tkinter.simpledialog', 'six.moves.tkinter_ttk': 'tkinter.ttk', 'six.moves.urllib.error': 'urllib.error', 'six.moves.urllib.parse': 'urllib.parse', 'six.moves.urllib.request': 'urllib.request', 'six.moves.urllib.response': 'urllib.response', 'six.moves.urllib.robotparser': 'urllib.robotparser', 'six.moves.urllib_error': 'urllib.error', 'six.moves.urllib_parse': 'urllib.parse', 'six.moves.urllib_robotparser': 'urllib.robotparser', 'six.moves.xmlrpc_client': 'xmlrpc.client', 'six.moves.xmlrpc_server': 'xmlrpc.server', 'xml.etree.cElementTree': 'xml.etree.ElementTree', } # END GENERATED @functools.lru_cache(maxsize=None) def _for_version( version: tuple[int, ...], *, keep_mock: bool, ) -> tuple[ Mapping[str, set[str]], Mapping[tuple[str, str], str], Mapping[str, str], ]: removals = {} for ver, ver_removals in REMOVALS.items(): if ver <= version: removals.update(ver_removals) exact = {} for ver, ver_exact in REPLACE_EXACT.items(): if ver <= version: exact.update(ver_exact) mods = {} if version >= (3,): mods.update(REPLACE_MODS) if not keep_mock: exact['mock', 'mock'] = 'unittest' mods.update({ 'mock': 'unittest.mock', 'mock.mock': 'unittest.mock', }) return removals, exact, mods def _remove_import(i: int, tokens: list[Token]) -> None: del tokens[i:find_end(tokens, i)] class FromImport(NamedTuple): start: int mod_start: int mod_end: int names: tuple[int, ...] end: int @classmethod def parse(cls, i: int, tokens: list[Token]) -> FromImport: if has_space_before(i, tokens): start = i - 1 else: start = i j = i + 1 # XXX: does not handle explicit relative imports while tokens[j].name != 'NAME': j += 1 mod_start = j import_token = find_token(tokens, j, 'import') j = import_token - 1 while tokens[j].name != 'NAME': j -= 1 mod_end = j end = find_end(tokens, import_token) # XXX: does not handle `*` imports names = [ j for j in range(import_token + 1, end) if tokens[j].name == 'NAME' ] for i in reversed(range(len(names))): if tokens[names[i]].src == 'as': del names[i:i + 2] return cls(start, mod_start, mod_end + 1, tuple(names), end) def remove_self(self, tokens: list[Token]) -> None: del tokens[self.start:self.end] def replace_modname(self, tokens: list[Token], modname: str) -> None: tokens[self.mod_start:self.mod_end] = [Token('CODE', modname)] def remove_parts(self, tokens: list[Token], idxs: list[int]) -> None: for idx in reversed(idxs): if idx == 0: # look forward until next name and del del tokens[self.names[idx]:self.names[idx + 1]] else: # look backward for comma and del j = end = self.names[idx] while tokens[j].src != ',': j -= 1 del tokens[j:end + 1] def _alias_to_s(alias: ast.alias) -> str: if alias.asname: return f'{alias.name} as {alias.asname}' else: return alias.name def _replace_from_modname( i: int, tokens: list[Token], *, modname: str, ) -> None: FromImport.parse(i, tokens).replace_modname(tokens, modname) def _replace_from_mixed( i: int, tokens: list[Token], *, removal_idxs: list[int], exact_moves: list[tuple[int, str, ast.alias]], module_moves: list[tuple[int, str, ast.alias]], ) -> None: try: indent = indented_amount(i, tokens) except ValueError: return parsed = FromImport.parse(i, tokens) added_from_imports = collections.defaultdict(list) for idx, mod, alias in exact_moves: added_from_imports[mod].append(_alias_to_s(alias)) bisect.insort(removal_idxs, idx) new_imports = [] for idx, new_mod, alias in module_moves: new_mod, _, new_sym = new_mod.rpartition('.') new_alias = ast.alias(name=new_sym, asname=alias.asname) if new_mod: added_from_imports[new_mod].append(_alias_to_s(new_alias)) else: new_imports.append(f'{indent}import {_alias_to_s(new_alias)}\n') bisect.insort(removal_idxs, idx) new_imports.extend( f'{indent}from {mod} import {", ".join(names)}\n' for mod, names in added_from_imports.items() ) new_imports.sort() if new_imports and tokens[parsed.end - 1].src != '\n': new_imports.insert(0, '\n') tokens[parsed.end:parsed.end] = [Token('CODE', ''.join(new_imports))] # all names rewritten -- delete import if len(parsed.names) == len(removal_idxs): parsed.remove_self(tokens) else: parsed.remove_parts(tokens, removal_idxs) @register(ast.ImportFrom) def visit_ImportFrom( state: State, node: ast.ImportFrom, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: removals, exact, mods = _for_version( state.settings.min_version, keep_mock=state.settings.keep_mock, ) # we don't have any relative rewrites if node.level != 0 or node.module is None: return mod = node.module removal_idxs = [] if node.col_offset == 0: removals_for_mod = removals.get(mod) if removals_for_mod is not None: removal_idxs = [ i for i, alias in enumerate(node.names) if not alias.asname and alias.name in removals_for_mod ] exact_moves = [] for i, alias in enumerate(node.names): new_mod = exact.get((mod, alias.name)) if new_mod is not None: exact_moves.append((i, new_mod, alias)) module_moves = [] for i, alias in enumerate(node.names): new_mod = mods.get(f'{node.module}.{alias.name}') if new_mod is not None and (alias.asname or alias.name == new_mod): module_moves.append((i, new_mod, alias)) if len(removal_idxs) == len(node.names): yield ast_to_offset(node), _remove_import elif ( len(exact_moves) == len(node.names) and len({mod for _, mod, _ in exact_moves}) == 1 ): _, modname, _ = exact_moves[0] func = functools.partial(_replace_from_modname, modname=modname) yield ast_to_offset(node), func elif removal_idxs or exact_moves or module_moves: func = functools.partial( _replace_from_mixed, removal_idxs=removal_idxs, exact_moves=exact_moves, module_moves=module_moves, ) yield ast_to_offset(node), func elif mod in mods: func = functools.partial(_replace_from_modname, modname=mods[mod]) yield ast_to_offset(node), func def _replace_import( i: int, tokens: list[Token], *, exact_moves: list[tuple[int, str, ast.alias]], to_from: list[tuple[int, str, str, ast.alias]], ) -> None: try: indent = indented_amount(i, tokens) except ValueError: return if has_space_before(i, tokens): start = i - 1 else: start = i end = find_end(tokens, i) parts = [] start_idx = None end_idx = None for j in range(i + 1, end): if start_idx is None and tokens[j].name == 'NAME': start_idx = j end_idx = j + 1 elif start_idx is not None and tokens[j].name == 'NAME': end_idx = j + 1 elif tokens[j].src == ',': assert start_idx is not None and end_idx is not None parts.append((start_idx, end_idx)) start_idx = end_idx = None assert start_idx is not None and end_idx is not None parts.append((start_idx, end_idx)) for idx, new_mod, alias in reversed(exact_moves): new_alias = ast.alias(name=new_mod, asname=alias.asname) tokens[slice(*parts[idx])] = [Token('CODE', _alias_to_s(new_alias))] new_imports = sorted( f'{indent}from {new_mod} import ' f'{_alias_to_s(ast.alias(name=new_sym, asname=alias.asname))}\n' for _, new_mod, new_sym, alias in to_from ) if new_imports and tokens[end - 1].src != '\n': new_imports.insert(0, '\n') tokens[end:end] = [Token('CODE', ''.join(new_imports))] if len(to_from) == len(parts): del tokens[start:end] else: for idx, _, _, _ in reversed(to_from): if idx == 0: # look forward until next name and del del tokens[parts[idx][0]:parts[idx + 1][0]] else: # look backward for comma and del j = part_end = parts[idx][0] while tokens[j].src != ',': j -= 1 del tokens[j:part_end + 1] @register(ast.Import) def visit_Import( state: State, node: ast.Import, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: _, _, mods = _for_version( state.settings.min_version, keep_mock=state.settings.keep_mock, ) to_from = [] exact_moves = [] for i, alias in enumerate(node.names): new_mod = mods.get(alias.name) if new_mod is not None: alias_base, _, _ = alias.name.partition('.') new_mod_base, _, new_sym = new_mod.rpartition('.') if new_mod_base and new_sym == alias_base: to_from.append((i, new_mod_base, new_sym, alias)) elif alias.asname is not None: exact_moves.append((i, new_mod, alias)) if to_from or exact_moves: func = functools.partial( _replace_import, exact_moves=exact_moves, to_from=to_from, ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/io_open.py0000644000175000017500000000171200000000000023122 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_open_paren def _replace_io_open(i: int, tokens: list[Token]) -> None: j = find_open_paren(tokens, i) tokens[i:j] = [tokens[i]._replace(name='NAME', src='open')] @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'io' and node.func.attr == 'open' ): yield ast_to_offset(node.func), _replace_io_open ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/legacy.py0000644000175000017500000001543300000000000022743 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import collections import contextlib import functools from typing import Any from typing import Generator from typing import Iterable 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._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import Block from pyupgrade._token_helpers import find_and_replace_call from pyupgrade._token_helpers import find_block_start from pyupgrade._token_helpers import find_token FUNC_TYPES = (ast.Lambda, ast.FunctionDef, ast.AsyncFunctionDef) def _fix_yield(i: int, tokens: list[Token]) -> None: in_token = find_token(tokens, i, 'in') colon = find_block_start(tokens, i) block = Block.find(tokens, i, trim_end=True) container = tokens_to_src(tokens[in_token + 1:colon]).strip() tokens[i:block.end] = [Token('CODE', f'yield from {container}\n')] def _all_isinstance( vals: Iterable[Any], tp: type[Any] | tuple[type[Any], ...], ) -> bool: return all(isinstance(v, tp) for v in vals) def _fields_same(n1: ast.AST, n2: ast.AST) -> bool: for (a1, v1), (a2, v2) in zip(ast.iter_fields(n1), ast.iter_fields(n2)): # ignore ast attributes, they'll be covered by walk if a1 != a2: return False elif _all_isinstance((v1, v2), ast.AST): continue elif _all_isinstance((v1, v2), (list, tuple)): if len(v1) != len(v2): return False # ignore sequences which are all-ast, they'll be covered by walk elif _all_isinstance(v1, ast.AST) and _all_isinstance(v2, ast.AST): continue elif v1 != v2: return False elif v1 != v2: return False return True def _targets_same(target: ast.AST, yield_value: ast.AST) -> bool: for t1, t2 in zip(ast.walk(target), ast.walk(yield_value)): # ignore `ast.Load` / `ast.Store` if _all_isinstance((t1, t2), ast.expr_context): continue elif type(t1) != type(t2): return False elif not _fields_same(t1, t2): return False else: return True class Scope: def __init__(self, node: ast.AST) -> None: self.node = node self.reads: set[str] = set() self.writes: set[str] = set() self.yield_from_fors: set[Offset] = set() self.yield_from_names: dict[str, set[Offset]] self.yield_from_names = collections.defaultdict(set) class Visitor(ast.NodeVisitor): def __init__(self) -> None: self._scopes: list[Scope] = [] self.super_offsets: set[Offset] = set() self.yield_offsets: set[Offset] = set() @contextlib.contextmanager def _scope(self, node: ast.AST) -> Generator[None, None, None]: self._scopes.append(Scope(node)) try: yield finally: info = self._scopes.pop() # discard any that were referenced outside of the loop for name in info.reads: offsets = info.yield_from_names[name] info.yield_from_fors.difference_update(offsets) self.yield_offsets.update(info.yield_from_fors) if self._scopes: cell_reads = info.reads - info.writes self._scopes[-1].reads.update(cell_reads) def _visit_scope(self, node: ast.AST) -> None: with self._scope(node): self.generic_visit(node) visit_ClassDef = _visit_scope visit_Lambda = visit_FunctionDef = visit_AsyncFunctionDef = _visit_scope visit_ListComp = visit_SetComp = _visit_scope visit_DictComp = visit_GeneratorExp = _visit_scope def visit_Name(self, node: ast.Name) -> None: if self._scopes: if isinstance(node.ctx, ast.Load): self._scopes[-1].reads.add(node.id) elif isinstance(node.ctx, (ast.Store, ast.Del)): self._scopes[-1].writes.add(node.id) else: raise AssertionError(node) self.generic_visit(node) def visit_Call(self, node: ast.Call) -> None: if ( isinstance(node.func, ast.Name) and node.func.id == 'super' and len(node.args) == 2 and isinstance(node.args[0], ast.Name) and isinstance(node.args[1], ast.Name) and # there are at least two scopes len(self._scopes) >= 2 and # the second to last scope is the class in arg1 isinstance(self._scopes[-2].node, ast.ClassDef) and node.args[0].id == self._scopes[-2].node.name and # the last scope is a function where the first arg is arg2 isinstance(self._scopes[-1].node, FUNC_TYPES) and self._scopes[-1].node.args.args and node.args[1].id == self._scopes[-1].node.args.args[0].arg ): self.super_offsets.add(ast_to_offset(node)) self.generic_visit(node) def visit_For(self, node: ast.For) -> None: if ( len(self._scopes) >= 1 and not isinstance(self._scopes[-1].node, ast.AsyncFunctionDef) and len(node.body) == 1 and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Yield) and node.body[0].value.value is not None and _targets_same(node.target, node.body[0].value.value) and not node.orelse ): offset = ast_to_offset(node) func_info = self._scopes[-1] func_info.yield_from_fors.add(offset) for target_node in ast.walk(node.target): if ( isinstance(target_node, ast.Name) and isinstance(target_node.ctx, ast.Store) ): func_info.yield_from_names[target_node.id].add(offset) # manually visit, but with target+body as a separate scope self.visit(node.iter) with self._scope(node): self.visit(node.target) for stmt in node.body: self.visit(stmt) assert not node.orelse else: self.generic_visit(node) @register(ast.Module) def visit_Module( state: State, node: ast.Module, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version < (3,): return visitor = Visitor() visitor.visit(node) super_func = functools.partial(find_and_replace_call, template='super()') for offset in visitor.super_offsets: yield offset, super_func for offset in visitor.yield_offsets: yield offset, _fix_yield ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/lru_cache.py0000644000175000017500000000462300000000000023423 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token 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_and_replace_call from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import find_token def _remove_call(i: int, tokens: list[Token]) -> None: i = find_open_paren(tokens, i) j = find_token(tokens, i, ')') del tokens[i:j + 1] def _is_literal_kwarg( keyword: ast.keyword, name: str, value: bool | None, ) -> bool: return ( keyword.arg == name and isinstance(keyword.value, ast.NameConstant) and keyword.value.value is value ) def _eligible(keywords: list[ast.keyword]) -> bool: if len(keywords) == 1: return _is_literal_kwarg(keywords[0], 'maxsize', None) elif len(keywords) == 2: return ( ( _is_literal_kwarg(keywords[0], 'maxsize', None) and _is_literal_kwarg(keywords[1], 'typed', False) ) or ( _is_literal_kwarg(keywords[1], 'maxsize', None) and _is_literal_kwarg(keywords[0], 'typed', False) ) ) else: return False @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3, 8) and not node.args and not node.keywords and is_name_attr( node.func, state.from_imports, ('functools',), ('lru_cache',), ) ): yield ast_to_offset(node), _remove_call elif ( state.settings.min_version >= (3, 9) and isinstance(node.func, ast.Attribute) and node.func.attr == 'lru_cache' and isinstance(node.func.value, ast.Name) and node.func.value.id == 'functools' and not node.args and _eligible(node.keywords) ): func = functools.partial( find_and_replace_call, template='functools.cache', ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/metaclass_type.py0000644000175000017500000000177200000000000024515 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_end def _remove_metaclass_type(i: int, tokens: list[Token]) -> None: j = find_end(tokens, i) del tokens[i:j] @register(ast.Assign) def visit_Assign( state: State, node: ast.Assign, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and node.targets[0].col_offset == 0 and node.targets[0].id == '__metaclass__' and isinstance(node.value, ast.Name) and node.value.id == 'type' ): yield ast_to_offset(node), _remove_metaclass_type ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657470858.0 pyupgrade-2.37.1/pyupgrade/_plugins/mock.py0000644000175000017500000000163100000000000022423 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_token def _fix_mock_mock(i: int, tokens: list[Token]) -> None: j = find_token(tokens, i + 1, 'mock') del tokens[i + 1:j + 1] @register(ast.Attribute) def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and not state.settings.keep_mock and isinstance(node.value, ast.Name) and node.value.id == 'mock' and node.attr == 'mock' ): yield ast_to_offset(node), _fix_mock_mock ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657465213.0 pyupgrade-2.37.1/pyupgrade/_plugins/native_literals.py0000644000175000017500000000445200000000000024663 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import has_starargs 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 from pyupgrade._token_helpers import replace_call SIX_NATIVE_STR = frozenset(('ensure_str', 'ensure_text', 'text_type')) def _fix_literal(i: int, tokens: list[Token], *, empty: str) -> None: j = find_open_paren(tokens, i) func_args, end = parse_call_args(tokens, j) if any(tok.name == 'NL' for tok in tokens[i:end]): return if func_args: replace_call(tokens, i, end, func_args, '{args[0]}') else: tokens[i:end] = [tokens[i]._replace(name='STRING', src=empty)] def is_a_native_literal_call( node: ast.Call, from_imports: dict[str, set[str]], ) -> bool: return ( ( is_name_attr(node.func, from_imports, ('six',), SIX_NATIVE_STR) or isinstance(node.func, ast.Name) and node.func.id == 'str' ) and not node.keywords and not has_starargs(node) and ( len(node.args) == 0 or (len(node.args) == 1 and isinstance(node.args[0], ast.Str)) ) ) @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and is_a_native_literal_call(node, state.from_imports) ): func = functools.partial(_fix_literal, empty="''") yield ast_to_offset(node), func elif ( state.settings.min_version >= (3,) and isinstance(node.func, ast.Name) and node.func.id == 'bytes' and not node.keywords and not has_starargs(node) and ( len(node.args) == 0 or (len(node.args) == 1 and isinstance(node.args[0], ast.Bytes)) ) ): func = functools.partial(_fix_literal, empty="b''") yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/new_style_classes.py0000644000175000017500000000125600000000000025223 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import remove_base_class @register(ast.ClassDef) def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3,): for base in node.bases: if isinstance(base, ast.Name) and base.id == 'object': yield ast_to_offset(base), remove_base_class ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657465213.0 pyupgrade-2.37.1/pyupgrade/_plugins/open_mode.py0000644000175000017500000000671400000000000023446 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from typing import NamedTuple 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 has_starargs from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import delete_argument from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import parse_call_args U_MODE_REMOVE = frozenset(('U', 'Ur', 'rU', 'r', 'rt', 'tr')) U_MODE_REPLACE_R = frozenset(('Ub', 'bU')) U_MODE_REMOVE_U = frozenset(('rUb', 'Urb', 'rbU', 'Ubr', 'bUr', 'brU')) U_MODE_REPLACE = U_MODE_REPLACE_R | U_MODE_REMOVE_U class FunctionArg(NamedTuple): arg_idx: int value: ast.expr def _fix_open_mode(i: int, tokens: list[Token], *, arg_idx: int) -> None: j = find_open_paren(tokens, i) func_args, end = parse_call_args(tokens, j) mode = tokens_to_src(tokens[slice(*func_args[arg_idx])]) mode_stripped = mode.split('=')[-1] mode_stripped = ast.literal_eval(mode_stripped.strip()) if mode_stripped in U_MODE_REMOVE: delete_argument(arg_idx, tokens, func_args) elif mode_stripped in U_MODE_REPLACE_R: new_mode = mode.replace('U', 'r') tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_mode)] elif mode_stripped in U_MODE_REMOVE_U: new_mode = mode.replace('U', '') tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_mode)] else: raise AssertionError(f'unreachable: {mode!r}') @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and ( ( isinstance(node.func, ast.Name) and node.func.id == 'open' ) or ( isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'io' and node.func.attr == 'open' ) ) and not has_starargs(node) ): if len(node.args) >= 2 and isinstance(node.args[1], ast.Str): if ( node.args[1].s in U_MODE_REPLACE or (len(node.args) == 2 and node.args[1].s in U_MODE_REMOVE) ): func = functools.partial( _fix_open_mode, arg_idx=1, ) yield ast_to_offset(node), func elif node.keywords and (len(node.keywords) + len(node.args) > 1): mode = next( ( FunctionArg(n, keyword.value) for n, keyword in enumerate(node.keywords) if keyword.arg == 'mode' ), None, ) if ( mode is not None and isinstance(mode.value, ast.Str) and ( mode.value.s in U_MODE_REMOVE or mode.value.s in U_MODE_REPLACE ) ): func = functools.partial( _fix_open_mode, arg_idx=len(node.args) + mode.arg_idx, ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/oserror_aliases.py0000644000175000017500000001040300000000000024663 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import arg_str from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import parse_call_args from pyupgrade._token_helpers import replace_name ERROR_NAMES = frozenset(('EnvironmentError', 'IOError', 'WindowsError')) ERROR_MODULES = frozenset(('mmap', 'select', 'socket')) def _fix_oserror_except( i: int, tokens: list[Token], *, from_imports: dict[str, set[str]], ) -> None: # find all the arg strs in the tuple except_index = i while tokens[except_index].src != 'except': except_index -= 1 start = find_open_paren(tokens, except_index) func_args, end = parse_call_args(tokens, start) # save the exceptions and remove the block arg_strs = [arg_str(tokens, *arg) for arg in func_args] del tokens[start:end] # rewrite the block without dupes args = [] for arg in arg_strs: left, part, right = arg.partition('.') if left in ERROR_MODULES and part == '.' and right == 'error': args.append('OSError') elif left in ERROR_NAMES and part == right == '': args.append('OSError') elif ( left == 'error' and part == right == '' and any('error' in from_imports[mod] for mod in ERROR_MODULES) ): args.append('OSError') else: args.append(arg) unique_args = tuple(dict.fromkeys(args)) if len(unique_args) > 1: joined = '({})'.format(', '.join(unique_args)) elif tokens[start - 1].name != 'UNIMPORTANT_WS': joined = f' {unique_args[0]}' else: joined = unique_args[0] new = Token('CODE', joined) tokens.insert(start, new) def _is_oserror_alias( node: ast.AST, from_imports: dict[str, set[str]], ) -> tuple[Offset, str] | None: if isinstance(node, ast.Name) and node.id in ERROR_NAMES: return ast_to_offset(node), node.id elif ( isinstance(node, ast.Name) and node.id == 'error' and any(node.id in from_imports[mod] for mod in ERROR_MODULES) ): return ast_to_offset(node), node.id elif ( isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and node.value.id in ERROR_MODULES and node.attr == 'error' ): return ast_to_offset(node), node.attr else: return None def _oserror_alias_cbs( node: ast.AST, from_imports: dict[str, set[str]], ) -> Iterable[tuple[Offset, TokenFunc]]: offset_name = _is_oserror_alias(node, from_imports) if offset_name is not None: offset, name = offset_name func = functools.partial(replace_name, name=name, new='OSError') yield offset, func @register(ast.Raise) def visit_Raise( state: State, node: ast.Raise, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3,) and node.exc is not None: yield from _oserror_alias_cbs(node.exc, state.from_imports) if isinstance(node.exc, ast.Call): yield from _oserror_alias_cbs(node.exc.func, state.from_imports) @register(ast.Try) def visit_Try( state: State, node: ast.Try, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3,): for handler in node.handlers: if ( isinstance(handler.type, ast.Tuple) and any( _is_oserror_alias(elt, state.from_imports) for elt in handler.type.elts ) ): func = functools.partial( _fix_oserror_except, from_imports=state.from_imports, ) yield ast_to_offset(handler.type), func elif handler.type is not None: yield from _oserror_alias_cbs(handler.type, state.from_imports) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/percent_format.py0000644000175000017500000002221500000000000024503 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools import re from typing import Generator from typing import Iterable from typing import Match from typing import Optional from typing import Pattern 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._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._string_helpers import curly_escape from pyupgrade._token_helpers import KEYWORDS from pyupgrade._token_helpers import remove_brace from pyupgrade._token_helpers import victims PercentFormatPart = Tuple[ Optional[str], Optional[str], Optional[str], Optional[str], str, ] PercentFormat = Tuple[str, Optional[PercentFormatPart]] MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') CONVERSION_FLAG_RE = re.compile('[#0+ -]*') WIDTH_RE = re.compile(r'(?:\*|\d*)') PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?') LENGTH_RE = re.compile('[hlL]?') def _must_match(regex: Pattern[str], string: str, pos: int) -> Match[str]: match = regex.match(string, pos) assert match is not None return match def _parse_percent_format(s: str) -> tuple[PercentFormat, ...]: def _parse_inner() -> Generator[PercentFormat, None, None]: string_start = 0 string_end = 0 in_fmt = False i = 0 while i < len(s): if not in_fmt: try: i = s.index('%', i) except ValueError: # no more % fields! yield s[string_start:], None return else: string_end = i i += 1 in_fmt = True else: key_match = MAPPING_KEY_RE.match(s, i) if key_match: key: str | None = key_match.group(1) i = key_match.end() else: key = None conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i) conversion_flag = conversion_flag_match.group() or None i = conversion_flag_match.end() width_match = _must_match(WIDTH_RE, s, i) width = width_match.group() or None i = width_match.end() precision_match = _must_match(PRECISION_RE, s, i) precision = precision_match.group() or None i = precision_match.end() # length modifier is ignored i = _must_match(LENGTH_RE, s, i).end() try: conversion = s[i] except IndexError: raise ValueError('end-of-string while parsing format') i += 1 fmt = (key, conversion_flag, width, precision, conversion) yield s[string_start:string_end], fmt in_fmt = False string_start = i if in_fmt: raise ValueError('end-of-string while parsing format') return tuple(_parse_inner()) def _simplify_conversion_flag(flag: str) -> str: parts: list[str] = [] for c in flag: if c in parts: continue c = c.replace('-', '<') parts.append(c) if c == '<' and '0' in parts: parts.remove('0') elif c == '+' and ' ' in parts: parts.remove(' ') return ''.join(parts) def _percent_to_format(s: str) -> str: def _handle_part(part: PercentFormat) -> str: s, fmt = part s = curly_escape(s) if fmt is None: return s else: key, conversion_flag, width, precision, conversion = fmt if conversion == '%': return s + '%' parts = [s, '{'] if conversion == 's': conversion = '' if key: parts.append(key) if conversion in {'r', 'a'}: converter = f'!{conversion}' conversion = '' else: converter = '' if any((conversion_flag, width, precision, conversion)): parts.append(':') if conversion_flag: parts.append(_simplify_conversion_flag(conversion_flag)) parts.extend(x for x in (width, precision, conversion) if x) parts.extend(converter) parts.append('}') return ''.join(parts) return ''.join(_handle_part(part) for part in _parse_percent_format(s)) def _fix_percent_format_tuple( i: int, tokens: list[Token], *, node_right: ast.Tuple, ) -> None: # TODO: this is overly timid paren = i + 4 if tokens_to_src(tokens[i + 1:paren + 1]) != ' % (': return fmt_victims = victims(tokens, paren, node_right, gen=False) fmt_victims.ends.pop() for index in reversed(fmt_victims.starts + fmt_victims.ends): remove_brace(tokens, index) newsrc = _percent_to_format(tokens[i].src) tokens[i] = tokens[i]._replace(src=newsrc) tokens[i + 1:paren] = [Token('Format', '.format'), Token('OP', '(')] def _fix_percent_format_dict( i: int, tokens: list[Token], *, node_right: ast.Dict, ) -> None: seen_keys: set[str] = set() keys = {} for k in node_right.keys: # not a string key if not isinstance(k, ast.Str): return # duplicate key elif k.s in seen_keys: return # not an identifier elif not k.s.isidentifier(): return # a keyword elif k.s in KEYWORDS: return seen_keys.add(k.s) keys[ast_to_offset(k)] = k # TODO: this is overly timid brace = i + 4 if tokens_to_src(tokens[i + 1:brace + 1]) != ' % {': return fmt_victims = victims(tokens, brace, node_right, gen=False) brace_end = fmt_victims.ends[-1] key_indices = [] for j, token in enumerate(tokens[brace:brace_end], brace): key = keys.pop(token.offset, None) if key is None: continue # we found the key, but the string didn't match (implicit join?) elif ast.literal_eval(token.src) != key.s: return # the map uses some strange syntax that's not `'key': value` elif tokens[j + 1].src != ':' or tokens[j + 2].src != ' ': return else: key_indices.append((j, key.s)) assert not keys, keys tokens[brace_end] = tokens[brace_end]._replace(src=')') for key_index, s in reversed(key_indices): tokens[key_index:key_index + 3] = [Token('CODE', f'{s}=')] newsrc = _percent_to_format(tokens[i].src) tokens[i] = tokens[i]._replace(src=newsrc) tokens[i + 1:brace + 1] = [Token('CODE', '.format'), Token('OP', '(')] @register(ast.BinOp) def visit_BinOp( state: State, node: ast.BinOp, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( not state.settings.keep_percent_format and isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Str) ): try: parsed = _parse_percent_format(node.left.s) except ValueError: pass else: for _, fmt in parsed: if not fmt: continue key, conversion_flag, width, precision, conversion = fmt # timid: these require out-of-order parameter consumption if width == '*' or precision == '.*': break # these conversions require modification of parameters if conversion in {'d', 'i', 'u', 'c'}: break # timid: py2: %#o formats different from {:#o} (--py3?) if '#' in (conversion_flag or '') and conversion == 'o': break # no equivalent in format if key == '': break # timid: py2: conversion is subject to modifiers (--py3?) nontrivial_fmt = any((conversion_flag, width, precision)) if conversion == '%' and nontrivial_fmt: break # no equivalent in format if conversion in {'a', 'r'} and nontrivial_fmt: break # %s with None and width is not supported if width and conversion == 's': break # all dict substitutions must be named if isinstance(node.right, ast.Dict) and not key: break else: if isinstance(node.right, ast.Tuple): func = functools.partial( _fix_percent_format_tuple, node_right=node.right, ) yield ast_to_offset(node), func elif isinstance(node.right, ast.Dict): func = functools.partial( _fix_percent_format_dict, node_right=node.right, ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/set_literals.py0000644000175000017500000000456000000000000024170 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import BRACES from pyupgrade._token_helpers import immediately_paren from pyupgrade._token_helpers import remove_brace from pyupgrade._token_helpers import victims SET_TRANSFORM = (ast.List, ast.ListComp, ast.GeneratorExp, ast.Tuple) def _fix_set_empty_literal(i: int, tokens: list[Token]) -> None: # TODO: this could be implemented with a little extra logic if not immediately_paren('set', tokens, i): return j = i + 2 brace_stack = ['('] while brace_stack: token = tokens[j].src if token == BRACES[brace_stack[-1]]: brace_stack.pop() elif token in BRACES: brace_stack.append(token) elif '\n' in token: # Contains a newline, could cause a SyntaxError, bail return j += 1 # Remove the inner tokens del tokens[i + 2:j - 1] def _fix_set_literal(i: int, tokens: list[Token], *, arg: ast.expr) -> None: # TODO: this could be implemented with a little extra logic if not immediately_paren('set', tokens, i): return gen = isinstance(arg, ast.GeneratorExp) set_victims = victims(tokens, i + 1, arg, gen=gen) del set_victims.starts[0] end_index = set_victims.ends.pop() tokens[end_index] = Token('OP', '}') for index in reversed(set_victims.starts + set_victims.ends): remove_brace(tokens, index) tokens[i:i + 2] = [Token('OP', '{')] @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( isinstance(node.func, ast.Name) and node.func.id == 'set' and len(node.args) == 1 and not node.keywords and isinstance(node.args[0], SET_TRANSFORM) ): arg, = node.args if isinstance(arg, (ast.List, ast.Tuple)) and not arg.elts: yield ast_to_offset(node.func), _fix_set_empty_literal else: func = functools.partial(_fix_set_literal, arg=arg) yield ast_to_offset(node.func), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/six_base_classes.py0000644000175000017500000000135300000000000025005 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset 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 remove_base_class @register(ast.ClassDef) def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3,): for base in node.bases: if is_name_attr(base, state.from_imports, ('six',), ('Iterator',)): yield ast_to_offset(base), remove_base_class ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/six_calls.py0000644000175000017500000001501300000000000023452 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools import sys from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import has_starargs 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_and_replace_call from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import parse_call_args from pyupgrade._token_helpers import replace_call _EXPR_NEEDS_PARENS: tuple[type[ast.expr], ...] = ( ast.Await, ast.BinOp, ast.BoolOp, ast.Compare, ast.GeneratorExp, ast.IfExp, ast.Lambda, ast.UnaryOp, ) if sys.version_info >= (3, 8): # pragma: >=3.8 cover _EXPR_NEEDS_PARENS += (ast.NamedExpr,) SIX_CALLS = { 'u': '{args[0]}', 'byte2int': '{args[0]}[0]', 'indexbytes': '{args[0]}[{rest}]', 'iteritems': '{args[0]}.items()', 'iterkeys': '{args[0]}.keys()', 'itervalues': '{args[0]}.values()', 'viewitems': '{args[0]}.items()', 'viewkeys': '{args[0]}.keys()', 'viewvalues': '{args[0]}.values()', 'create_unbound_method': '{args[0]}', 'get_unbound_function': '{args[0]}', 'get_method_function': '{args[0]}.__func__', 'get_method_self': '{args[0]}.__self__', 'get_function_closure': '{args[0]}.__closure__', 'get_function_code': '{args[0]}.__code__', 'get_function_defaults': '{args[0]}.__defaults__', 'get_function_globals': '{args[0]}.__globals__', 'assertCountEqual': '{args[0]}.assertCountEqual({rest})', 'assertRaisesRegex': '{args[0]}.assertRaisesRegex({rest})', 'assertRegex': '{args[0]}.assertRegex({rest})', } SIX_INT2BYTE_TMPL = 'bytes(({args[0]},))' RAISE_FROM_TMPL = 'raise {args[0]} from {args[1]}' RERAISE_TMPL = 'raise' RERAISE_2_TMPL = 'raise {args[1]}.with_traceback(None)' RERAISE_3_TMPL = 'raise {args[1]}.with_traceback({args[2]})' def _fix_six_b(i: int, tokens: list[Token]) -> None: j = find_open_paren(tokens, i) if ( tokens[j + 1].name == 'STRING' and tokens[j + 1].src.isascii() and tokens[j + 2].src == ')' ): func_args, end = parse_call_args(tokens, j) replace_call(tokens, i, end, func_args, 'b{args[0]}') @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version < (3,): return if isinstance(node.func, ast.Name): name = node.func.id elif isinstance(node.func, ast.Attribute): name = node.func.attr else: return if ( is_name_attr( node.func, state.from_imports, ('six',), ('iteritems', 'iterkeys', 'itervalues'), ) and node.args and not has_starargs(node) and # parent is next(...) isinstance(parent, ast.Call) and isinstance(parent.func, ast.Name) and parent.func.id == 'next' ): func = functools.partial( find_and_replace_call, template=f'iter({SIX_CALLS[name]})', ) yield ast_to_offset(node), func elif ( is_name_attr( node.func, state.from_imports, ('six',), SIX_CALLS, ) and node.args and not has_starargs(node) ): if isinstance(node.args[0], _EXPR_NEEDS_PARENS): parens: tuple[int, ...] = (0,) else: parens = () func = functools.partial( find_and_replace_call, template=SIX_CALLS[name], parens=parens, ) yield ast_to_offset(node), func elif ( is_name_attr( node.func, state.from_imports, ('six',), ('int2byte',), ) and node.args and not has_starargs(node) ): func = functools.partial( find_and_replace_call, template=SIX_INT2BYTE_TMPL, ) yield ast_to_offset(node), func elif ( state.settings.min_version >= (3,) and is_name_attr( node.func, state.from_imports, ('six',), ('b', 'ensure_binary'), ) and not node.keywords and not has_starargs(node) and len(node.args) == 1 and isinstance(node.args[0], ast.Str) ): yield ast_to_offset(node), _fix_six_b elif ( isinstance(parent, ast.Expr) and is_name_attr( node.func, state.from_imports, ('six',), ('raise_from',), ) and node.args and not has_starargs(node) ): func = functools.partial( find_and_replace_call, template=RAISE_FROM_TMPL, ) yield ast_to_offset(node), func elif ( isinstance(parent, ast.Expr) and is_name_attr( node.func, state.from_imports, ('six',), ('reraise',), ) ): if ( len(node.args) == 2 and not node.keywords and not has_starargs(node) ): func = functools.partial( find_and_replace_call, template=RERAISE_2_TMPL, ) yield ast_to_offset(node), func elif ( len(node.args) == 3 and not node.keywords and not has_starargs(node) ): func = functools.partial( find_and_replace_call, template=RERAISE_3_TMPL, ) yield ast_to_offset(node), func elif ( len(node.args) == 1 and not node.keywords and isinstance(node.args[0], ast.Starred) and isinstance(node.args[0].value, ast.Call) and is_name_attr( node.args[0].value.func, state.from_imports, ('sys',), ('exc_info',), ) ): func = functools.partial( find_and_replace_call, template=RERAISE_TMPL, ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/six_metaclasses.py0000644000175000017500000000650600000000000024667 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import NON_CODING_TOKENS from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import has_starargs 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 arg_str from pyupgrade._token_helpers import find_block_start from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import parse_call_args from pyupgrade._token_helpers import remove_decorator from pyupgrade._token_helpers import replace_call def _fix_add_metaclass(i: int, tokens: list[Token]) -> None: j = find_open_paren(tokens, i) func_args, end = parse_call_args(tokens, j) metaclass = f'metaclass={arg_str(tokens, *func_args[0])}' # insert `metaclass={args[0]}` into `class:` # search forward for the `class` token j = i + 1 while tokens[j].src != 'class': j += 1 class_token = j # then search forward for a `:` token, not inside a brace j = find_block_start(tokens, j) last_paren = -1 for k in range(class_token, j): if tokens[k].src == ')': last_paren = k if last_paren == -1: tokens.insert(j, Token('CODE', f'({metaclass})')) else: insert = last_paren - 1 while tokens[insert].name in NON_CODING_TOKENS: insert -= 1 if tokens[insert].src == '(': # no bases src = metaclass elif tokens[insert].src != ',': src = f', {metaclass}' else: src = f' {metaclass},' tokens.insert(insert + 1, Token('CODE', src)) remove_decorator(i, tokens) def _fix_with_metaclass(i: int, tokens: list[Token]) -> None: j = find_open_paren(tokens, i) func_args, end = parse_call_args(tokens, j) if len(func_args) == 1: tmpl = 'metaclass={args[0]}' elif len(func_args) == 2: base = arg_str(tokens, *func_args[1]) if base == 'object': tmpl = 'metaclass={args[0]}' else: tmpl = '{rest}, metaclass={args[0]}' else: tmpl = '{rest}, metaclass={args[0]}' replace_call(tokens, i, end, func_args, tmpl) @register(ast.ClassDef) def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version < (3,): return for decorator in node.decorator_list: if ( isinstance(decorator, ast.Call) and is_name_attr( decorator.func, state.from_imports, ('six',), ('add_metaclass',), ) and not has_starargs(decorator) ): yield ast_to_offset(decorator), _fix_add_metaclass if ( len(node.bases) == 1 and isinstance(node.bases[0], ast.Call) and is_name_attr( node.bases[0].func, state.from_imports, ('six',), ('with_metaclass',), ) and not has_starargs(node.bases[0]) ): yield ast_to_offset(node.bases[0]), _fix_with_metaclass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/six_remove_decorators.py0000644000175000017500000000156300000000000026103 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset 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 remove_decorator @register(ast.ClassDef) def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3,): for decorator in node.decorator_list: if is_name_attr( decorator, state.from_imports, ('six',), ('python_2_unicode_compatible',), ): yield ast_to_offset(decorator), remove_decorator ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657465213.0 pyupgrade-2.37.1/pyupgrade/_plugins/six_simple.py0000644000175000017500000000746100000000000023655 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._plugins.imports import REMOVALS from pyupgrade._plugins.native_literals import is_a_native_literal_call from pyupgrade._token_helpers import replace_name NAMES = { 'text_type': 'str', 'binary_type': 'bytes', 'class_types': '(type,)', 'string_types': '(str,)', 'integer_types': '(int,)', 'unichr': 'chr', 'iterbytes': 'iter', 'print_': 'print', 'exec_': 'exec', 'advance_iterator': 'next', 'next': 'next', 'callable': 'callable', } NAMES_MOVES = REMOVALS[(3,)]['six.moves'] NAMES_TYPE_CTX = { 'class_types': 'type', 'string_types': 'str', 'integer_types': 'int', } def _is_type_check(node: ast.AST | None) -> bool: return ( isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in {'isinstance', 'issubclass'} ) @register(ast.Attribute) def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and isinstance(node.value, ast.Name) and node.value.id == 'six' and node.attr in NAMES ): # these will be handled by the native literals plugin if ( isinstance(parent, ast.Call) and is_a_native_literal_call(parent, state.from_imports) ): return if node.attr in NAMES_TYPE_CTX and _is_type_check(parent): new = NAMES_TYPE_CTX[node.attr] else: new = NAMES[node.attr] func = functools.partial(replace_name, name=node.attr, new=new) yield ast_to_offset(node), func elif ( state.settings.min_version >= (3,) and isinstance(node.value, ast.Attribute) and isinstance(node.value.value, ast.Name) and node.value.value.id == 'six' and node.value.attr == 'moves' and node.attr == 'xrange' ): func = functools.partial(replace_name, name=node.attr, new='range') yield ast_to_offset(node), func elif ( state.settings.min_version >= (3,) and isinstance(node.value, ast.Attribute) and isinstance(node.value.value, ast.Name) and node.value.value.id == 'six' and node.value.attr == 'moves' and node.attr in NAMES_MOVES ): func = functools.partial(replace_name, name=node.attr, new=node.attr) yield ast_to_offset(node), func @register(ast.Name) def visit_Name( state: State, node: ast.Name, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and node.id in state.from_imports['six'] and node.id in NAMES ): # these will be handled by the native literals plugin if ( isinstance(parent, ast.Call) and is_a_native_literal_call(parent, state.from_imports) ): return if node.id in NAMES_TYPE_CTX and _is_type_check(parent): new = NAMES_TYPE_CTX[node.id] else: new = NAMES[node.id] func = functools.partial(replace_name, name=node.id, new=new) yield ast_to_offset(node), func elif ( state.settings.min_version >= (3,) and node.id in state.from_imports['six.moves'] and node.id in {'xrange', 'range'} ): func = functools.partial(replace_name, name=node.id, new='range') yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/subprocess_run.py0000644000175000017500000000704100000000000024547 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token 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 delete_argument from pyupgrade._token_helpers import find_open_paren from pyupgrade._token_helpers import parse_call_args from pyupgrade._token_helpers import replace_argument def _use_capture_output( i: int, tokens: list[Token], *, stdout_arg_idx: int, stderr_arg_idx: int, ) -> None: j = find_open_paren(tokens, i) func_args, _ = parse_call_args(tokens, j) if stdout_arg_idx < stderr_arg_idx: delete_argument(stderr_arg_idx, tokens, func_args) replace_argument( stdout_arg_idx, tokens, func_args, new='capture_output=True', ) else: replace_argument( stdout_arg_idx, tokens, func_args, new='capture_output=True', ) delete_argument(stderr_arg_idx, tokens, func_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) for i in range(*func_args[arg_idx]): if tokens[i].src == 'universal_newlines': tokens[i] = tokens[i]._replace(src='text') break else: raise AssertionError('`universal_newlines` argument not found') @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',), ('check_output', 'run'), ) ): stdout_idx = None stderr_idx = None universal_newlines_idx = None skip_universal_newlines_rewrite = False for n, keyword in enumerate(node.keywords): if keyword.arg == 'stdout' and is_name_attr( keyword.value, state.from_imports, ('subprocess',), ('PIPE',), ): stdout_idx = n elif keyword.arg == 'stderr' and is_name_attr( keyword.value, state.from_imports, ('subprocess',), ('PIPE',), ): stderr_idx = n elif keyword.arg == 'universal_newlines': universal_newlines_idx = n elif keyword.arg == 'text' or keyword.arg is None: skip_universal_newlines_rewrite = True if ( universal_newlines_idx is not None and not skip_universal_newlines_rewrite ): func = functools.partial( _replace_universal_newlines_with_text, arg_idx=len(node.args) + universal_newlines_idx, ) yield ast_to_offset(node), func if stdout_idx is not None and stderr_idx is not None: func = functools.partial( _use_capture_output, stdout_arg_idx=len(node.args) + stdout_idx, stderr_arg_idx=len(node.args) + stderr_idx, ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/type_of_primitive.py0000644000175000017500000000340600000000000025231 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_open_paren NUM_TYPES = { int: 'int', float: 'float', complex: 'complex', } def _rewrite_type_of_primitive( i: int, tokens: list[Token], *, src: str, ) -> None: open_paren = find_open_paren(tokens, i + 1) j = find_closing_bracket(tokens, open_paren) tokens[i] = tokens[i]._replace(src=src) del tokens[i + 1:j + 1] @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and isinstance(node.func, ast.Name) and node.func.id == 'type' and len(node.args) == 1 ): if isinstance(node.args[0], ast.Str): func = functools.partial( _rewrite_type_of_primitive, src='str', ) yield ast_to_offset(node), func elif isinstance(node.args[0], ast.Bytes): func = functools.partial( _rewrite_type_of_primitive, src='bytes', ) yield ast_to_offset(node), func elif isinstance(node.args[0], ast.Num): func = functools.partial( _rewrite_type_of_primitive, src=NUM_TYPES[type(node.args[0].n)], ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/typing_classes.py0000644000175000017500000001633400000000000024527 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools import sys from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from tokenize_rt import UNIMPORTANT_WS from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import has_starargs 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 KEYWORDS def _unparse(node: ast.expr) -> str: if isinstance(node, ast.Name): return node.id elif isinstance(node, ast.Attribute): return ''.join((_unparse(node.value), '.', node.attr)) elif isinstance(node, ast.Subscript): if sys.version_info >= (3, 9): # pragma: >=3.9 cover node_slice: ast.expr = node.slice elif isinstance(node.slice, ast.Index): # pragma: <3.9 cover node_slice = node.slice.value else: raise AssertionError(f'expected Slice: {ast.dump(node)}') if isinstance(node_slice, ast.Tuple): if len(node_slice.elts) == 1: slice_s = f'{_unparse(node_slice.elts[0])},' else: slice_s = ', '.join(_unparse(elt) for elt in node_slice.elts) else: slice_s = _unparse(node_slice) return f'{_unparse(node.value)}[{slice_s}]' elif isinstance(node, ast.Str): return repr(node.s) elif isinstance(node, ast.Ellipsis): return '...' elif isinstance(node, ast.List): return '[{}]'.format(', '.join(_unparse(elt) for elt in node.elts)) elif isinstance(node, ast.NameConstant): return repr(node.value) else: raise NotImplementedError(ast.dump(node)) def _typed_class_replacement( tokens: list[Token], i: int, call: ast.Call, types: dict[str, ast.expr], ) -> tuple[int, str]: while i > 0 and tokens[i - 1].name == 'DEDENT': i -= 1 if i > 0 and tokens[i - 1].name in {'INDENT', UNIMPORTANT_WS}: indent = f'{tokens[i - 1].src}{" " * 4}' else: indent = ' ' * 4 # NT = NamedTuple("nt", [("a", int)]) # ^i ^end end = i + 1 while end < len(tokens) and tokens[end].name != 'NEWLINE': end += 1 attrs = '\n'.join(f'{indent}{k}: {_unparse(v)}' for k, v in types.items()) return end, attrs def _fix_named_tuple(i: int, tokens: list[Token], *, call: ast.Call) -> None: types = { tup.elts[0].s: tup.elts[1] for tup in call.args[1].elts # type: ignore # (checked below) } end, attrs = _typed_class_replacement(tokens, i, call, types) src = f'class {tokens[i].src}({_unparse(call.func)}):\n{attrs}' tokens[i:end] = [Token('CODE', src)] def _fix_kw_typed_dict(i: int, tokens: list[Token], *, call: ast.Call) -> None: types = { arg.arg: arg.value for arg in call.keywords if arg.arg is not None } end, attrs = _typed_class_replacement(tokens, i, call, types) src = f'class {tokens[i].src}({_unparse(call.func)}):\n{attrs}' tokens[i:end] = [Token('CODE', src)] def _fix_dict_typed_dict( i: int, tokens: list[Token], *, call: ast.Call, ) -> None: types = { k.s: v for k, v in zip( call.args[1].keys, # type: ignore # (checked below) call.args[1].values, # type: ignore # (checked below) ) } if call.keywords: total = call.keywords[0].value.value # type: ignore # (checked below) # noqa: E501 end, attrs = _typed_class_replacement(tokens, i, call, types) src = ( f'class {tokens[i].src}(' f'{_unparse(call.func)}, total={total}' f'):\n' f'{attrs}' ) tokens[i:end] = [Token('CODE', src)] else: end, attrs = _typed_class_replacement(tokens, i, call, types) src = f'class {tokens[i].src}({_unparse(call.func)}):\n{attrs}' tokens[i:end] = [Token('CODE', src)] @register(ast.Assign) def visit_Assign( state: State, node: ast.Assign, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version < (3, 6): return if ( # NT = ...("NT", ...) len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and isinstance(node.value, ast.Call) and len(node.value.args) >= 1 and isinstance(node.value.args[0], ast.Str) and node.targets[0].id == node.value.args[0].s and not has_starargs(node.value) ): if ( is_name_attr( node.value.func, state.from_imports, ('typing',), ('NamedTuple',), ) and len(node.value.args) == 2 and not node.value.keywords and isinstance(node.value.args[1], (ast.List, ast.Tuple)) and len(node.value.args[1].elts) > 0 and all( isinstance(tup, ast.Tuple) and len(tup.elts) == 2 and isinstance(tup.elts[0], ast.Str) and tup.elts[0].s.isidentifier() and tup.elts[0].s not in KEYWORDS for tup in node.value.args[1].elts ) ): func = functools.partial(_fix_named_tuple, call=node.value) yield ast_to_offset(node), func elif ( is_name_attr( node.value.func, state.from_imports, ('typing', 'typing_extensions'), ('TypedDict',), ) and len(node.value.args) == 1 and len(node.value.keywords) > 0 and not any( keyword.arg == 'total' for keyword in node.value.keywords ) ): func = functools.partial(_fix_kw_typed_dict, call=node.value) yield ast_to_offset(node), func elif ( is_name_attr( node.value.func, state.from_imports, ('typing', 'typing_extensions'), ('TypedDict',), ) and len(node.value.args) == 2 and ( not node.value.keywords or ( len(node.value.keywords) == 1 and node.value.keywords[0].arg == 'total' and isinstance( node.value.keywords[0].value, (ast.Constant, ast.NameConstant), ) ) ) and isinstance(node.value.args[1], ast.Dict) and node.value.args[1].keys and all( isinstance(k, ast.Str) and k.s.isidentifier() and k.s not in KEYWORDS for k in node.value.args[1].keys ) ): func = functools.partial(_fix_dict_typed_dict, call=node.value) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/typing_pep563.py0000644000175000017500000001134400000000000024110 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools import sys from typing import Iterable from typing import Sequence from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import Token from pyupgrade._data import TokenFunc def _supported_version(state: State) -> bool: return 'annotations' in state.from_imports['__future__'] def _dequote(i: int, tokens: list[Token], *, new: str) -> None: tokens[i] = tokens[i]._replace(src=new) def _get_name(node: ast.expr) -> str: if isinstance(node, ast.Name): return node.id elif isinstance(node, ast.Attribute): return node.attr else: raise AssertionError(f'expected Name or Attribute: {ast.dump(node)}') def _get_keyword_value( keywords: list[ast.keyword], keyword: str, ) -> ast.expr | None: for kw in keywords: if kw.arg == keyword: return kw.value else: return None def _process_call(node: ast.Call) -> Iterable[ast.AST]: name = _get_name(node.func) args = node.args keywords = node.keywords if name == 'TypedDict': if keywords: for keyword in keywords: yield keyword.value elif len(args) != 2: # garbage pass elif isinstance(args[1], ast.Dict): yield from args[1].values else: raise AssertionError(f'expected ast.Dict: {ast.dump(args[1])}') elif name == 'NamedTuple': if len(args) == 2: fields: ast.expr | None = args[1] elif keywords: fields = _get_keyword_value(keywords, 'fields') else: # garbage fields = None if isinstance(fields, ast.List): for elt in fields.elts: if isinstance(elt, ast.Tuple) and len(elt.elts) == 2: yield elt.elts[1] elif fields is not None: raise AssertionError(f'expected ast.List: {ast.dump(fields)}') elif name in { 'Arg', 'DefaultArg', 'NamedArg', 'DefaultNamedArg', 'VarArg', 'KwArg', }: if args: yield args[0] else: keyword_value = _get_keyword_value(keywords, 'type') if keyword_value is not None: yield keyword_value def _process_subscript(node: ast.Subscript) -> Iterable[ast.AST]: name = _get_name(node.value) if name == 'Annotated': if sys.version_info >= (3, 9): # pragma: >=3.9 cover node_slice = node.slice elif isinstance(node.slice, ast.Index): # pragma: <3.9 cover node_slice: ast.AST = node.slice.value else: # pragma: <3.9 cover node_slice = node.slice if isinstance(node_slice, ast.Tuple) and node_slice.elts: yield node_slice.elts[0] elif name != 'Literal': yield node.slice def _replace_string_literal( annotation: ast.expr, ) -> Iterable[tuple[Offset, TokenFunc]]: nodes: list[ast.AST] = [annotation] while nodes: node = nodes.pop() if isinstance(node, ast.Call): nodes.extend(_process_call(node)) elif isinstance(node, ast.Subscript): nodes.extend(_process_subscript(node)) elif isinstance(node, ast.Str): func = functools.partial(_dequote, new=node.s) yield ast_to_offset(node), func else: for name in node._fields: value = getattr(node, name) if isinstance(value, ast.AST): nodes.append(value) elif isinstance(value, list): nodes.extend(value) def _process_args( args: Sequence[ast.arg | None], ) -> Iterable[tuple[Offset, TokenFunc]]: for arg in args: if arg is not None and arg.annotation is not None: yield from _replace_string_literal(arg.annotation) @register(ast.FunctionDef) def visit_FunctionDef( state: State, node: ast.FunctionDef, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if not _supported_version(state): return yield from _process_args([node.args.vararg, node.args.kwarg]) yield from _process_args(node.args.args) yield from _process_args(node.args.kwonlyargs) yield from _process_args(getattr(node.args, 'posonlyargs', [])) if node.returns is not None: yield from _replace_string_literal(node.returns) @register(ast.AnnAssign) def visit_AnnAssign( state: State, node: ast.AnnAssign, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if not _supported_version(state): return yield from _replace_string_literal(node.annotation) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/typing_pep585.py0000644000175000017500000000323700000000000024116 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import replace_name PEP585_BUILTINS = frozenset(( 'Dict', 'FrozenSet', 'List', 'Set', 'Tuple', 'Type', )) def _should_rewrite(state: State) -> bool: return ( state.settings.min_version >= (3, 9) or ( not state.settings.keep_runtime_typing and state.in_annotation and 'annotations' in state.from_imports['__future__'] ) ) @register(ast.Attribute) def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( _should_rewrite(state) and isinstance(node.value, ast.Name) and node.value.id == 'typing' and node.attr in PEP585_BUILTINS ): func = functools.partial( replace_name, name=node.attr, new=node.attr.lower(), ) yield ast_to_offset(node), func @register(ast.Name) def visit_Name( state: State, node: ast.Name, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( _should_rewrite(state) and node.id in state.from_imports['typing'] and node.id in PEP585_BUILTINS ): func = functools.partial( replace_name, name=node.id, new=node.id.lower(), ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/typing_pep604.py0000644000175000017500000001254500000000000024110 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools import sys from typing import Iterable from tokenize_rt import NON_CODING_TOKENS from tokenize_rt import Offset from tokenize_rt import Token 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 CLOSING from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_token from pyupgrade._token_helpers import OPENING def _fix_optional(i: int, tokens: list[Token]) -> None: j = find_token(tokens, i, '[') k = find_closing_bracket(tokens, j) if tokens[j].line == tokens[k].line: tokens[k] = Token('CODE', ' | None') del tokens[i:j + 1] else: tokens[j] = tokens[j]._replace(src='(') tokens[k] = tokens[k]._replace(src=')') tokens[i:j] = [Token('CODE', 'None | ')] def _fix_union( i: int, tokens: list[Token], *, arg_count: int, ) -> None: depth = 1 parens_done = [] open_parens = [] commas = [] coding_depth = None j = find_token(tokens, i, '[') k = j + 1 while depth: # it's possible our first coding token is a close paren # so make sure this is separate from the if chain below if ( tokens[k].name not in NON_CODING_TOKENS and tokens[k].src != '(' and coding_depth is None ): if tokens[k].src == ')': # the coding token was an empty tuple coding_depth = depth - 1 else: coding_depth = depth if tokens[k].src in OPENING: if tokens[k].src == '(': open_parens.append((depth, k)) depth += 1 elif tokens[k].src in CLOSING: if tokens[k].src == ')': paren_depth, open_paren = open_parens.pop() parens_done.append((paren_depth, (open_paren, k))) depth -= 1 elif tokens[k].src == ',': commas.append((depth, k)) k += 1 k -= 1 assert coding_depth is not None assert not open_parens, open_parens comma_depth = min((depth for depth, _ in commas), default=sys.maxsize) min_depth = min(comma_depth, coding_depth) to_delete = [ paren for depth, positions in parens_done if depth < min_depth for paren in positions ] if comma_depth <= coding_depth: comma_positions = [k for depth, k in commas if depth == comma_depth] if len(comma_positions) == arg_count: to_delete.append(comma_positions.pop()) else: comma_positions = [] to_delete.sort() if tokens[j].line == tokens[k].line: del tokens[k] for comma in comma_positions: tokens[comma] = Token('CODE', ' |') for paren in reversed(to_delete): del tokens[paren] del tokens[i:j + 1] else: tokens[j] = tokens[j]._replace(src='(') tokens[k] = tokens[k]._replace(src=')') for comma in comma_positions: tokens[comma] = Token('CODE', ' |') for paren in reversed(to_delete): del tokens[paren] del tokens[i:j] def _supported_version(state: State) -> bool: return ( state.in_annotation and ( state.settings.min_version >= (3, 10) or ( not state.settings.keep_runtime_typing and 'annotations' in state.from_imports['__future__'] ) ) ) def _any_arg_is_str(node_slice: ast.expr) -> bool: return ( isinstance(node_slice, ast.Str) or ( isinstance(node_slice, ast.Tuple) and any(isinstance(elt, ast.Str) for elt in node_slice.elts) ) ) @register(ast.Subscript) def visit_Subscript( state: State, node: ast.Subscript, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if not _supported_version(state): return # prevent rewriting forward annotations if ( (sys.version_info >= (3, 9) and _any_arg_is_str(node.slice)) or ( sys.version_info < (3, 9) and isinstance(node.slice, ast.Index) and _any_arg_is_str(node.slice.value) ) ): return if is_name_attr( node.value, state.from_imports, ('typing',), ('Optional',), ): yield ast_to_offset(node), _fix_optional elif is_name_attr(node.value, state.from_imports, ('typing',), ('Union',)): if sys.version_info >= (3, 9): # pragma: >=3.9 cover node_slice = node.slice elif isinstance(node.slice, ast.Index): # pragma: <3.9 cover node_slice: ast.AST = node.slice.value else: # pragma: <3.9 cover node_slice = node.slice # unexpected slice type if isinstance(node_slice, ast.Slice): # not a valid annotation return if isinstance(node_slice, ast.Tuple): if node_slice.elts: arg_count = len(node_slice.elts) else: return # empty Union else: arg_count = 1 func = functools.partial(_fix_union, arg_count=arg_count) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/typing_text.py0000644000175000017500000000243700000000000024055 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import replace_name @register(ast.Attribute) def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and isinstance(node.value, ast.Name) and node.value.id == 'typing' and node.attr == 'Text' ): func = functools.partial( replace_name, name=node.attr, new='str', ) yield ast_to_offset(node), func @register(ast.Name) def visit_Name( state: State, node: ast.Name, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and node.id in state.from_imports['typing'] and node.id == 'Text' ): func = functools.partial( replace_name, name=node.id, new='str', ) yield ast_to_offset(node), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657465213.0 pyupgrade-2.37.1/pyupgrade/_plugins/unittest_aliases.py0000644000175000017500000000453400000000000025057 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import functools from typing import Iterable from tokenize_rt import Offset from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import replace_name METHOD_MAPPING_PY27 = { 'assertEquals': 'assertEqual', 'failUnlessEqual': 'assertEqual', 'failIfEqual': 'assertNotEqual', 'failUnless': 'assertTrue', 'assert_': 'assertTrue', 'failIf': 'assertFalse', 'failUnlessRaises': 'assertRaises', 'failUnlessAlmostEqual': 'assertAlmostEqual', 'failIfAlmostEqual': 'assertNotAlmostEqual', } METHOD_MAPPING_PY35_PLUS = { **METHOD_MAPPING_PY27, 'assertNotEquals': 'assertNotEqual', 'assertAlmostEquals': 'assertAlmostEqual', 'assertNotAlmostEquals': 'assertNotAlmostEqual', 'assertRegexpMatches': 'assertRegex', 'assertNotRegexpMatches': 'assertNotRegex', 'assertRaisesRegexp': 'assertRaisesRegex', } FUNCTION_MAPPING = { 'findTestCases': 'defaultTestLoader.loadTestsFromModule', 'makeSuite': 'defaultTestLoader.loadTestsFromTestCase', 'getTestCaseNames': 'defaultTestLoader.getTestCaseNames', } @register(ast.Call) def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3,): method_mapping = METHOD_MAPPING_PY35_PLUS else: method_mapping = METHOD_MAPPING_PY27 if ( isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'self' and node.func.attr in method_mapping ): func = functools.partial( replace_name, name=node.func.attr, new=f'self.{method_mapping[node.func.attr]}', ) yield ast_to_offset(node.func), func elif ( isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'unittest' and node.func.attr in FUNCTION_MAPPING ): func = functools.partial( replace_name, name=node.func.attr, new=f'unittest.{FUNCTION_MAPPING[node.func.attr]}', ) yield ast_to_offset(node.func), func ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/pyupgrade/_plugins/unpack_list_comprehension.py0000644000175000017500000000233000000000000026734 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import Iterable from tokenize_rt import Offset from tokenize_rt import Token from pyupgrade._ast_helpers import ast_to_offset from pyupgrade._ast_helpers import is_async_listcomp from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc from pyupgrade._token_helpers import find_closing_bracket from pyupgrade._token_helpers import find_comprehension_opening_bracket def _replace_list_comprehension(i: int, tokens: list[Token]) -> None: start = find_comprehension_opening_bracket(i, tokens) end = find_closing_bracket(tokens, start) tokens[start] = tokens[start]._replace(src='(') tokens[end] = tokens[end]._replace(src=')') @register(ast.Assign) def visit_Assign( state: State, node: ast.Assign, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Tuple) and isinstance(node.value, ast.ListComp) and not is_async_listcomp(node.value) ): yield ast_to_offset(node.value), _replace_list_comprehension ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1651712425.0 pyupgrade-2.37.1/pyupgrade/_plugins/versioned_branches.py0000644000175000017500000001525000000000000025337 0ustar00asottileasottile00000000000000from __future__ import annotations import ast from typing import cast from typing import Iterable from typing import List from tokenize_rt import Offset from tokenize_rt import Token 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._data import Version from pyupgrade._token_helpers import Block def _find_if_else_block(tokens: list[Token], i: int) -> tuple[Block, Block]: if_block = Block.find(tokens, i) i = if_block.end while tokens[i].src != 'else': i += 1 else_block = Block.find(tokens, i, trim_end=True) return if_block, else_block def _find_elif(tokens: list[Token], i: int) -> int: while tokens[i].src != 'elif': # pragma: no cover (only for <3.8.1) i -= 1 return i def _fix_py3_block(i: int, tokens: list[Token]) -> None: if tokens[i].src == 'if': if_block = Block.find(tokens, i) if_block.dedent(tokens) del tokens[if_block.start:if_block.block] else: if_block = Block.find(tokens, _find_elif(tokens, i)) if_block.replace_condition(tokens, [Token('NAME', 'else')]) def _fix_py2_block(i: int, tokens: list[Token]) -> None: if tokens[i].src == 'if': if_block, else_block = _find_if_else_block(tokens, i) else_block.dedent(tokens) del tokens[if_block.start:else_block.block] else: j = _find_elif(tokens, i) if_block, else_block = _find_if_else_block(tokens, j) del tokens[if_block.start:else_block.start] def _fix_py3_block_else(i: int, tokens: list[Token]) -> None: if tokens[i].src == 'if': if_block, else_block = _find_if_else_block(tokens, i) if_block.dedent(tokens) del tokens[if_block.end:else_block.end] del tokens[if_block.start:if_block.block] else: j = _find_elif(tokens, i) if_block, else_block = _find_if_else_block(tokens, j) del tokens[if_block.end:else_block.end] if_block.replace_condition(tokens, [Token('NAME', 'else')]) def _eq(test: ast.Compare, n: int) -> bool: return ( isinstance(test.ops[0], ast.Eq) and isinstance(test.comparators[0], ast.Num) and test.comparators[0].n == n ) def _compare_to_3( test: ast.Compare, op: type[ast.cmpop] | tuple[type[ast.cmpop], ...], minor: int = 0, ) -> bool: if not ( isinstance(test.ops[0], op) and isinstance(test.comparators[0], ast.Tuple) and len(test.comparators[0].elts) >= 1 and all(isinstance(n, ast.Num) for n in test.comparators[0].elts) ): return False # checked above but mypy needs help ast_elts = cast('List[ast.Num]', test.comparators[0].elts) # padding a 0 for compatibility with (3,) used as a spec elts = tuple(e.n for e in ast_elts) + (0,) return elts[:2] == (3, minor) and all(n == 0 for n in elts[2:]) @register(ast.If) def visit_If( state: State, node: ast.If, parent: ast.AST, ) -> Iterable[tuple[Offset, TokenFunc]]: min_version: Version if state.settings.min_version == (3,): min_version = (3, 0) else: min_version = state.settings.min_version assert len(min_version) >= 2 if ( min_version >= (3,) and ( # if six.PY2: is_name_attr( node.test, state.from_imports, ('six',), ('PY2',), ) or # if not six.PY3: ( isinstance(node.test, ast.UnaryOp) and isinstance(node.test.op, ast.Not) and is_name_attr( node.test.operand, state.from_imports, ('six',), ('PY3',), ) ) or # sys.version_info == 2 or < (3,) # or < (3, n) or <= (3, n) (with n= (3,) and ( # if six.PY3: is_name_attr( node.test, state.from_imports, ('six',), ('PY3',), ) or # if not six.PY2: ( isinstance(node.test, ast.UnaryOp) and isinstance(node.test.op, ast.Not) and is_name_attr( node.test.operand, state.from_imports, ('six',), ('PY2',), ) ) or # sys.version_info == 3 or >= (3,) or > (3,) # sys.version_info >= (3, n) (with n<=m) # or sys.version_info > (3, n) (with n list[DotFormatPart]: """handle named escape sequences""" ret: list[DotFormatPart] = [] for part in NAMED_UNICODE_RE.split(s): if NAMED_UNICODE_RE.fullmatch(part): if not ret: ret.append((part, None, None, None)) else: ret[-1] = (ret[-1][0] + part, None, None, None) else: first = True for tup in _stdlib_parse_format(part): if not first or not ret: ret.append(tup) else: ret[-1] = (ret[-1][0] + tup[0], *tup[1:]) first = False if not ret: ret.append((s, None, None, None)) return ret def unparse_parsed_string(parsed: list[DotFormatPart]) -> str: def _convert_tup(tup: DotFormatPart) -> str: ret, field_name, format_spec, conversion = tup ret = curly_escape(ret) if field_name is not None: ret += '{' + field_name if conversion: ret += '!' + conversion if format_spec: ret += ':' + format_spec ret += '}' return ret return ''.join(_convert_tup(tup) for tup in parsed) def curly_escape(s: str) -> str: parts = NAMED_UNICODE_RE.split(s) return ''.join( part.replace('{', '{{').replace('}', '}}') if not NAMED_UNICODE_RE.fullmatch(part) else part for part in parts ) def is_codec(encoding: str, name: str) -> bool: try: return codecs.lookup(encoding).name == name except LookupError: return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542383.0 pyupgrade-2.37.1/pyupgrade/_token_helpers.py0000644000175000017500000003740700000000000022665 0ustar00asottileasottile00000000000000from __future__ import annotations import ast import keyword import sys from typing import NamedTuple from typing import Sequence from tokenize_rt import NON_CODING_TOKENS from tokenize_rt import Token from tokenize_rt import tokens_to_src from tokenize_rt import UNIMPORTANT_WS BRACES = {'(': ')', '[': ']', '{': '}'} OPENING, CLOSING = frozenset(BRACES), frozenset(BRACES.values()) KEYWORDS = frozenset(keyword.kwlist) def immediately_paren(func: str, tokens: list[Token], i: int) -> bool: return tokens[i].src == func and tokens[i + 1].src == '(' class Victims(NamedTuple): starts: list[int] ends: list[int] first_comma_index: int | None arg_index: int def _search_until(tokens: list[Token], idx: int, arg: ast.expr) -> int: while ( idx < len(tokens) and not ( tokens[idx].line == arg.lineno and tokens[idx].utf8_byte_offset == arg.col_offset ) ): idx += 1 return idx def find_token(tokens: list[Token], i: int, src: str) -> int: while tokens[i].src != src: i += 1 return i def find_open_paren(tokens: list[Token], i: int) -> int: return find_token(tokens, i, '(') def find_end(tokens: list[Token], i: int) -> int: while tokens[i].name not in {'NEWLINE', 'ENDMARKER'}: i += 1 # depending on the version of python, some will not emit # NEWLINE('') at the end of a file which does not end with a # newline (for example 3.7.0) if tokens[i].name == 'ENDMARKER': # pragma: no cover i -= 1 else: i += 1 return i if sys.version_info >= (3, 8): # pragma: >=3.8 cover # python 3.8 fixed the offsets of generators / tuples def _arg_token_index(tokens: list[Token], i: int, arg: ast.expr) -> int: idx = _search_until(tokens, i, arg) + 1 while idx < len(tokens) and tokens[idx].name in NON_CODING_TOKENS: idx += 1 return idx else: # pragma: <3.8 cover def _arg_token_index(tokens: list[Token], i: int, arg: ast.expr) -> int: # lists containing non-tuples report the first element correctly if isinstance(arg, ast.List): # If the first element is a tuple, the ast lies to us about its col # offset. We must find the first `(` token after the start of the # list element. if isinstance(arg.elts[0], ast.Tuple): i = _search_until(tokens, i, arg) return find_open_paren(tokens, i) else: return _search_until(tokens, i, arg.elts[0]) # others' start position points at their first child node already else: return _search_until(tokens, i, arg) def victims( tokens: list[Token], start: int, arg: ast.expr, gen: bool, ) -> Victims: starts = [start] start_depths = [1] ends: list[int] = [] first_comma_index = None arg_depth = None arg_index = _arg_token_index(tokens, start, arg) brace_stack = [tokens[start].src] i = start + 1 while brace_stack: token = tokens[i].src is_start_brace = token in BRACES is_end_brace = token == BRACES[brace_stack[-1]] if i == arg_index: arg_depth = len(brace_stack) if is_start_brace: brace_stack.append(token) # Remove all braces before the first element of the inner # comprehension's target. if is_start_brace and arg_depth is None: start_depths.append(len(brace_stack)) starts.append(i) if ( token == ',' and len(brace_stack) == arg_depth and first_comma_index is None ): first_comma_index = i if is_end_brace and len(brace_stack) in start_depths: if tokens[i - 2].src == ',' and tokens[i - 1].src == ' ': ends.extend((i - 2, i - 1, i)) elif tokens[i - 1].src == ',': ends.extend((i - 1, i)) else: ends.append(i) if len(brace_stack) > 1 and tokens[i + 1].src == ',': ends.append(i + 1) if is_end_brace: brace_stack.pop() i += 1 # May need to remove a trailing comma for a comprehension if gen: i -= 2 while tokens[i].name in NON_CODING_TOKENS: i -= 1 if tokens[i].src == ',': ends.append(i) return Victims(starts, sorted(set(ends)), first_comma_index, arg_index) def find_closing_bracket(tokens: list[Token], i: int) -> int: assert tokens[i].src in OPENING depth = 1 i += 1 while depth: if tokens[i].src in OPENING: depth += 1 elif tokens[i].src in CLOSING: depth -= 1 i += 1 return i - 1 def find_block_start(tokens: list[Token], i: int) -> int: depth = 0 while depth or tokens[i].src != ':': if tokens[i].src in OPENING: depth += 1 elif tokens[i].src in CLOSING: depth -= 1 i += 1 return i class Block(NamedTuple): start: int colon: int block: int end: int line: bool def _initial_indent(self, tokens: list[Token]) -> int: if tokens[self.start].src.isspace(): return len(tokens[self.start].src) else: return 0 def _minimum_indent(self, tokens: list[Token]) -> int: block_indent = None for i in range(self.block, self.end): if ( tokens[i - 1].name in ('NL', 'NEWLINE') and tokens[i].name in ('INDENT', UNIMPORTANT_WS) and # comments can have arbitrary indentation so ignore them tokens[i + 1].name != 'COMMENT' ): token_indent = len(tokens[i].src) if block_indent is None: block_indent = token_indent else: block_indent = min(block_indent, token_indent) assert block_indent is not None return block_indent def dedent(self, tokens: list[Token]) -> None: if self.line: return initial_indent = self._initial_indent(tokens) diff = self._minimum_indent(tokens) - initial_indent for i in range(self.block, self.end): if ( tokens[i - 1].name in ('DEDENT', 'NL', 'NEWLINE') and tokens[i].name in ('INDENT', UNIMPORTANT_WS) ): # make sure we preserve *at least* the initial indent s = tokens[i].src s = s[:initial_indent] + s[initial_indent + diff:] tokens[i] = tokens[i]._replace(src=s) def replace_condition(self, tokens: list[Token], new: list[Token]) -> None: start = self.start while tokens[start].name == 'UNIMPORTANT_WS': start += 1 tokens[start:self.colon] = new def _trim_end(self, tokens: list[Token]) -> Block: """the tokenizer reports the end of the block at the beginning of the next block """ i = last_token = self.end - 1 while tokens[i].name in NON_CODING_TOKENS | {'DEDENT', 'NEWLINE'}: # if we find an indented comment inside our block, keep it if ( tokens[i].name in {'NL', 'NEWLINE'} and tokens[i + 1].name == UNIMPORTANT_WS and len(tokens[i + 1].src) > self._initial_indent(tokens) ): break # otherwise we've found another line to remove elif tokens[i].name in {'NL', 'NEWLINE'}: last_token = i i -= 1 return self._replace(end=last_token + 1) @classmethod def find( cls, tokens: list[Token], i: int, trim_end: bool = False, ) -> Block: if i > 0 and tokens[i - 1].name in {'INDENT', UNIMPORTANT_WS}: i -= 1 start = i colon = find_block_start(tokens, i) j = colon + 1 while ( tokens[j].name != 'NEWLINE' and tokens[j].name in NON_CODING_TOKENS ): j += 1 if tokens[j].name == 'NEWLINE': # multi line block block = j + 1 while tokens[j].name != 'INDENT': j += 1 level = 1 j += 1 while level: level += {'INDENT': 1, 'DEDENT': -1}.get(tokens[j].name, 0) j += 1 ret = cls(start, colon, block, j, line=False) if trim_end: return ret._trim_end(tokens) else: return ret else: # single line block block = j j = find_end(tokens, j) return cls(start, colon, block, j, line=True) def _is_on_a_line_by_self(tokens: list[Token], i: int) -> bool: return ( tokens[i - 2].name == 'NL' and tokens[i - 1].name == UNIMPORTANT_WS and tokens[i + 1].name == 'NL' ) def remove_brace(tokens: list[Token], i: int) -> None: if _is_on_a_line_by_self(tokens, i): del tokens[i - 1:i + 2] else: del tokens[i] def remove_base_class(i: int, tokens: list[Token]) -> None: # look forward and backward to find commas / parens brace_stack = [] j = i while tokens[j].src not in {',', ':'}: if tokens[j].src == ')': brace_stack.append(j) j += 1 right = j if tokens[right].src == ':': brace_stack.pop() else: # if there's a close-paren after a trailing comma j = right + 1 while tokens[j].name in NON_CODING_TOKENS: j += 1 if tokens[j].src == ')': while tokens[j].src != ':': j += 1 right = j if brace_stack: last_part = brace_stack[-1] else: last_part = i j = i while brace_stack: if tokens[j].src == '(': brace_stack.pop() j -= 1 while tokens[j].src not in {',', '('}: j -= 1 left = j # single base, remove the entire bases if tokens[left].src == '(' and tokens[right].src == ':': del tokens[left:right] # multiple bases, base is first elif tokens[left].src == '(' and tokens[right].src != ':': # if there's space / comment afterwards remove that too while tokens[right + 1].name in {UNIMPORTANT_WS, 'COMMENT'}: right += 1 del tokens[left + 1:right + 1] # multiple bases, base is not first else: del tokens[left:last_part + 1] def remove_decorator(i: int, tokens: list[Token]) -> None: while tokens[i - 1].src != '@': i -= 1 if i > 1 and tokens[i - 2].name not in {'NEWLINE', 'NL'}: i -= 1 end = i + 1 while tokens[end].name != 'NEWLINE': end += 1 del tokens[i - 1:end + 1] def parse_call_args( tokens: list[Token], i: int, ) -> tuple[list[tuple[int, int]], int]: args = [] stack = [i] i += 1 arg_start = i while stack: token = tokens[i] if len(stack) == 1 and token.src == ',': args.append((arg_start, i)) arg_start = i + 1 elif token.src in BRACES: stack.append(i) elif token.src == BRACES[tokens[stack[-1]].src]: stack.pop() # if we're at the end, append that argument if not stack and tokens_to_src(tokens[arg_start:i]).strip(): args.append((arg_start, i)) i += 1 return args, i def arg_str(tokens: list[Token], start: int, end: int) -> str: return tokens_to_src(tokens[start:end]).strip() def _arg_contains_newline(tokens: list[Token], start: int, end: int) -> bool: while tokens[start].name in {'NL', 'NEWLINE', UNIMPORTANT_WS}: start += 1 for i in range(start, end): if tokens[i].name in {'NL', 'NEWLINE'}: return True else: return False def replace_call( tokens: list[Token], start: int, end: int, args: list[tuple[int, int]], tmpl: str, *, parens: Sequence[int] = (), ) -> None: arg_strs = [arg_str(tokens, *arg) for arg in args] for paren in parens: arg_strs[paren] = f'({arg_strs[paren]})' # there are a few edge cases which cause syntax errors when the first # argument contains newlines (especially when moved outside of a natural # continuation context) if _arg_contains_newline(tokens, *args[0]) and 0 not in parens: # this attempts to preserve more of the whitespace by using the # original non-stripped argument string arg_strs[0] = f'({tokens_to_src(tokens[slice(*args[0])])})' start_rest = args[0][1] + 1 while ( start_rest < end and tokens[start_rest].name in {'COMMENT', UNIMPORTANT_WS} ): start_rest += 1 # Remove trailing comma end_rest = end - 1 while ( tokens[end_rest - 1].name == 'OP' and tokens[end_rest - 1].src == ',' ): end_rest -= 1 rest = tokens_to_src(tokens[start_rest:end_rest]) src = tmpl.format(args=arg_strs, rest=rest) tokens[start:end] = [Token('CODE', src)] def find_and_replace_call( i: int, tokens: list[Token], *, template: str, parens: tuple[int, ...] = (), ) -> None: j = find_open_paren(tokens, i) func_args, end = parse_call_args(tokens, j) replace_call(tokens, i, end, func_args, template, parens=parens) def replace_name(i: int, tokens: list[Token], *, name: str, new: str) -> None: # preserve token offset in case we need to match it later new_token = tokens[i]._replace(name='CODE', src=new) j = i while tokens[j].src != name: # timid: if we see a parenthesis here, skip it if tokens[j].src == ')': return j += 1 tokens[i:j + 1] = [new_token] def delete_argument( i: int, tokens: list[Token], func_args: Sequence[tuple[int, int]], ) -> None: if i == 0: # delete leading whitespace before next token end_idx, _ = func_args[i + 1] while tokens[end_idx].name == 'UNIMPORTANT_WS': end_idx += 1 del tokens[func_args[i][0]:end_idx] else: del tokens[func_args[i - 1][1]:func_args[i][1]] def replace_argument( i: int, tokens: list[Token], func_args: Sequence[tuple[int, int]], *, new: str, ) -> None: start_idx, end_idx = func_args[i] # don't replace leading whitespace / newlines while tokens[start_idx].name in {'UNIMPORTANT_WS', 'NL'}: start_idx += 1 tokens[start_idx:end_idx] = [Token('SRC', new)] def find_comprehension_opening_bracket(i: int, tokens: list[Token]) -> int: """Find opening bracket of comprehension given first argument.""" if sys.version_info < (3, 8): # pragma: <3.8 cover i -= 1 while not (tokens[i].name == 'OP' and tokens[i].src == '[') and i: i -= 1 return i else: # pragma: >=3.8 cover return i def replace_list_comp_brackets(i: int, tokens: list[Token]) -> None: start = find_comprehension_opening_bracket(i, tokens) end = find_closing_bracket(tokens, start) tokens[end] = Token('OP', ')') tokens[start] = Token('OP', '(') def has_space_before(i: int, tokens: list[Token]) -> bool: return i >= 1 and tokens[i - 1].name in {UNIMPORTANT_WS, 'INDENT'} def indented_amount(i: int, tokens: list[Token]) -> str: if i == 0: return '' elif has_space_before(i, tokens): if i >= 2 and tokens[i - 2].name in {'NL', 'NEWLINE', 'DEDENT'}: return tokens[i - 1].src else: # inline import raise ValueError('not at beginning of line') elif tokens[i - 1].name not in {'NL', 'NEWLINE', 'DEDENT'}: raise ValueError('not at beginning of line') else: return '' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1657542385.1854024 pyupgrade-2.37.1/pyupgrade.egg-info/0000755000175000017500000000000000000000000020771 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542384.0 pyupgrade-2.37.1/pyupgrade.egg-info/PKG-INFO0000644000175000017500000003703600000000000022077 0ustar00asottileasottile00000000000000Metadata-Version: 2.1 Name: pyupgrade Version: 2.37.1 Summary: A tool to automatically upgrade syntax for newer versions. Home-page: https://github.com/asottile/pyupgrade Author: Anthony Sottile Author-email: asottile@umich.edu License: MIT Platform: UNKNOWN Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE [![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/asottile.pyupgrade?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=2&branchName=main) [![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/2/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=2&branchName=main) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/pyupgrade/main.svg)](https://results.pre-commit.ci/latest/github/asottile/pyupgrade/main) pyupgrade ========= A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language. ## Installation `pip install pyupgrade` ## 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/pyupgrade rev: v2.37.1 hooks: - id: pyupgrade ``` ## Implemented features ### Set literals ```diff -set(()) +set() -set([]) +set() -set((1,)) +{1} -set((1, 2)) +{1, 2} -set([1, 2]) +{1, 2} -set(x for x in y) +{x for x in y} -set([x for x in y]) +{x for x in y} ``` ### Dictionary comprehensions ```diff -dict((a, b) for a, b in y) +{a: b for a, b in y} -dict([(a, b) for a, b in y]) +{a: b for a, b in y} ``` ### Generator expressions for some built-in functions (pep 289) ```diff -min([i for i in range(3)]) +min(i for i in range(3)) -max([i for i in range(3)]) +max(i for i in range(3)) -sum([i for i in range(3)]) +sum(i for i in range(3)) -''.join([str(i) for i in range(3)]) +''.join(str(i) for i in range(3)) ``` ### Python2.7+ Format Specifiers ```diff -'{0} {1}'.format(1, 2) +'{} {}'.format(1, 2) -'{0}' '{1}'.format(1, 2) +'{}' '{}'.format(1, 2) ``` ### printf-style string formatting Availability: - Unless `--keep-percent-format` is passed. ```diff -'%s %s' % (a, b) +'{} {}'.format(a, b) -'%r %2f' % (a, b) +'{!r} {:2f}'.format(a, b) -'%(a)s %(b)s' % {'a': 1, 'b': 2} +'{a} {b}'.format(a=1, b=2) ``` ### Unicode literals Availability: - File imports `from __future__ import unicode_literals` - `--py3-plus` is passed on the commandline. ```diff -u'foo' +'foo' -u"foo" +'foo' -u'''foo''' +'''foo''' ``` ### Invalid escape sequences ```diff # strings with only invalid sequences become raw strings -'\d' +r'\d' # strings with mixed valid / invalid sequences get escaped -'\n\d' +'\n\\d' # `ur` is not a valid string prefix in python3 -u'\d' +u'\\d' # this fixes a syntax error in python3.3+ -'\N' +r'\N' # note: pyupgrade is timid in one case (that's usually a mistake) # in python2.x `'\u2603'` is the same as `'\\u2603'` without `unicode_literals` # but in python3.x, that's our friend ☃ ``` ### `is` / `is not` comparison to constant literals In python3.8+, comparison to literals becomes a `SyntaxWarning` as the success of those comparisons is implementation specific (due to common object caching). ```diff -x is 5 +x == 5 -x is not 5 +x != 5 -x is 'foo' +x == 'foo' ``` ### `ur` string literals `ur'...'` literals are not valid in python 3.x ```diff -ur'foo' +u'foo' -ur'\s' +u'\\s' # unicode escapes are left alone -ur'\u2603' +u'\u2603' -ur'\U0001f643' +u'\U0001f643' ``` ### `.encode()` to bytes literals ```diff -'foo'.encode() +b'foo' -'foo'.encode('ascii') +b'foo' -'foo'.encode('utf-8') +b'foo' -u'foo'.encode() +b'foo' -'\xa0'.encode('latin1') +b'\xa0' ``` ### Long literals ```diff -5L +5 -5l +5 -123456789123456789123456789L +123456789123456789123456789 ``` ### Octal literals ```diff -0755 +0o755 -05 +5 ``` ### extraneous parens in `print(...)` A fix for [python-modernize/python-modernize#178] ```diff # ok: printing an empty tuple print(()) # ok: printing a tuple print((1,)) # ok: parenthesized generator argument sum((i for i in range(3)), []) # fixed: -print(("foo")) +print("foo") ``` [python-modernize/python-modernize#178]: https://github.com/python-modernize/python-modernize/issues/178 ### unittest deprecated aliases Rewrites [deprecated unittest method aliases](https://docs.python.org/3/library/unittest.html#deprecated-aliases) to their non-deprecated forms. Availability: - More deprecated aliases are rewritten with `--py3-plus` ```diff from unittest import TestCase class MyTests(TestCase): def test_something(self): - self.failUnlessEqual(1, 1) + self.assertEqual(1, 1) - self.assertEquals(1, 1) + self.assertEqual(1, 1) ``` ### `super()` calls Availability: - `--py3-plus` is passed on the commandline. ```diff class C(Base): def f(self): - super(C, self).f() + super().f() ``` ### "new style" classes Availability: - `--py3-plus` is passed on the commandline. #### rewrites class declaration ```diff -class C(object): pass +class C: pass -class C(B, object): pass +class C(B): pass ``` #### removes `__metaclass__ = type` declaration ```diff class C: - __metaclass__ = type ``` ### forced `str("native")` literals Availability: - `--py3-plus` is passed on the commandline. ```diff -str() +'' -str("foo") +"foo" ``` ### `.encode("utf-8")` Availability: - `--py3-plus` is passed on the commandline. ```diff -"foo".encode("utf-8") +"foo".encode() ``` ### `# coding: ...` comment Availability: - `--py3-plus` is passed on the commandline. as of [PEP 3120], the default encoding for python source is UTF-8 ```diff -# coding: utf-8 x = 1 ``` [PEP 3120]: https://www.python.org/dev/peps/pep-3120/ ### `__future__` import removal Availability: - by default removes `nested_scopes`, `generators`, `with_statement` - `--py3-plus` will also remove `absolute_import` / `division` / `print_function` / `unicode_literals` - `--py37-plus` will also remove `generator_stop` ```diff -from __future__ import with_statement ``` ### Remove unnecessary py3-compat imports Availability: - `--py3-plus` is passed on the commandline. ```diff -from io import open -from six.moves import map -from builtins import object # python-future ``` ### import replacements Availability: - `--py3-plus` (and others) will replace imports see also [reorder-python-imports](https://github.com/asottile/reorder_python_imports#removing--rewriting-obsolete-six-imports) some examples: ```diff -from collections import deque, Mapping +from collections import deque +from collections.abc import Mapping ``` ```diff -from typing import Sequence +from collections.abc import Sequence ``` ```diff -from typing_extensions import Concatenate +from typing import Concatenate ``` ### rewrite `mock` imports Availability: - `--py3-plus` is passed on the commandline. - [Unless `--keep-mock` is passed on the commandline](https://github.com/asottile/pyupgrade/issues/314). ```diff -from mock import patch +from unittest.mock import patch ``` ### `yield` => `yield from` Availability: - `--py3-plus` is passed on the commandline. ```diff def f(): - for x in y: - yield x + yield from y - for a, b in c: - yield (a, b) + yield from c ``` ### Python2 and old Python3.x blocks Availability: - `--py3-plus` is passed on the commandline. ```diff import sys -if sys.version_info < (3,): # also understands `six.PY2` (and `not`), `six.PY3` (and `not`) - print('py2') -else: - print('py3') +print('py3') ``` Availability: - `--py36-plus` will remove Python <= 3.5 only blocks - `--py37-plus` will remove Python <= 3.6 only blocks - so on and so forth ```diff # using --py36-plus for this example import sys -if sys.version_info < (3, 6): - print('py3.5') -else: - print('py3.6+') +print('py3.6+') -if sys.version_info <= (3, 5): - print('py3.5') -else: - print('py3.6+') +print('py3.6+') -if sys.version_info >= (3, 6): - print('py3.6+') -else: - print('py3.5') +print('py3.6+') ``` Note that `if` blocks without an `else` will not be rewritten as it could introduce a syntax error. ### remove `six` compatibility code Availability: - `--py3-plus` is passed on the commandline. ```diff -six.text_type +str -six.binary_type +bytes -six.class_types +(type,) -six.string_types +(str,) -six.integer_types +(int,) -six.unichr +chr -six.iterbytes +iter -six.print_(...) +print(...) -six.exec_(c, g, l) +exec(c, g, l) -six.advance_iterator(it) +next(it) -six.next(it) +next(it) -six.callable(x) +callable(x) -six.moves.range(x) +range(x) -six.moves.xrange(x) +range(x) -from six import text_type -text_type +str -@six.python_2_unicode_compatible class C: def __str__(self): return u'C()' -class C(six.Iterator): pass +class C: pass -class C(six.with_metaclass(M, B)): pass +class C(B, metaclass=M): pass -@six.add_metaclass(M) -class C(B): pass +class C(B, metaclass=M): pass -isinstance(..., six.class_types) +isinstance(..., type) -issubclass(..., six.integer_types) +issubclass(..., int) -isinstance(..., six.string_types) +isinstance(..., str) -six.b('...') +b'...' -six.u('...') +'...' -six.byte2int(bs) +bs[0] -six.indexbytes(bs, i) +bs[i] -six.int2byte(i) +bytes((i,)) -six.iteritems(dct) +dct.items() -six.iterkeys(dct) +dct.keys() -six.itervalues(dct) +dct.values() -next(six.iteritems(dct)) +next(iter(dct.items())) -next(six.iterkeys(dct)) +next(iter(dct.keys())) -next(six.itervalues(dct)) +next(iter(dct.values())) -six.viewitems(dct) +dct.items() -six.viewkeys(dct) +dct.keys() -six.viewvalues(dct) +dct.values() -six.create_unbound_method(fn, cls) +fn -six.get_unbound_function(meth) +meth -six.get_method_function(meth) +meth.__func__ -six.get_method_self(meth) +meth.__self__ -six.get_function_closure(fn) +fn.__closure__ -six.get_function_code(fn) +fn.__code__ -six.get_function_defaults(fn) +fn.__defaults__ -six.get_function_globals(fn) +fn.__globals__ -six.raise_from(exc, exc_from) +raise exc from exc_from -six.reraise(tp, exc, tb) +raise exc.with_traceback(tb) -six.reraise(*sys.exc_info()) +raise -six.assertCountEqual(self, a1, a2) +self.assertCountEqual(a1, a2) -six.assertRaisesRegex(self, e, r, fn) +self.assertRaisesRegex(e, r, fn) -six.assertRegex(self, s, r) +self.assertRegex(s, r) # note: only for *literals* -six.ensure_binary('...') +b'...' -six.ensure_str('...') +'...' -six.ensure_text('...') +'...' ``` ### `open` alias Availability: - `--py3-plus` is passed on the commandline. ```diff -with io.open('f.txt') as f: +with open('f.txt') as f: ... ``` ### redundant `open` modes Availability: - `--py3-plus` is passed on the commandline. ```diff -open("foo", "U") +open("foo") -open("foo", "Ur") +open("foo") -open("foo", "Ub") +open("foo", "rb") -open("foo", "rUb") +open("foo", "rb") -open("foo", "r") +open("foo") -open("foo", "rt") +open("foo") -open("f", "r", encoding="UTF-8") +open("f", encoding="UTF-8") ``` ### `OSError` aliases Availability: - `--py3-plus` is passed on the commandline. ```diff # also understands: # - IOError # - WindowsError # - mmap.error and uses of `from mmap import error` # - select.error and uses of `from select import error` # - socket.error and uses of `from socket import error` def throw(): - raise EnvironmentError('boom') + raise OSError('boom') def catch(): try: throw() - except EnvironmentError: + except OSError: handle_error() ``` ### `typing.Text` str alias Availability: - `--py3-plus` is passed on the commandline. ```diff -def f(x: Text) -> None: +def f(x: str) -> None: ... ``` ### Unpacking list comprehensions Availability: - `--py3-plus` is passed on the commandline. ```diff -foo, bar, baz = [fn(x) for x in items] +foo, bar, baz = (fn(x) for x in items) ``` ### Rewrite `xml.etree.cElementTree` to `xml.etree.ElementTree` Availability: - `--py3-plus` is passed on the commandline. ```diff -import xml.etree.cElementTree as ET +import xml.etree.ElementTree as ET -from xml.etree.cElementTree import XML +from xml.etree.ElementTree import XML ``` ### Rewrite `type` of primitive Availability: - `--py3-plus` is passed on the commandline. ```diff -type('') +str -type(b'') +bytes -type(0) +int -type(0.) +float ``` ### `typing.NamedTuple` / `typing.TypedDict` py36+ syntax Availability: - `--py36-plus` is passed on the commandline. ```diff -NT = typing.NamedTuple('NT', [('a', int), ('b', Tuple[str, ...])]) +class NT(typing.NamedTuple): + a: int + b: Tuple[str, ...] -D1 = typing.TypedDict('D1', a=int, b=str) +class D1(typing.TypedDict): + a: int + b: str -D2 = typing.TypedDict('D2', {'a': int, 'b': str}) +class D2(typing.TypedDict): + a: int + b: str ``` ### f-strings Availability: - `--py36-plus` is passed on the commandline. ```diff -'{foo} {bar}'.format(foo=foo, bar=bar) +f'{foo} {bar}' -'{} {}'.format(foo, bar) +f'{foo} {bar}' -'{} {}'.format(foo.bar, baz.womp) +f'{foo.bar} {baz.womp}' -'{} {}'.format(f(), g()) +f'{f()} {g()}' -'{x}'.format(**locals()) +f'{x}' ``` _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 sufficiently complicated (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) ``` ### `subprocess.run`: replace `stdout=subprocess.PIPE, stderr=subprocess.PIPE` with `capture_output=True` Availability: - `--py37-plus` is passed on the commandline. ```diff -output = subprocess.run(['foo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +output = subprocess.run(['foo'], capture_output=True) ``` ### remove parentheses from `@functools.lru_cache()` Availability: - `--py38-plus` is passed on the commandline. ```diff import functools -@functools.lru_cache() +@functools.lru_cache def expensive(): ... ``` ### replace `@functools.lru_cache(maxsize=None)` with shorthand Availability: - `--py39-plus` is passed on the commandline. ```diff import functools -@functools.lru_cache(maxsize=None) +@functools.cache def expensive(): ... ``` ### pep 585 typing rewrites Availability: - File imports `from __future__ import annotations` - Unless `--keep-runtime-typing` is passed on the commandline. - `--py39-plus` is passed on the commandline. ```diff -def f(x: List[str]) -> None: +def f(x: list[str]) -> None: ... ``` ### remove unnecessary abspath Availability: - `--py39-plus` is passed on the commandline. ```diff from os.path import abspath -abspath(__file__) +__file__ ``` ### pep 604 typing rewrites Availability: - File imports `from __future__ import annotations` - Unless `--keep-runtime-typing` is passed on the commandline. - `--py310-plus` is passed on the commandline. ```diff -def f() -> Optional[str]: +def f() -> str | None: ... ``` ```diff -def f() -> Union[int, str]: +def f() -> int | str: ... ``` ### remove quoted annotations Availability: - File imports `from __future__ import annotations` ```diff -def f(x: 'queue.Queue[int]') -> C: +def f(x: queue.Queue[int]) -> C: ``` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542385.0 pyupgrade-2.37.1/pyupgrade.egg-info/SOURCES.txt0000644000175000017500000000326100000000000022657 0ustar00asottileasottile00000000000000LICENSE README.md setup.cfg setup.py pyupgrade/__init__.py pyupgrade/__main__.py pyupgrade/_ast_helpers.py pyupgrade/_data.py pyupgrade/_main.py pyupgrade/_string_helpers.py pyupgrade/_token_helpers.py pyupgrade.egg-info/PKG-INFO pyupgrade.egg-info/SOURCES.txt pyupgrade.egg-info/dependency_links.txt pyupgrade.egg-info/entry_points.txt pyupgrade.egg-info/requires.txt pyupgrade.egg-info/top_level.txt pyupgrade/_plugins/__init__.py pyupgrade/_plugins/abspath_file.py pyupgrade/_plugins/collections_abc.py pyupgrade/_plugins/default_encoding.py pyupgrade/_plugins/dict_literals.py pyupgrade/_plugins/format_locals.py pyupgrade/_plugins/fstrings.py pyupgrade/_plugins/generator_expressions_pep289.py pyupgrade/_plugins/identity_equality.py pyupgrade/_plugins/imports.py pyupgrade/_plugins/io_open.py pyupgrade/_plugins/legacy.py pyupgrade/_plugins/lru_cache.py pyupgrade/_plugins/metaclass_type.py pyupgrade/_plugins/mock.py pyupgrade/_plugins/native_literals.py pyupgrade/_plugins/new_style_classes.py pyupgrade/_plugins/open_mode.py pyupgrade/_plugins/oserror_aliases.py pyupgrade/_plugins/percent_format.py pyupgrade/_plugins/set_literals.py pyupgrade/_plugins/six_base_classes.py pyupgrade/_plugins/six_calls.py pyupgrade/_plugins/six_metaclasses.py pyupgrade/_plugins/six_remove_decorators.py pyupgrade/_plugins/six_simple.py pyupgrade/_plugins/subprocess_run.py pyupgrade/_plugins/type_of_primitive.py pyupgrade/_plugins/typing_classes.py pyupgrade/_plugins/typing_pep563.py pyupgrade/_plugins/typing_pep585.py pyupgrade/_plugins/typing_pep604.py pyupgrade/_plugins/typing_text.py pyupgrade/_plugins/unittest_aliases.py pyupgrade/_plugins/unpack_list_comprehension.py pyupgrade/_plugins/versioned_branches.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542384.0 pyupgrade-2.37.1/pyupgrade.egg-info/dependency_links.txt0000644000175000017500000000000100000000000025037 0ustar00asottileasottile00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542384.0 pyupgrade-2.37.1/pyupgrade.egg-info/entry_points.txt0000644000175000017500000000006300000000000024266 0ustar00asottileasottile00000000000000[console_scripts] pyupgrade = pyupgrade._main:main ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542384.0 pyupgrade-2.37.1/pyupgrade.egg-info/requires.txt0000644000175000017500000000002300000000000023364 0ustar00asottileasottile00000000000000tokenize-rt>=3.2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1657542384.0 pyupgrade-2.37.1/pyupgrade.egg-info/top_level.txt0000644000175000017500000000001200000000000023514 0ustar00asottileasottile00000000000000pyupgrade ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1657542385.1894023 pyupgrade-2.37.1/setup.cfg0000644000175000017500000000255100000000000017123 0ustar00asottileasottile00000000000000[metadata] name = pyupgrade version = 2.37.1 description = A tool to automatically upgrade syntax for newer versions. long_description = file: README.md long_description_content_type = text/markdown url = https://github.com/asottile/pyupgrade author = Anthony Sottile author_email = asottile@umich.edu license = MIT license_file = LICENSE classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy [options] packages = find: install_requires = tokenize-rt>=3.2.0 python_requires = >=3.7 [options.packages.find] exclude = tests* testing* [options.entry_points] console_scripts = pyupgrade = pyupgrade._main: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 no_implicit_optional = true warn_redundant_casts = true warn_unused_ignores = true [mypy-testing.*] disallow_untyped_defs = false [mypy-tests.*] disallow_untyped_defs = false [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645363292.0 pyupgrade-2.37.1/setup.py0000644000175000017500000000011100000000000017002 0ustar00asottileasottile00000000000000from __future__ import annotations from setuptools import setup setup()