Skip to content

Commit ffb37c2

Browse files
smackeseyDagster Devtools
authored andcommitted
chore(ruff): bump to 0.15.14 and enable PERF401 (#25026)
## Summary & Motivation Bumps ruff `0.15.13 → 0.15.14` and enables `PERF401` (manual-list-comprehension), which has been a long-standing temporary-disable in `dagster-oss/config/ruff.toml`. Ruff 0.15.14 ships an auto-fix for `PERF401` in **preview** mode. I ran `ruff check --select PERF401 --preview --unsafe-fixes --fix . && ruff format .`, which fixed all 241 violations across the repo (net **-119 lines**). The rule is now enabled in the default config (no preview required at check time — preview was only needed to apply the bulk fix). The temporary-disable comment for `PERF401` is removed; `PERF402` stays in the ignore list (still no auto-fix even in preview). ## Scope - `dagster-oss/config/ruff.toml`: `required-version` bump, `PERF401` removed from temporary-disables. - Ruff pin bumps in 3 other `pyproject.toml`s (`pyproject.toml`, `python_modules/purina/pyproject.toml`, `public/skills/dagster-skills-evals/pyproject.toml`, `dagster-oss/python_modules/dagster/pyproject.toml`). - `uv.lock` (internal) and `dagster-oss/python_modules/dagster/uv.lock` relocked. The internal relock also pulled `ty` from `0.0.38 → 0.0.39` as an incidental dependency update. - ~170 `.py` files: mechanical ruff auto-fix output. Every change is one of: a `for…append` loop collapsed into a list comprehension, or `extend(generator)`. ## Side effects the auto-fix exposed The PERF401 transformation removed `xxx = []` initializers and `for` bodies, which surfaced 4 latent lint errors: - **3 × `D202`** (no blank line after docstring): the auto-fix left a blank line between a docstring and the next statement. Auto-fixed in a second `ruff check --fix` pass. - **1 × `F841` (`specs` unused)** in `dagster-oss/python_modules/libraries/dagster-shared/dagster_shared_tests/test_record_jit.py`: the test deliberately constructs `Spec(...)` instances inside a loop to exercise `@record` JIT-collision behavior; `specs` itself was never read. The PERF401 fix preserved runtime behavior (the comprehension still constructs each `Spec`) but exposed the latent unused-variable warning. Added `assert len(specs) == len(checks)` to make the variable load-bearing while keeping the original test semantics. ## Verification - `just ruff` — clean (check + format). - `uvx ruff@0.15.14 check --select PERF401,PERF402 .` — only the 10 known `PERF402` violations remain (unchanged). Internal-RevId: 04c6647013db97b4e51e40d56a42e7152a1a9cf2
1 parent e243b10 commit ffb37c2

118 files changed

Lines changed: 831 additions & 930 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.buildkite/buildkite-shared/lints.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ def test_tox_passenv(tox_config: configparser.ConfigParser) -> None:
3535
"PYTEST_PLUGINS",
3636
]
3737

38-
missing_env = []
39-
for env in PASSENV_ENV:
40-
if env not in tox_config["testenv"]["passenv"].split("\n"):
41-
missing_env.append(env)
38+
missing_env = [
39+
env for env in PASSENV_ENV if env not in tox_config["testenv"]["passenv"].split("\n")
40+
]
4241

4342
assert not missing_env, f"tox.ini missing passenv {missing_env}"
4443

.buildkite/dagster-buildkite/dagster_buildkite/steps/test_project.py

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,56 +34,54 @@ def build_test_project_steps() -> list[GroupStepConfiguration]:
3434
"""This set of tasks builds and pushes Docker images, which are used by the dagster-airflow and
3535
the dagster-k8s tests.
3636
"""
37-
steps: list[GroupLeafStepConfiguration] = []
38-
3937
# Build for all available versions because a dependent extension might need to run tests on any version.
4038
py_versions = AvailablePythonVersion.get_all()
4139

