
Research
Security News
The Landscape of Malicious Open Source Packages: 2025 Mid‑Year Threat Report
A look at the top trends in how threat actors are weaponizing open source packages to deliver malware and persist across the software supply chain.
A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language.
pip install pyupgrade
See pre-commit for instructions
Sample .pre-commit-config.yaml
:
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.1
hooks:
- id: pyupgrade
-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}
-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}
collections.defaultdict
calls-defaultdict(lambda: [])
+defaultdict(list)
-defaultdict(lambda: list())
+defaultdict(list)
-defaultdict(lambda: {})
+defaultdict(dict)
-defaultdict(lambda: dict())
+defaultdict(dict)
-defaultdict(lambda: ())
+defaultdict(tuple)
-defaultdict(lambda: tuple())
+defaultdict(tuple)
-defaultdict(lambda: set())
+defaultdict(set)
-defaultdict(lambda: 0)
+defaultdict(int)
-defaultdict(lambda: 0.0)
+defaultdict(float)
-defaultdict(lambda: 0j)
+defaultdict(complex)
-defaultdict(lambda: '')
+defaultdict(str)
-'{0} {1}'.format(1, 2)
+'{} {}'.format(1, 2)
-'{0}' '{1}'.format(1, 2)
+'{}' '{}'.format(1, 2)
Availability:
--keep-percent-format
is passed.-'%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)
-u'foo'
+'foo'
-u"foo"
+'foo'
-u'''foo'''
+'''foo'''
# strings with only invalid sequences become raw strings
-'\d'
+r'\d'
# strings with mixed valid / invalid sequences get escaped
-'\n\d'
+'\n\\d'
-u'\d'
+r'\d'
# this fixes a syntax error in python3.3+
-'\N'
+r'\N'
is
/ is not
comparison to constant literalsIn python3.8+, comparison to literals becomes a SyntaxWarning
as the success
of those comparisons is implementation specific (due to common object caching).
-x is 5
+x == 5
-x is not 5
+x != 5
-x is 'foo'
+x == 'foo'
.encode()
to bytes literals-'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'
print(...)
A fix for python-modernize/python-modernize#178
# 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")
isinstance
/ issubclass
/ except
-isinstance(x, (int, int))
+isinstance(x, int)
-issubclass(y, (str, str))
+issubclass(y, str)
try:
raises()
-except (Error1, Error1, Error2):
+except (Error1, Error2):
pass
Rewrites deprecated unittest method aliases to their non-deprecated forms.
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 class C(Base):
def f(self):
- super(C, self).f()
+ super().f()
-class C(object): pass
+class C: pass
-class C(B, object): pass
+class C(B): pass
__metaclass__ = type
declaration class C:
- __metaclass__ = type
str("native")
literals-str()
+''
-str("foo")
+"foo"
.encode("utf-8")
-"foo".encode("utf-8")
+"foo".encode()
# coding: ...
commentas of PEP 3120, the default encoding for python source is UTF-8
-# coding: utf-8
x = 1
__future__
import removalAvailability:
nested_scopes
, generators
, with_statement
,
absolute_import
, division
, print_function
, unicode_literals
--py37-plus
will also remove generator_stop
-from __future__ import with_statement
-from io import open
-from six.moves import map
-from builtins import object # python-future
Availability:
--py36-plus
(and others) will replace importssee also reorder-python-imports
some examples:
-from collections import deque, Mapping
+from collections import deque
+from collections.abc import Mapping
-from typing import Sequence
+from collections.abc import Sequence
-from typing_extensions import Concatenate
+from typing import Concatenate
mock
importsAvailability:
-from mock import patch
+from unittest.mock import patch
yield
=> yield from
def f():
- for x in y:
- yield x
+ yield from y
- for a, b in c:
- yield (a, b)
+ yield from c
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 # 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.
six
compatibility code-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-with io.open('f.txt') as f:
+with open('f.txt') as f:
...
open
modes-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")
-open("f", "wt")
+open("f", "w")
OSError
aliases # 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()
TimeoutError
aliasesAvailability:
--py310-plus
for socket.timeout
--py311-plus
for asyncio.TimeoutError
def throw(a):
if a:
- raise asyncio.TimeoutError('boom')
+ raise TimeoutError('boom')
else:
- raise socket.timeout('boom')
+ raise TimeoutError('boom')
def catch(a):
try:
throw(a)
- except (asyncio.TimeoutError, socket.timeout):
+ except TimeoutError:
handle_error()
typing.Text
str alias-def f(x: Text) -> None:
+def f(x: str) -> None:
...
-foo, bar, baz = [fn(x) for x in items]
+foo, bar, baz = (fn(x) for x in items)
xml.etree.cElementTree
to xml.etree.ElementTree
-import xml.etree.cElementTree as ET
+import xml.etree.ElementTree as ET
-from xml.etree.cElementTree import XML
+from xml.etree.ElementTree import XML
type
of primitive-type('')
+str
-type(b'')
+bytes
-type(0)
+int
-type(0.)
+float
typing.NamedTuple
/ typing.TypedDict
py36+ syntaxAvailability:
--py36-plus
is passed on the commandline.-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
Availability:
--py36-plus
is passed on the commandline.-'{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.-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.-output = subprocess.run(['foo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+output = subprocess.run(['foo'], capture_output=True)
@functools.lru_cache()
Availability:
--py38-plus
is passed on the commandline. import functools
-@functools.lru_cache()
+@functools.lru_cache
def expensive():
...
Availability:
--py38-plus
is passed on the commandline.-' '.join(shlex.quote(arg) for arg in cmd)
+shlex.join(cmd)
@functools.lru_cache(maxsize=None)
with shorthandAvailability:
--py39-plus
is passed on the commandline. import functools
-@functools.lru_cache(maxsize=None)
+@functools.cache
def expensive():
...
Availability:
from __future__ import annotations
--keep-runtime-typing
is passed on the commandline.--py39-plus
is passed on the commandline.-def f(x: List[str]) -> None:
+def f(x: list[str]) -> None:
...
Availability:
from __future__ import annotations
--keep-runtime-typing
is passed on the commandline.--py310-plus
is passed on the commandline.-def f() -> Optional[str]:
+def f() -> str | None:
...
-def f() -> Union[int, str]:
+def f() -> int | str:
...
Availability:
from __future__ import annotations
--keep-runtime-typing
is passed on the commandline.--py313-plus
is passed on the commandline.-def f() -> Generator[int, None, None]:
+def f() -> Generator[int]:
yield 1
-async def f() -> AsyncGenerator[int, None]:
+async def f() -> AsyncGenerator[int]:
yield 1
Availability:
from __future__ import annotations
-def f(x: 'queue.Queue[int]') -> C:
+def f(x: queue.Queue[int]) -> C:
datetime.UTC
aliasAvailability:
--py311-plus
is passed on the commandline. import datetime
-datetime.timezone.utc
+datetime.UTC
FAQs
A tool to automatically upgrade syntax for newer versions.
We found that pyupgrade demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A look at the top trends in how threat actors are weaponizing open source packages to deliver malware and persist across the software supply chain.
Security News
ESLint now supports HTML linting with 48 new rules, expanding its language plugin system to cover more of the modern web development stack.
Security News
CISA is discontinuing official RSS support for KEV and cybersecurity alerts, shifting updates to email and social media, disrupting automation workflows.