From 79e092e13305cb0e4acc019f198d05422cdbdc36 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 20 Jul 2023 13:41:52 +0100 Subject: [PATCH] Add some ruff autofixes to CI (#10458) --- .pre-commit-config.yaml | 5 +++++ CONTRIBUTING.md | 7 +++--- pyproject.toml | 38 ++++++++++++++++++++++++++++++++ requirements-tests.txt | 1 + scripts/create_baseline_stubs.py | 8 ++++++- scripts/runtests.py | 2 ++ stdlib/typing_extensions.pyi | 2 +- tests/check_consistent.py | 5 ++++- 8 files changed, 62 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c03eec00f65a..9c451ffe34af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,6 +26,11 @@ repos: hooks: - id: isort name: isort (python) + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.278 # must match requirements-tests.txt + hooks: + - id: ruff + args: [--exit-non-zero-on-fix] - repo: https://github.com/pycqa/flake8 rev: 6.0.0 # must match requirements-tests.txt hooks: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a185c385fd0d..1b3e15cf4905 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ it takes a bit longer. For more details, read below. Typeshed runs continuous integration (CI) on all pull requests. This means that if you file a pull request (PR), our full test suite -- including our linter, `flake8` -- is run on your PR. It also means that bots will automatically apply -changes to your PR (using `pycln`, `black` and `isort`) to fix any formatting issues. +changes to your PR (using `pycln`, `black`, `isort` and `ruff`) to fix any formatting issues. This frees you up to ignore all local setup on your side, focus on the code and rely on the CI to fix everything, or point you to the places that need fixing. @@ -85,7 +85,7 @@ terminal to install all non-pytype requirements: ## Code formatting The code is formatted using `black` and `isort`. Unused imports are also -auto-removed using `pycln`. +auto-removed using `pycln`, and various other autofixes are performed by `ruff`. The repository is equipped with a [`pre-commit.ci`](https://pre-commit.ci/) configuration file. This means that you don't *need* to do anything yourself to @@ -93,11 +93,12 @@ run the code formatters. When you push a commit, a bot will run those for you right away and add a commit to your PR. That being said, if you *want* to run the checks locally when you commit, -you're free to do so. Either run `pycln`, `black` and `isort` manually... +you're free to do so. Either run `pycln`, `isort`, `black` and `ruff` manually... ```bash $ pycln --config=pyproject.toml . $ isort . +$ ruff . $ black . ``` diff --git a/pyproject.toml b/pyproject.toml index c83915b5b0b2..41d304e2f18a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,44 @@ extra_standard_library = [ ] known_first_party = ["utils", "parse_metadata"] +[tool.ruff] +line-length = 130 +target-version = "py37" +fix = true +exclude = [ + # We're only interested in autofixes for our stubs + "*.py", + # Ignore generated protobuf stubs + "*_pb2.pyi", + # virtual environment, cache directories, etc.: + "env", + ".env", + ".venv", + ".git", + ".mypy_cache", + ".pytype", +] + +# Only enable rules that have safe autofixes; +# only enable rules that are relevant to stubs +select = [ + "UP004", # Remove explicit `object` inheritance + "UP006", # PEP-585 autofixes + "UP007", # PEP-604 autofixes + "UP013", # Class-based syntax for TypedDicts + "UP014", # Class-based syntax for NamedTuples + "UP019", # Use str over typing.Text + "UP035", # import from typing, not typing_extensions, wherever possible + "UP039", # don't use parens after a class definition with no bases + "PYI009", # use `...`, not `pass`, in empty class bodies + "PYI010", # function bodies must be empty + "PYI012", # class bodies must not contain `pass` + "PYI013", # non-empty class bodies must not contain `...` + "PYI020", # quoted annotations are always unnecessary in stubs + "PYI025", # always alias `collections.abc.Set` as `AbstractSet` when importing it + "PYI032", # use `object`, not `Any`, as the second parameter to `__eq__` +] + [tool.pycln] all = true disable_all_dunder_policy = true diff --git a/requirements-tests.txt b/requirements-tests.txt index 5a53fbb55883..13f6313d3f86 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -11,6 +11,7 @@ mypy==1.4.1 pre-commit-hooks==4.4.0 # must match .pre-commit-config.yaml pycln==2.1.5 # must match .pre-commit-config.yaml pytype==2023.6.16; platform_system != "Windows" and python_version < "3.11" +ruff==0.0.278 # must match .pre-commit-config.yaml # Libraries used by our various scripts. aiohttp==3.8.4; python_version < "3.12" # aiohttp can't be installed on 3.12 yet diff --git a/scripts/create_baseline_stubs.py b/scripts/create_baseline_stubs.py index 4675ee58bce0..7d41fad37b60 100755 --- a/scripts/create_baseline_stubs.py +++ b/scripts/create_baseline_stubs.py @@ -65,6 +65,11 @@ def run_isort(stub_dir: str) -> None: subprocess.run([sys.executable, "-m", "isort", stub_dir]) +def run_ruff(stub_dir: str) -> None: + print(f"Running ruff: ruff {stub_dir}") + subprocess.run([sys.executable, "-m", "ruff", stub_dir]) + + def create_metadata(stub_dir: str, version: str) -> None: """Create a METADATA.toml file.""" match = re.match(r"[0-9]+.[0-9]+", version) @@ -110,7 +115,7 @@ def add_pyright_exclusion(stub_dir: str) -> None: def main() -> None: parser = argparse.ArgumentParser( description="""Generate baseline stubs automatically for an installed pip package - using stubgen. Also run black and isort. If the name of + using stubgen. Also run black, isort and ruff. If the name of the project is different from the runtime Python package name, you may need to use --package (example: --package yaml PyYAML).""" ) @@ -159,6 +164,7 @@ def main() -> None: run_stubgen(package, stub_dir) run_stubdefaulter(stub_dir) + run_ruff(stub_dir) run_isort(stub_dir) run_black(stub_dir) diff --git a/scripts/runtests.py b/scripts/runtests.py index 8c6efd71cb0f..001e4eff7e5c 100644 --- a/scripts/runtests.py +++ b/scripts/runtests.py @@ -77,6 +77,8 @@ def main() -> None: # Run formatters first. Order matters. print("\nRunning pycln...") subprocess.run([sys.executable, "-m", "pycln", path, "--config=pyproject.toml"]) + print("\nRunning ruff...") + subprocess.run([sys.executable, "-m", "ruff", path]) print("\nRunning isort...") subprocess.run([sys.executable, "-m", "isort", path]) print("\nRunning Black...") diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index 93087a45a108..aa55d29b1b4d 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -113,7 +113,7 @@ __all__ = [ _T = typing.TypeVar("_T") _F = typing.TypeVar("_F", bound=Callable[..., Any]) -_TC = typing.TypeVar("_TC", bound=Type[object]) +_TC = typing.TypeVar("_TC", bound=type[object]) # unfortunately we have to duplicate this class definition from typing.pyi or we break pytype class _SpecialForm: diff --git a/tests/check_consistent.py b/tests/check_consistent.py index 6b62d2869150..9eb8ffb26f12 100755 --- a/tests/check_consistent.py +++ b/tests/check_consistent.py @@ -23,7 +23,7 @@ # These type checkers and linters must have exact versions in the requirements file to ensure # consistent CI runs. -linters = {"black", "flake8", "flake8-bugbear", "flake8-noqa", "flake8-pyi", "isort", "mypy", "pycln", "pytype"} +linters = {"black", "flake8", "flake8-bugbear", "flake8-noqa", "flake8-pyi", "isort", "ruff", "mypy", "pycln", "pytype"} def assert_consistent_filetypes( @@ -191,6 +191,9 @@ def check_precommit_requirements() -> None: precommit_requirements = get_precommit_requirements() no_txt_entry_msg = "All pre-commit requirements must also be listed in `requirements-tests.txt` (missing {requirement!r})" for requirement, specifier in precommit_requirements.items(): + # annoying: the ruff repo for pre-commit is different to the name in requirements-tests.txt + if requirement == "ruff-pre-commit": + requirement = "ruff" assert requirement in requirements_txt_requirements, no_txt_entry_msg.format(requirement=requirement) specifier_mismatch = ( f'Specifier "{specifier}" for {requirement!r} in `.pre-commit-config.yaml` '