42-
for version in py_versions:
43-
steps.append(
44-
CommandStepBuilder(_test_project_step_key(version), [":docker:"])
45-
# these run commands are coupled to the way the buildkite-build-test-project-image is built
46-
# see python_modules/automation/automation/docker/images/buildkite-build-test-project-image
47-
.run(
48-
# credentials
49-
"/scriptdir/aws.pex ecr get-login --no-include-email --region us-west-2 | sh",
50-
f'export GOOGLE_APPLICATION_CREDENTIALS="{GCP_CREDS_LOCAL_FILE}"',
51-
"/scriptdir/aws.pex s3 cp"
52-
f" s3://$${{BUILDKITE_SECRETS_BUCKET}}/{GCP_CREDS_FILENAME}"
53-
" $${GOOGLE_APPLICATION_CREDENTIALS}",
54-
"export"
55-
" BASE_IMAGE=$${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/test-project-base:py"
56-
+ version.value
57-
+ "-"
58-
+ TEST_PROJECT_BASE_IMAGE_VERSION,
59-
# build and tag test image
60-
"export"
61-
" TEST_PROJECT_IMAGE=$${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/test-project:$${BUILDKITE_BUILD_ID}-"
62-
+ version.value,
63-
"git config --global --add safe.directory /workdir",
64-
f"./{oss_path('python_modules/dagster-test/dagster_test/test_project/build.sh')} "
65-
+ version.value
66-
+ " $${TEST_PROJECT_IMAGE}",
67-
#
68-
# push the built image
69-
'echo -e "--- \033[32m:docker: Pushing Docker image\033[0m"',
70-
"docker push $${TEST_PROJECT_IMAGE}",
71-
)
72-
.skip(skip_if_version_not_needed(version))
73-
.on_python_image(
74-
image=f"buildkite-build-test-project-image:py{AvailablePythonVersion.V3_11.value}-{BUILDKITE_BUILD_TEST_PROJECT_IMAGE_IMAGE_VERSION}",
75-
env=[
76-
"AIRFLOW_HOME",
77-
"AWS_ACCOUNT_ID",
78-
"AWS_ACCESS_KEY_ID",
79-
"AWS_SECRET_ACCESS_KEY",
80-
"BUILDKITE_SECRETS_BUCKET",
81-
],
82-
)
83-
.with_ecr_login()
84-
.with_docker() # build.sh runs `docker build`; final step `docker push`
85-
.build()
40+
steps: list[GroupLeafStepConfiguration] = [
41+
CommandStepBuilder(_test_project_step_key(version), [":docker:"])
42+
# these run commands are coupled to the way the buildkite-build-test-project-image is built
43+
# see python_modules/automation/automation/docker/images/buildkite-build-test-project-image
44+
.run(
45+
# credentials
46+
"/scriptdir/aws.pex ecr get-login --no-include-email --region us-west-2 | sh",
47+
f'export GOOGLE_APPLICATION_CREDENTIALS="{GCP_CREDS_LOCAL_FILE}"',
48+
"/scriptdir/aws.pex s3 cp"
49+
f" s3://$${{BUILDKITE_SECRETS_BUCKET}}/{GCP_CREDS_FILENAME}"
50+
" $${GOOGLE_APPLICATION_CREDENTIALS}",
51+
"export"
52+
" BASE_IMAGE=$${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/test-project-base:py"
53+
+ version.value
54+
+ "-"
55+
+ TEST_PROJECT_BASE_IMAGE_VERSION,
56+
# build and tag test image
57+
"export"
58+
" TEST_PROJECT_IMAGE=$${AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/test-project:$${BUILDKITE_BUILD_ID}-"
59+
+ version.value,
60+
"git config --global --add safe.directory /workdir",
61+
f"./{oss_path('python_modules/dagster-test/dagster_test/test_project/build.sh')} "
62+
+ version.value
63+
+ " $${TEST_PROJECT_IMAGE}",
64+
#
65+
# push the built image
66+
'echo -e "--- \033[32m:docker: Pushing Docker image\033[0m"',
67+
"docker push $${TEST_PROJECT_IMAGE}",
68+
)
69+
.skip(skip_if_version_not_needed(version))
70+
.on_python_image(
71+
image=f"buildkite-build-test-project-image:py{AvailablePythonVersion.V3_11.value}-{BUILDKITE_BUILD_TEST_PROJECT_IMAGE_IMAGE_VERSION}",
72+
env=[
73+
"AIRFLOW_HOME",
74+
"AWS_ACCOUNT_ID",
75+
"AWS_ACCESS_KEY_ID",
76+
"AWS_SECRET_ACCESS_KEY",
77+
"BUILDKITE_SECRETS_BUCKET",
78+
],
8679
)
80+
.with_ecr_login()
81+
.with_docker() # build.sh runs `docker build`; final step `docker push`
82+
.build()
83+
for version in py_versions
84+
]
8785
return [
8886
GroupStepBuilder(
8987
"test-project-image",

config/ruff.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ target-version = "py310"
2626
line-length = 100
2727

2828
# Fail if Ruff is not running this version.
29-
required-version = "0.15.13"
29+
required-version = "0.15.14"
3030

3131
[lint]
3232

@@ -85,9 +85,8 @@ ignore = [
8585
"D205", # (blank line after summary)
8686
"D417", # (missing arg in docstring)
8787

88-
# (assorted perf rules) We have a lot of violations, enable when autofix is available
89-
"PERF401", # (manual-list-comprehension)
90-
"PERF402", # (manual-list-copy)
88+
# (manual-list-copy) No autofix available; enable once violations are fixed by hand.
89+
"PERF402",
9190

9291
]
9392

docs/scripts/rebuild-kinds-tags.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,15 @@ def main() -> None:
135135
docs_file_new_contents.append("|-----|-------|")
136136

137137
# Table content
138-
for kind in output:
139-
docs_file_new_contents.append(
140-
KIND_LINE.format(
141-
kind=kind,
142-
kind_spacing=" " * (20 - len(kind)),
143-
image=kind_docs_images.get(kind, ""),
144-
image_spacing=" " * (100 - len(kind_docs_images.get(kind, ""))),
145-
)
138+
docs_file_new_contents.extend(
139+
KIND_LINE.format(
140+
kind=kind,
141+
kind_spacing=" " * (20 - len(kind)),
142+
image=kind_docs_images.get(kind, ""),
143+
image_spacing=" " * (100 - len(kind_docs_images.get(kind, ""))),
146144
)
145+
for kind in output
146+
)
147147

148148
KIND_TAGS_DOCS_PARTIAL.write_text("\n".join(docs_file_new_contents))
149149

docs/sphinx/_ext/dagster-sphinx/dagster_sphinx/configurable.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ def config_field_to_lines(field, name=None) -> list[str]:
8181
if field.description:
8282
# trim / normalize whitespace. some of our config descriptions misuse triple-quote blocks
8383
# so this makes them look nicer
84-
for ln in field.description.split("\n"):
85-
# escape '*' characters because they get interpreted as emphasis markers in rst
86-
lines.append(" " * 4 + textwrap.dedent(ln.replace("*", "\\*")))
84+
# escape '*' characters because they get interpreted as emphasis markers in rst
85+
lines.extend(
86+
" " * 4 + textwrap.dedent(ln.replace("*", "\\*"))
87+
for ln in field.description.split("\n")
88+
)
8789
lines.append("")
8890

8991
if field.default_provided:
@@ -96,8 +98,7 @@ def config_field_to_lines(field, name=None) -> list[str]:
9698
lines.append("")
9799
lines.append(" .. code-block:: javascript")
98100
lines.append("")
99-
for ln in ls:
100-
lines.append(" " * 12 + ln)
101+
lines.extend(" " * 12 + ln for ln in ls)
101102
else:
102103
lines.append("")
103104
lines.append(f" **Default Value:** {val!r}")

examples/docs_projects/project_ask_ai_dagster/src/project_ask_ai_dagster/defs/github.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ def _fetch_results(self, query_str: str, object_type: str) -> list[dict]:
5858
result = client.execute(query)
5959
search = result["search"]
6060
edges = search["edges"]
61-
for node in edges:
62-
results.append(node["node"])
61+
results.extend(node["node"] for node in edges)
6362
log.info(f"Total results: {len(results)}")
6463
if not search["pageInfo"]["hasNextPage"]:
6564
break
@@ -191,9 +190,11 @@ def convert_issues_to_documents(self, items: list[dict[str, Any]]) -> list[Docum
191190

192191
# Add comments if present
193192
if "comments" in item and "nodes" in item["comments"]:
194-
for comment in item["comments"]["nodes"]:
195-
if comment and "body" in comment:
196-
content_parts.append(f"Comment: {comment['body']}")
193+
content_parts.extend(
194+
f"Comment: {comment['body']}"
195+
for comment in item["comments"]["nodes"]
196+
if comment and "body" in comment
197+
)
197198

198199
# Create document with properly formatted content
199200
doc = Document(page_content="\n\n".join(content_parts), metadata=metadata)

examples/docs_projects/project_ask_ai_dagster/src/project_ask_ai_dagster/defs/scraper.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ def scrape_page(self, url: str) -> Document | None:
3838
main_content = soup.find("main") or soup.find("article") or soup.body
3939

4040
if main_content:
41-
content = []
42-
for elem in main_content.stripped_strings:
43-
if elem.strip():
44-
content.append(elem.strip())
41+
content = [elem.strip() for elem in main_content.stripped_strings if elem.strip()]
4542
text_content = "\n".join(content)
4643
else:
4744
text_content = "\n".join(s.strip() for s in soup.stripped_strings if s.strip())

examples/docs_projects/project_llm_fine_tune/src/project_llm_fine_tune/defs/assets.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,10 @@ def training_file(
228228
enriched_graphic_novels,
229229
) -> str:
230230
graphic_novels = enriched_graphic_novels.sample(n=TRAINING_FILE_NUM)
231-
prompt_data = []
232-
for record in [row for _, row in graphic_novels.iterrows()]:
233-
prompt_data.append(create_prompt_record(record, CATEGORIES))
231+
prompt_data = [
232+
create_prompt_record(record, CATEGORIES)
233+
for record in [row for _, row in graphic_novels.iterrows()]
234+
]
234235

235236
file_name = "goodreads-training.jsonl"
236237
write_openai_file(file_name, prompt_data)
@@ -249,9 +250,10 @@ def validation_file(
249250
enriched_graphic_novels,
250251
) -> str:
251252
graphic_novels = enriched_graphic_novels.sample(n=VALIDATION_FILE_NUM)
252-
prompt_data = []
253-
for record in [row for _, row in graphic_novels.iterrows()]:
254-
prompt_data.append(create_prompt_record(record, CATEGORIES))
253+
prompt_data = [
254+
create_prompt_record(record, CATEGORIES)
255+
for record in [row for _, row in graphic_novels.iterrows()]
256+
]
255257

256258
file_name = "goodreads-validation.jsonl"
257259
write_openai_file(file_name, prompt_data)

examples/docs_projects/project_ml/src/project_ml/defs/resources.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ def list_models(self) -> list[str]:
111111
return []
112112

113113
# Get model files and their last modified times
114-
model_objects = []
115-
for obj in response["Contents"]:
116-
if obj["Key"].endswith(".pth"):
117-
model_objects.append({"key": obj["Key"], "last_modified": obj["LastModified"]})
114+
model_objects = [
115+
{"key": obj["Key"], "last_modified": obj["LastModified"]}
116+
for obj in response["Contents"]
117+
if obj["Key"].endswith(".pth")
118+
]
118119

119120
# Sort by modification time, newest first
120121
model_objects.sort(key=lambda x: x["last_modified"], reverse=True)

examples/docs_snippets/docs_snippets/guides/build/assets/data-assets/quality-testing/data-contracts/assets.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,16 @@ def validate_shipments_data_contract(
9090

9191
# Compare schemas
9292
mismatches = []
93-
missing_columns = []
94-
extra_columns = []
9593

9694
# Check for missing columns in actual schema
97-
for col_name in expected_schema:
98-
if col_name not in actual_schema:
99-
missing_columns.append(col_name)
95+
missing_columns = [
96+
col_name for col_name in expected_schema if col_name not in actual_schema
97+
]
10098

10199
# Check for extra columns in actual schema
102-
for col_name in actual_schema:
103-
if col_name not in expected_schema:
104-
extra_columns.append(col_name)
100+
extra_columns = [
101+
col_name for col_name in actual_schema if col_name not in expected_schema
102+
]
105103

106104
# Check for type mismatches
107105
for col_name in expected_schema:

0 commit comments

Comments
 (0)