Allow flake8-type-checking rules to automatically quote runtime-evaluated references#6001
Allow flake8-type-checking rules to automatically quote runtime-evaluated references#6001charliermarsh merged 6 commits intomainfrom
flake8-type-checking rules to automatically quote runtime-evaluated references#6001Conversation
270814f to
b760b41
Compare
PR Check ResultsEcosystem✅ ecosystem check detected no changes. |
41a82c7 to
33ee9b7
Compare
|
This "works" on Dagster in that it generates 1834 fixes (and no syntax errors) that generally look right: --- a/examples/assets_smoke_test/assets_smoke_test/pure_python_assets.py
+++ b/examples/assets_smoke_test/assets_smoke_test/pure_python_assets.py
@@ -1,5 +1,9 @@
+from typing import TYPE_CHECKING
+
from dagster import SourceAsset, TableSchema, asset
-from pandas import DataFrame
+
+if TYPE_CHECKING:
+ from pandas import DataFrame
raw_country_populations = SourceAsset(
"raw_country_populations",
@@ -19,7 +23,7 @@
@asset
-def country_populations(raw_country_populations) -> DataFrame:
+def country_populations(raw_country_populations) -> "DataFrame":
country_populations = raw_country_populations.copy()
country_populations["change"] = (
country_populations["change"]
@@ -32,13 +36,13 @@ def country_populations(raw_country_populations) -> DataFrame:
@asset
-def continent_stats(country_populations: DataFrame) -> DataFrame:
+def continent_stats(country_populations: "DataFrame") -> "DataFrame":
result = country_populations.groupby("continent").agg({"pop2019": "sum", "change": "mean"})
return result
@asset
-def country_stats(country_populations: DataFrame, continent_stats: DataFrame) -> DataFrame:
+def country_stats(country_populations: "DataFrame", continent_stats: "DataFrame") -> "DataFrame":
result = country_populations.join(continent_stats, on="continent", lsuffix="_continent")
result["continent_pop_fraction"] = result["pop2019"] / result["pop2019_continent"]
return result |
|
Yes! I was wondering yesterday if I misunderstood the purpose of the rule, because it was barely applying. When I realized I needed to add quotes, I briefly started manually adding them, but then realized it was unfeasible to do manually on even a medium-sized codebase. |
|
(If you use |
1976038 to
0c338cf
Compare
|
This was blocked as it became too difficult to quote an annotation given just the reference range. It should be unblocked now that we store all expressions in a vector, since we can store an ExpressionId on all references to go from reference to Expr. |
0c338cf to
a457f8b
Compare
a457f8b to
ee16edc
Compare
ee16edc to
6c4fd6b
Compare
|
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| RUF100 | 2 | 2 | 0 | 0 | 0 |
| TCH002 | 1 | 0 | 1 | 0 | 0 |
Linter (preview)
ℹ️ ecosystem check detected linter changes. (+2 -1 violations, +0 -0 fixes in 41 projects)
ibis-project/ibis (+2 -0 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --preview
+ ibis/common/tests/test_graph_benchmarks.py:6:37: RUF100 Unused `noqa` directive (unused: `TCH002`) + ibis/common/typing.py:33:46: RUF100 Unused `noqa` directive (unused: `TCH002`)
zulip/zulip (+0 -1 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --preview --select ALL
- analytics/views/stats.py:13:31: TCH002 Move third-party import `typing_extensions.TypeAlias` into a type-checking block
Changes by rule (2 rules affected)
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| RUF100 | 2 | 2 | 0 | 0 | 0 |
| TCH002 | 1 | 0 | 1 | 0 | 0 |
57915b4 to
77301d4
Compare
|
The diff of this on Dagster actually looks really good. |
|
For example: @@ -25,7 +25,6 @@
from dagster._core.events import DagsterEvent
from dagster._core.execution.api import create_execution_plan, scoped_job_context
from dagster._core.execution.plan.outputs import StepOutputHandle
-from dagster._core.execution.plan.plan import ExecutionPlan
from dagster._core.execution.plan.state import KnownExecutionState
from dagster._core.execution.plan.step import ExecutionStep
from dagster._core.execution.resources_init import (
@@ -34,7 +33,6 @@
)
from dagster._core.instance import DagsterInstance
from dagster._core.instance.ref import InstanceRef
-from dagster._core.log_manager import DagsterLogManager
from dagster._core.storage.dagster_run import DagsterRun, DagsterRunStatus
from dagster._core.system_config.objects import ResolvedRunConfig, ResourceConfig
from dagster._core.utils import make_new_run_id
@@ -47,6 +45,8 @@
from .serialize import PICKLE_PROTOCOL
if TYPE_CHECKING:
+ from dagster._core.execution.plan.plan import ExecutionPlan
+ from dagster._core.log_manager import DagsterLogManager
from dagster._core.definitions.node_definition import NodeDefinition
@@ -81,8 +81,8 @@ def _setup_resources(
self,
resource_defs: Mapping[str, ResourceDefinition],
resource_configs: Mapping[str, ResourceConfig],
- log_manager: DagsterLogManager,
- execution_plan: Optional[ExecutionPlan],
+ log_manager: "DagsterLogManager",
+ execution_plan: Optional["ExecutionPlan"],
dagster_run: Optional[DagsterRun],
resource_keys_to_init: Optional[AbstractSet[str]],
instance: Optional[DagsterInstance], |
|
We may want to add a setting to allow users to either (1) insert |
|
@smackesey -- You may be interested in this (finally resurrected after a long break). |
4ebec4e to
b19e86e
Compare
653b685 to
ce3d2b2
Compare
ce3d2b2 to
c6f9ab4
Compare
c6f9ab4 to
eef1966
Compare
eef1966 to
faf1c2f
Compare
faf1c2f to
bf5749a
Compare
|
I've gone through the changes and it looks good although I'd like to give a second look at it in the evening. |
Definitely-- thanks for bringing it back! To spare churn in imports for other engs I like to apply changes in one PR-- is this anywhere on your agenda: #1107? Would be great to apply them both at once. |
|
@smackesey - I'm still interested in supporting that but it won't be merged on the same timeline, since it requires us to build up an import map that we share across modules. |
MichaReiser
left a comment
There was a problem hiding this comment.
Looks good to me, but I only reviewed if I roughly understand what's happening, not if the implemented semantics are correct (because I don't understand thme).
...es/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs
Outdated
Show resolved
Hide resolved
| /// The trimmed range of the import (e.g., `List` in `from typing import List`). | ||
| pub(crate) range: TextRange, | ||
| /// The range of the import's parent statement. | ||
| pub(crate) parent_range: Option<TextRange>, |
There was a problem hiding this comment.
In which situations can the parent_range be None? Shouldn't an import always come from a statement. This might be worth documenting.
crates/ruff_workspace/src/options.rs
Outdated
| /// In other words, moving `from collections.abc import Sequence` into an | ||
| /// `if TYPE_CHECKING:` block above would cause a runtime error. |
There was a problem hiding this comment.
This part confused me. I admint, mainly because I didn't read carefully but I first thought that what ruff does will create a runtime error.
maybe explain why it causes a runtime error.
Or it might be that I got confused because the documentation explains what it does and the example shows when it doesn't work (rather than when it does).
would cause a runtime error because the type is no longer available at runtime.
dhruvmanila
left a comment
There was a problem hiding this comment.
Thanks! This is a great improvement to the rule. Overall the implementation looks really solid apart from some minor nits.
| flake8_type_checking::helpers::runtime_evaluated_class( | ||
| flake8_type_checking::helpers::runtime_required_class( |
There was a problem hiding this comment.
It's unfortunate that we now have runtime required internally but runtime evaluated in the user facing option. Maybe we could use either v0.2.0 or v0.3.0 as a way to rename these options.
There was a problem hiding this comment.
Yes great observation! It's really unfortunate.
There was a problem hiding this comment.
Actually, I'm gonna change the internal names.
| if annotation.contains('\'') || annotation.contains('"') { | ||
| return Err(anyhow::anyhow!("Annotation already contains a quote")); | ||
| } |
There was a problem hiding this comment.
In the function example, we've mentioned to update the internal quote:
- When quoting
SeriesinSeries[Literal["pd.Timestamp"]], we want"Series[Literal['pd.Timestamp']]".
With this code, I think that doesn't work, right? Can we use the Stylist to only check for the opposite quote?
There was a problem hiding this comment.
Yeah sorry, it doesn't do that yet.
...es/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs
Outdated
Show resolved
Hide resolved
bf5749a to
054dd68
Compare
daa49ed to
f3eaec7
Compare
Summary
This allows us to fix usages like:
By quoting the
DataFramein-> DataFrame. Without quotes, movingfrom pandas import DataFrameinto anif TYPE_CHECKING:block will fail at runtime, since Python tries to evaluate the annotation to add it to the function's__annotations__.Unfortunately, this does require us to split our "annotation kind" flags into three categories, rather than two:
typing-only: The annotation is only evaluated at type-checking-time.runtime-evaluated: Python will evaluate the annotation at runtime (like above) -- but we're willing to quote it.runtime-required: Python will evaluate the annotation at runtime (like above), and some library (like Pydantic) needs it to be available at runtime, so we can't quote it.This functionality is gated behind a setting (
flake8-type-checking.quote-annotations).Closes #5559.