strategy:
fail-fast: false
matrix:
- python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.7"]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
python -m pip install --upgrade tox
- name: Unit tests
+ if: "!startsWith(matrix.python-version, 'pypy')"
run: |
tox -e ci-py -- -v --color=yes
+ - name: Unit tests pypy
+ if: "startsWith(matrix.python-version, 'pypy')"
+ run: |
+ tox -e ci-pypy3 -- -v --color=yes
+
- name: Publish coverage to Coveralls
# If pushed / is a pull request against main repo AND
# we're running on Linux (this action only supports Linux)
- Warn about Python 2 deprecation in more cases by improving Python 2 only syntax
detection (#2592)
+- Add experimental PyPy support (#2559)
- Add partial support for the match statement. As it's experimental, it's only enabled
when `--target-version py310` is explicitly specified (#2586)
- Add support for parenthesized with (#2586)
them in the right place, this detection is not and cannot be perfect. Therefore, you'll
sometimes have to manually move these comments to the right place after you format your
codebase with _Black_.
+
+## Can I run black with PyPy?
+
+Yes, there is support for PyPy 3.7 and higher. You cannot format Python 2 files under
+PyPy, because PyPy's inbuilt ast module does not support this.
"click>=7.1.2",
"platformdirs>=2",
"tomli>=0.2.6,<2.0.0",
- "typed-ast>=1.4.2; python_version < '3.8'",
+ "typed-ast>=1.4.2; python_version < '3.8' and implementation_name == 'cpython'",
"regex>=2020.1.8",
"pathspec>=0.9.0, <1",
"dataclasses>=0.6; python_version < '3.7'",
with cache_file.open("rb") as fobj:
try:
cache: Cache = pickle.load(fobj)
- except (pickle.UnpicklingError, ValueError):
+ except (pickle.UnpicklingError, ValueError, IndexError):
return {}
return cache
Parse Python code and perform AST validation.
"""
import ast
+import platform
import sys
from typing import Iterable, Iterator, List, Set, Union, Tuple
from black.mode import TargetVersion, Feature, supports_feature
from black.nodes import syms
+_IS_PYPY = platform.python_implementation() == "PyPy"
+
try:
from typed_ast import ast3, ast27
except ImportError:
- if sys.version_info < (3, 8):
+ # Either our python version is too low, or we're on pypy
+ if sys.version_info < (3, 7) or (sys.version_info < (3, 8) and not _IS_PYPY):
print(
"The typed_ast package is required but not installed.\n"
"You can upgrade to Python 3.8+ or install typed_ast with\n"
if sys.version_info >= (3, 8) and version >= (3,):
return ast.parse(src, filename, feature_version=version)
elif version >= (3,):
- return ast3.parse(src, filename, feature_version=version[1])
+ if _IS_PYPY:
+ return ast3.parse(src, filename)
+ else:
+ return ast3.parse(src, filename, feature_version=version[1])
elif version == (2, 7):
return ast27.parse(src)
raise AssertionError("INTERNAL ERROR: Tried parsing unsupported Python version!")
yield f"{' ' * depth}{node.__class__.__name__}("
for field in sorted(node._fields): # noqa: F402
- # TypeIgnore has only one field 'lineno' which breaks this comparison
- type_ignore_classes = (ast3.TypeIgnore, ast27.TypeIgnore)
- if sys.version_info >= (3, 8):
- type_ignore_classes += (ast.TypeIgnore,)
- if isinstance(node, type_ignore_classes):
- break
+ # TypeIgnore will not be present using pypy < 3.8, so need for this
+ if not (_IS_PYPY and sys.version_info < (3, 8)):
+ # TypeIgnore has only one field 'lineno' which breaks this comparison
+ type_ignore_classes = (ast3.TypeIgnore, ast27.TypeIgnore)
+ if sys.version_info >= (3, 8):
+ type_ignore_classes += (ast.TypeIgnore,)
+ if isinstance(node, type_ignore_classes):
+ break
try:
value = getattr(node, field)
symlink = workspace / "broken_link.py"
try:
symlink.symlink_to("nonexistent.py")
- except OSError as e:
+ except (OSError, NotImplementedError) as e:
self.skipTest(f"Can't create symlinks: {e}")
self.invokeBlack([str(workspace.resolve())])
[tox]
-envlist = {,ci-}py{36,37,38,39,310},fuzz
+envlist = {,ci-}py{36,37,38,39,310,py3},fuzz
[testenv]
setenv = PYTHONPATH = {toxinidir}/src
--cov --cov-append {posargs}
coverage report
+[testenv:{,ci-}pypy3]
+setenv = PYTHONPATH = {toxinidir}/src
+skip_install = True
+recreate = True
+deps =
+ -r{toxinidir}/test_requirements.txt
+; a separate worker is required in ci due to https://foss.heptapod.net/pypy/pypy/-/issues/3317
+; this seems to cause tox to wait forever
+; remove this when pypy releases the bugfix
+commands =
+ pip install -e .[d]
+ coverage erase
+ pytest tests --run-optional no_python2 \
+ --run-optional no_jupyter \
+ !ci: --numprocesses auto \
+ ci: --numprocesses 1 \
+ --cov {posargs}
+ pip install -e .[jupyter]
+ pytest tests --run-optional jupyter \
+ -m jupyter \
+ !ci: --numprocesses auto \
+ ci: --numprocesses 1 \
+ --cov --cov-append {posargs}
+ coverage report
+
[testenv:fuzz]
skip_install = True
deps =