From eb9d0396cd51065c975e366f06dfea60221a2d03 Mon Sep 17 00:00:00 2001 From: Oliver Margetts Date: Sun, 14 Nov 2021 03:46:15 +0000 Subject: [PATCH] Allow install under pypy (#2559) Co-authored-by: Jelle Zijlstra Co-authored-by: Richard Si <63936253+ichard26@users.noreply.github.com> --- .github/workflows/test.yml | 8 +++++++- CHANGES.md | 1 + docs/faq.md | 5 +++++ setup.py | 2 +- src/black/cache.py | 2 +- src/black/parsing.py | 25 +++++++++++++++++-------- tests/test_black.py | 2 +- tox.ini | 27 ++++++++++++++++++++++++++- 8 files changed, 59 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 296ac34..7ba2a84 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: 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: @@ -41,9 +41,15 @@ jobs: 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) diff --git a/CHANGES.md b/CHANGES.md index b2e8f74..c8b9c84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ - 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) diff --git a/docs/faq.md b/docs/faq.md index 77f9df5..72bae6b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -92,3 +92,8 @@ influence their behavior. While Black does its best to recognize such comments a 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. diff --git a/setup.py b/setup.py index de84dc3..a0c2006 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,7 @@ setup( "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'", diff --git a/src/black/cache.py b/src/black/cache.py index 3f165de..bca7279 100644 --- a/src/black/cache.py +++ b/src/black/cache.py @@ -35,7 +35,7 @@ def read_cache(mode: Mode) -> Cache: 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 diff --git a/src/black/parsing.py b/src/black/parsing.py index fc540ad..ee6aae1 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -2,6 +2,7 @@ Parse Python code and perform AST validation. """ import ast +import platform import sys from typing import Iterable, Iterator, List, Set, Union, Tuple @@ -15,10 +16,13 @@ from blib2to3.pgen2.parse import ParseError 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" @@ -117,7 +121,10 @@ def parse_single_version( 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!") @@ -151,12 +158,14 @@ def stringify_ast( 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) diff --git a/tests/test_black.py b/tests/test_black.py index 7dbc380..301a3a5 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -944,7 +944,7 @@ class BlackTestCase(BlackBaseTestCase): 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())]) diff --git a/tox.ini b/tox.ini index 57f41ac..683a543 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [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 @@ -31,6 +31,31 @@ commands = --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 = -- 2.39.2