Skip to content

Commit a7c7786

Browse files
authored
Migrate Common Linting Tooling to Rust-Based Packages (#38107)
* Swap out pylint and flake8 with ruff * time the mypy run * consolidate mypy checks into linting * Migrate pre-commit hook, pin versions in setup.py * fix new breakages * lint/format adjustments * review comments, fix SKILL configs
1 parent e71ba54 commit a7c7786

10 files changed

Lines changed: 131 additions & 44 deletions

File tree

.agent/skills/python-development/SKILL.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ description: Guides Python SDK development in Apache Beam, including environment
4141
- `pyproject.toml` - Build configuration
4242
- `tox.ini` - Test automation
4343
- `pytest.ini` - Pytest configuration
44-
- `.pylintrc` - Linting rules
44+
- `ruff.toml` - Linting rules
4545
- `.isort.cfg` - Import sorting
46-
- `mypy.ini` - Type checking
46+
- `pyrefly.toml` - Type checking
4747

4848
## Environment Setup
4949

@@ -176,10 +176,10 @@ Use `--requirements_file=requirements.txt` or custom containers.
176176
## Code Quality Tools
177177
```bash
178178
# Linting
179-
pylint apache_beam/
179+
ruff check apache_beam/
180180

181181
# Type checking
182-
mypy apache_beam/
182+
pyrefly check apache_beam/
183183

184184
# Formatting (via yapf)
185185
yapf -i apache_beam/file.py

.pre-commit-config.yaml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ repos:
3131
sdks/python/apache_beam/portability/api/.*pb2.*.py
3232
)$
3333

34-
- repo: https://github.com/pycqa/pylint
35-
# this rev is a release tag in the repo above and corresponds with a pylint
36-
# version. make sure this matches the version of pylint in tox.ini.
37-
rev: v4.0.2
34+
- repo: https://github.com/astral-sh/ruff-pre-commit
35+
# this rev is a release tag in the repo above and corresponds with a ruff
36+
# version. make sure this matches the version of ruff in setup.py
37+
rev: v0.15.7
3838
hooks:
39-
- id: pylint
40-
args: ["--rcfile=sdks/python/.pylintrc"]
39+
- id: ruff-check
4140
files: ^sdks/python/apache_beam/
42-
exclude: *exclude
41+
args: ["--config=sdks/python/ruff.toml"]

sdks/python/apache_beam/io/gcp/bigquery_change_history_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
def _ts(*args, **kwargs) -> Timestamp:
4545
"""Create a UTC datetime and return a Beam Timestamp."""
46+
# pyrefly: ignore[bad-keyword-argument]
4647
dt = datetime.datetime(*args, tzinfo=datetime.timezone.utc, **kwargs)
4748
return Timestamp(dt.timestamp())
4849

sdks/python/apache_beam/ml/inference/agent_development_kit.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ def run_inference(
237237

238238
# Wrap plain strings in a Content object
239239
if isinstance(element, str):
240+
# pyrefly: ignore[bad-instantiation]
240241
message = genai_Content(role="user", parts=[genai_Part(text=element)])
241242
else:
242243
# Assume the caller has already constructed a types.Content object

sdks/python/apache_beam/typehints/typehints_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,8 @@ def test_type_checks_not_dict(self):
712712

713713
def test_type_check_collection(self):
714714
hint = typehints.Dict[str, int]
715-
l = collections.defaultdict(list[("blue", 2)])
715+
element = ("blue", 2)
716+
l = collections.defaultdict(list[element])
716717
self.assertIsNone(hint.type_check(l))
717718

718719
def test_type_check_invalid_key_type(self):

sdks/python/ruff.toml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
exclude = [
19+
".bzr",
20+
".direnv",
21+
".eggs",
22+
".git",
23+
".git-rewrite",
24+
".hg",
25+
".ipynb_checkpoints",
26+
".mypy_cache",
27+
".nox",
28+
".pants.d",
29+
".pyenv",
30+
".pytest_cache",
31+
".pytype",
32+
".ruff_cache",
33+
".svn",
34+
".tox",
35+
".venv",
36+
".vscode",
37+
"__pypackages__",
38+
"_build",
39+
"buck-out",
40+
"build",
41+
"dist",
42+
"node_modules",
43+
"site-packages",
44+
"venv",
45+
"*.pxd",
46+
"*.pyx",
47+
"*pb2*.py",
48+
"**/examples/**/*.py",
49+
"**/examples/**/*.ipynb",
50+
"**/portability/api/**/*.py",
51+
"**/portability/api/__init__.py",
52+
]
53+
54+
target-version = "py310"
55+
56+
src = ["apache_beam"]
57+
58+
[lint]
59+
select = ["E9", "PL", "F821", "F822", "F823"]
60+
ignore = [
61+
# Ignored Pylint Checks
62+
"PLC0415", # import-outside-toplevel
63+
"PLR2004", # magic-value-comparison
64+
"PLR0913", # too-many-arguments
65+
"PLR0912", # too-many-branches
66+
"PLW0108", # unnecessary-lambda
67+
"PLW2901", # redefined-loop-name
68+
"PLR0915", # too-many-statements
69+
"PLR1714", # repeated-equality-comparison
70+
"PLR0911", # too-many-return-statements
71+
"PLR5501", # collapsible-else-if
72+
"PLW0603", # global-statement
73+
"PLR1730", # if-stmt-min-max
74+
"PLW1641", # eq-without-hash
75+
"PLW0602", # global-variable-not-assigned
76+
"PLC1802", # len-test
77+
"PLC3002", # unnecessary-direct-lambda-call
78+
"PLW0642", # self-or-cls-assignment
79+
"PLR1733", # unnecessary-dict-index-lookup
80+
"PLR0402", # manual-from-import
81+
"PLC0132", # type-param-name-mismatch
82+
"PLC0206", # dict-index-missing-items
83+
"PLC0207", # missing-maxsplit-arg
84+
"PLR1704", # redefined-argument-from-local
85+
"PLR1711", # useless-return
86+
"PLW0406", # import-self
87+
"PLW3301", # nested-min-max
88+
"PLR2044", # empty-comment
89+
]
90+
91+
# Allow fix for all enabled rules (when `--fix`) is provided.
92+
fixable = ["ALL"]
93+
unfixable = []
94+
95+
# Allow unused variables when underscore-prefixed.
96+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

sdks/python/scripts/run_pylint.sh

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,8 @@ done
7878
echo -e "Skipping lint for files:\n${FILES_TO_IGNORE}"
7979
echo -e "Linting modules:\n${MODULE}"
8080

81-
echo "Running pylint..."
82-
pylint -j8 ${MODULE} --ignore-patterns="$FILES_TO_IGNORE"
83-
echo "Running flake8..."
84-
flake8 ${MODULE} --count --select=E9,F821,F822,F823 --show-source --statistics \
85-
--exclude="${FILES_TO_IGNORE}"
81+
echo "Running ruff..."
82+
ruff check ${MODULE} --extend-exclude="$FILES_TO_IGNORE"
8683

8784
echo "Running isort..."
8885
# Skip files where isort is behaving weirdly

sdks/python/setup.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def normalize_path(filename):
4949
return os.path.normcase(os.path.realpath(os.path.normpath(filename)))
5050

5151

52-
class mypy(Command):
52+
class pyrefly(Command):
5353
user_options = []
5454

5555
def initialize_options(self):
@@ -71,10 +71,10 @@ def get_project_path(self):
7171
return os.path.join(project_path, to_filename(ei_cmd.egg_name))
7272

7373
def run(self):
74-
args = ['mypy', self.get_project_path()]
74+
args = ['pyrefly', 'check', self.get_project_path()]
7575
result = subprocess.call(args)
7676
if result != 0:
77-
raise DistutilsError("mypy exited with status %d" % result)
77+
raise DistutilsError("pyrefly exited with status %d" % result)
7878

7979

8080
def get_version():
@@ -446,6 +446,12 @@ def get_portability_package_data():
446446
python_requires=python_requires,
447447
# BEAM-8840: Do NOT use tests_require or setup_requires.
448448
extras_require={
449+
'dev': [
450+
'isort==7.0.0',
451+
'pyrefly==0.54.0',
452+
'ruff==0.15.7',
453+
'yapf==0.43.0',
454+
],
449455
'dill': [
450456
# Dill doesn't have forwards-compatibility guarantees within minor
451457
# version. Pickles created with a new version of dill may not
@@ -683,6 +689,6 @@ def get_portability_package_data():
683689
license='Apache License, Version 2.0',
684690
keywords=PACKAGE_KEYWORDS,
685691
cmdclass={
686-
'mypy': mypy,
692+
'pyrefly': pyrefly,
687693
},
688694
)

sdks/python/test-suites/tox/pycommon/build.gradle

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,3 @@ check.dependsOn formatter
4040

4141
toxTask "lint", "lint", "${posargs}"
4242
linter.dependsOn lint
43-
44-
toxTask "mypy", "mypy", "${posargs}"
45-
linter.dependsOn mypy

sdks/python/tox.ini

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
[tox]
1919
# new environments will be excluded by default unless explicitly added to envlist.
20-
envlist = py310,py311,py312,py313,py314,py310-{cloud,cloudcoverage,dask},py311-{cloud,dask},py312-{cloud,dask},py313-{cloud,dask},py314-{cloud,dask},docs,lint,mypy,whitespacelint
20+
envlist = py310,py311,py312,py313,py314,py310-{cloud,cloudcoverage,dask},py311-{cloud,dask},py312-{cloud,dask},py313-{cloud,dask},py314-{cloud,dask},docs,lint,whitespacelint
2121
toxworkdir = {toxinidir}/target/{env:ENV_NAME:.tox}
2222

2323
[pycodestyle]
@@ -190,16 +190,17 @@ commands =
190190
[testenv:lint]
191191
# Don't set TMPDIR to avoid "AF_UNIX path too long" errors in pylint.
192192
setenv =
193-
# keep the version of pylint in sync with the 'rev' in .pre-commit-config.yaml
194193
deps =
195-
astroid<4.1.0,>=4.0.1
196-
pycodestyle==2.8.0
197-
pylint==4.0.2
198-
isort==7.0.0
199-
flake8==4.0.1
194+
dask==2022.01.0
195+
distributed==2022.01.0
196+
extras =
197+
gcp
198+
dev
200199
commands =
201-
pylint --version
200+
ruff --version
202201
time {toxinidir}/scripts/run_pylint.sh
202+
pyrefly --version
203+
time python setup.py pyrefly
203204

204205
[testenv:whitespacelint]
205206
setenv =
@@ -208,18 +209,6 @@ deps =
208209
commands =
209210
time {toxinidir}/scripts/run_whitespacelint.sh
210211

211-
[testenv:mypy]
212-
deps =
213-
mypy==1.13.0
214-
dask==2022.01.0
215-
distributed==2022.01.0
216-
# make extras available in case any of these libs are typed
217-
extras =
218-
gcp
219-
commands =
220-
mypy --version
221-
python setup.py mypy
222-
223212

224213
[testenv:docs]
225214
extras = test,gcp,docs,interactive,dataframe,dask

0 commit comments

Comments
 (0)