This is an automated email from the ASF dual-hosted git repository.

ephraimanierobi pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 861521631dda11be34f0c0146fce77c20d6d9dd2
Author: Jens Scheffler <[email protected]>
AuthorDate: Mon Nov 3 06:45:49 2025 +0100

    [v3-1-test] Enable ruff PLW1641 rule (#57679) (#57701)
    
    (cherry picked from commit 58fc8609d543676921a88e426793db83120460cb)
---
 airflow-core/src/airflow/serialization/serialized_objects.py     | 3 +++
 airflow-core/src/airflow/task/priority_strategy.py               | 3 +++
 airflow-core/src/airflow/timetables/_cron.py                     | 3 +++
 airflow-core/src/airflow/timetables/interval.py                  | 3 +++
 airflow-core/src/airflow/timetables/simple.py                    | 3 +++
 airflow-core/src/airflow/utils/db.py                             | 3 +++
 airflow-core/tests/unit/models/test_renderedtifields.py          | 3 +++
 airflow-core/tests/unit/serialization/test_dag_serialization.py  | 3 +++
 airflow-core/tests/unit/serialization/test_serde.py              | 3 +++
 airflow-core/tests/unit/utils/test_json.py                       | 3 +++
 airflow-core/tests/unit/utils/test_sqlalchemy.py                 | 2 +-
 dev/airflow_perf/sql_queries.py                                  | 3 +++
 devel-common/src/sphinx_exts/docs_build/errors.py                | 3 +++
 devel-common/src/sphinx_exts/docs_build/spelling_checks.py       | 3 +++
 devel-common/src/tests_common/test_utils/timetables.py           | 3 +++
 kubernetes-tests/tests/kubernetes_tests/test_base.py             | 3 +++
 .../celery/tests/integration/celery/test_celery_executor.py      | 3 +++
 .../kubernetes/src/airflow/providers/cncf/kubernetes/secret.py   | 3 +++
 .../src/airflow/providers/databricks/hooks/databricks.py         | 9 +++++++++
 .../src/airflow/providers/fab/auth_manager/models/__init__.py    | 3 +++
 .../tests/unit/snowflake/operators/test_snowflake_sql.py         | 3 +++
 providers/weaviate/tests/unit/weaviate/hooks/test_weaviate.py    | 3 +++
 pyproject.toml                                                   | 1 +
 task-sdk/src/airflow/sdk/definitions/asset/__init__.py           | 4 ++++
 task-sdk/src/airflow/sdk/definitions/operator_resources.py       | 6 ++++++
 task-sdk/src/airflow/sdk/definitions/param.py                    | 3 +++
 task-sdk/src/airflow/sdk/execution_time/context.py               | 9 +++++++++
 task-sdk/src/airflow/sdk/execution_time/lazy_sequence.py         | 3 +++
 task-sdk/src/airflow/sdk/io/store.py                             | 3 +++
 task-sdk/tests/task_sdk/bases/test_operator.py                   | 3 +++
 30 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/airflow-core/src/airflow/serialization/serialized_objects.py 
b/airflow-core/src/airflow/serialization/serialized_objects.py
index 626ead30132..727e2685daa 100644
--- a/airflow-core/src/airflow/serialization/serialized_objects.py
+++ b/airflow-core/src/airflow/serialization/serialized_objects.py
@@ -1345,6 +1345,9 @@ class SerializedBaseOperator(DAGNode, BaseSerialization):
             getattr(self, c, None) == getattr(other, c, None) for c in 
BaseOperator._comps
         )
 
+    def __hash__(self):
+        return hash((self.task_type, *[getattr(self, c, None) for c in 
BaseOperator._comps]))
+
     def __repr__(self) -> str:
         return f"<SerializedTask({self.task_type}): {self.task_id}>"
 
diff --git a/airflow-core/src/airflow/task/priority_strategy.py 
b/airflow-core/src/airflow/task/priority_strategy.py
index 05209fec8a3..90abf3aa0a3 100644
--- a/airflow-core/src/airflow/task/priority_strategy.py
+++ b/airflow-core/src/airflow/task/priority_strategy.py
@@ -73,6 +73,9 @@ class PriorityWeightStrategy(ABC):
             return False
         return self.serialize() == other.serialize()
 
+    def __hash__(self):
+        return hash(self.serialize())
+
 
 class _AbsolutePriorityWeightStrategy(PriorityWeightStrategy):
     """Priority weight strategy that uses the task's priority weight 
directly."""
diff --git a/airflow-core/src/airflow/timetables/_cron.py 
b/airflow-core/src/airflow/timetables/_cron.py
index 632c00ae2dc..b8bc22921ba 100644
--- a/airflow-core/src/airflow/timetables/_cron.py
+++ b/airflow-core/src/airflow/timetables/_cron.py
@@ -128,6 +128,9 @@ class CronMixin:
             return NotImplemented
         return self._expression == other._expression and self._timezone == 
other._timezone
 
+    def __hash__(self):
+        return hash((self._expression, self._timezone))
+
     @property
     def summary(self) -> str:
         return self._expression
diff --git a/airflow-core/src/airflow/timetables/interval.py 
b/airflow-core/src/airflow/timetables/interval.py
index e248dd67da2..c116b2343fb 100644
--- a/airflow-core/src/airflow/timetables/interval.py
+++ b/airflow-core/src/airflow/timetables/interval.py
@@ -201,6 +201,9 @@ class DeltaDataIntervalTimetable(DeltaMixin, 
_DataIntervalTimetable):
             return NotImplemented
         return self._delta == other._delta
 
+    def __hash__(self):
+        return hash(self._delta)
+
     def serialize(self) -> dict[str, Any]:
         from airflow.serialization.serialized_objects import 
encode_relativedelta
 
diff --git a/airflow-core/src/airflow/timetables/simple.py 
b/airflow-core/src/airflow/timetables/simple.py
index b5b6f2468f3..3723d7321ee 100644
--- a/airflow-core/src/airflow/timetables/simple.py
+++ b/airflow-core/src/airflow/timetables/simple.py
@@ -50,6 +50,9 @@ class _TrivialTimetable(Timetable):
             return NotImplemented
         return True
 
+    def __hash__(self):
+        return hash(self.__class__.__name__)
+
     def serialize(self) -> dict[str, Any]:
         return {}
 
diff --git a/airflow-core/src/airflow/utils/db.py 
b/airflow-core/src/airflow/utils/db.py
index 11bafc01c25..9e99f53c8c5 100644
--- a/airflow-core/src/airflow/utils/db.py
+++ b/airflow-core/src/airflow/utils/db.py
@@ -1596,6 +1596,9 @@ class LazySelectSequence(Sequence[T]):
         z = itertools.zip_longest(iter(self), iter(other), fillvalue=object())
         return all(x == y for x, y in z)
 
+    def __hash__(self):
+        return hash(tuple(x for x in iter(self)))
+
     def __reversed__(self) -> Iterator[T]:
         return iter(self._process_row(r) for r in 
self._session.execute(self._select_desc))
 
diff --git a/airflow-core/tests/unit/models/test_renderedtifields.py 
b/airflow-core/tests/unit/models/test_renderedtifields.py
index ca97b8456b7..47aa8829aa8 100644
--- a/airflow-core/tests/unit/models/test_renderedtifields.py
+++ b/airflow-core/tests/unit/models/test_renderedtifields.py
@@ -70,6 +70,9 @@ class ClassWithCustomAttributes:
     def __eq__(self, other):
         return self.__dict__ == other.__dict__
 
+    def __hash__(self):
+        return hash(self.__dict__)
+
     def __ne__(self, other):
         return not self.__eq__(other)
 
diff --git a/airflow-core/tests/unit/serialization/test_dag_serialization.py 
b/airflow-core/tests/unit/serialization/test_dag_serialization.py
index be27d4a8c7b..3373f238dd5 100644
--- a/airflow-core/tests/unit/serialization/test_dag_serialization.py
+++ b/airflow-core/tests/unit/serialization/test_dag_serialization.py
@@ -1339,6 +1339,9 @@ class TestStringifiedDAGs:
         def __eq__(self, other):
             return self.__dict__ == other.__dict__
 
+        def __hash__(self):
+            return hash(self.__dict__)
+
         def __ne__(self, other):
             return not self.__eq__(other)
 
diff --git a/airflow-core/tests/unit/serialization/test_serde.py 
b/airflow-core/tests/unit/serialization/test_serde.py
index 0dadfc7e9ee..6130437f1b9 100644
--- a/airflow-core/tests/unit/serialization/test_serde.py
+++ b/airflow-core/tests/unit/serialization/test_serde.py
@@ -142,6 +142,9 @@ class Z:
     def __eq__(self, other):
         return self.x == other.x
 
+    def __hash__(self):
+        return hash(self.x)
+
 
 @attr.define
 class Y:
diff --git a/airflow-core/tests/unit/utils/test_json.py 
b/airflow-core/tests/unit/utils/test_json.py
index 2148830bd43..052b8118bad 100644
--- a/airflow-core/tests/unit/utils/test_json.py
+++ b/airflow-core/tests/unit/utils/test_json.py
@@ -45,6 +45,9 @@ class Z:
     def __eq__(self, other):
         return self.x == other.x
 
+    def __hash__(self):
+        return hash(self.x)
+
 
 @dataclass
 class U:
diff --git a/airflow-core/tests/unit/utils/test_sqlalchemy.py 
b/airflow-core/tests/unit/utils/test_sqlalchemy.py
index 051c91f05b1..a7765086461 100644
--- a/airflow-core/tests/unit/utils/test_sqlalchemy.py
+++ b/airflow-core/tests/unit/utils/test_sqlalchemy.py
@@ -264,7 +264,7 @@ class TestExecutorConfigType:
         under older kubernetes library version.
         """
 
-        class MockAttrError:
+        class MockAttrError:  # noqa: PLW1641
             def __eq__(self, other):
                 raise AttributeError("hello")
 
diff --git a/dev/airflow_perf/sql_queries.py b/dev/airflow_perf/sql_queries.py
index 60f4a9e0cbf..a0164309d98 100644
--- a/dev/airflow_perf/sql_queries.py
+++ b/dev/airflow_perf/sql_queries.py
@@ -101,6 +101,9 @@ class Query(NamedTuple):
             and self.file == other.file
         )
 
+    def __hash__(self):
+        return hash((self.function, self.sql, self.location, self.file))
+
     def to_dict(self):
         """
         Convert selected attributes of the instance into a dictionary.
diff --git a/devel-common/src/sphinx_exts/docs_build/errors.py 
b/devel-common/src/sphinx_exts/docs_build/errors.py
index 823815fa5c3..d3dded809e9 100644
--- a/devel-common/src/sphinx_exts/docs_build/errors.py
+++ b/devel-common/src/sphinx_exts/docs_build/errors.py
@@ -45,6 +45,9 @@ class DocBuildError(NamedTuple):
         right = (other.file_path, other.line_no, other.message)
         return left == right
 
+    def __hash__(self):
+        return hash((self.file_path, self.line_no, self.message))
+
     def __ne__(self, other):
         return not self == other
 
diff --git a/devel-common/src/sphinx_exts/docs_build/spelling_checks.py 
b/devel-common/src/sphinx_exts/docs_build/spelling_checks.py
index 58197f13409..94a2de20500 100644
--- a/devel-common/src/sphinx_exts/docs_build/spelling_checks.py
+++ b/devel-common/src/sphinx_exts/docs_build/spelling_checks.py
@@ -61,6 +61,9 @@ class SpellingError(NamedTuple):
         )
         return left == right
 
+    def __hash__(self):
+        return hash((self.file_path, self.line_no, self.spelling, 
self.context_line, self.message))
+
     def __ne__(self, other):
         return not self == other
 
diff --git a/devel-common/src/tests_common/test_utils/timetables.py 
b/devel-common/src/tests_common/test_utils/timetables.py
index f2053d83b24..3d48921aa56 100644
--- a/devel-common/src/tests_common/test_utils/timetables.py
+++ b/devel-common/src/tests_common/test_utils/timetables.py
@@ -45,6 +45,9 @@ class CustomSerializationTimetable(Timetable):
             return False
         return self.value == other.value
 
+    def __hash__(self):
+        return hash(self.value)
+
     def serialize(self):
         return {"value": self.value}
 
diff --git a/kubernetes-tests/tests/kubernetes_tests/test_base.py 
b/kubernetes-tests/tests/kubernetes_tests/test_base.py
index 0ee8d49c727..20b807954f0 100644
--- a/kubernetes-tests/tests/kubernetes_tests/test_base.py
+++ b/kubernetes-tests/tests/kubernetes_tests/test_base.py
@@ -53,6 +53,9 @@ class StringContainingId(str):
     def __eq__(self, other):
         return self in other.strip() or self in other
 
+    def __hash__(self):
+        return hash(self)
+
 
 class BaseK8STest:
     """Base class for K8S Tests."""
diff --git a/providers/celery/tests/integration/celery/test_celery_executor.py 
b/providers/celery/tests/integration/celery/test_celery_executor.py
index 874fbce3cbc..a333d454404 100644
--- a/providers/celery/tests/integration/celery/test_celery_executor.py
+++ b/providers/celery/tests/integration/celery/test_celery_executor.py
@@ -312,6 +312,9 @@ class ClassWithCustomAttributes:
     def __eq__(self, other):
         return self.__dict__ == other.__dict__
 
+    def __hash__(self):
+        return hash(self.__dict__)
+
     def __ne__(self, other):
         return not self.__eq__(other)
 
diff --git 
a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/secret.py 
b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/secret.py
index 692777de447..bc063e75307 100644
--- a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/secret.py
+++ b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/secret.py
@@ -121,5 +121,8 @@ class Secret(K8SModel):
             and self.key == other.key
         )
 
+    def __hash__(self):
+        return hash((self.deploy_type, self.deploy_target, self.secret, 
self.key))
+
     def __repr__(self):
         return f"Secret({self.deploy_type}, {self.deploy_target}, 
{self.secret}, {self.key})"
diff --git 
a/providers/databricks/src/airflow/providers/databricks/hooks/databricks.py 
b/providers/databricks/src/airflow/providers/databricks/hooks/databricks.py
index aaa9fece60c..9d7b1bf417c 100644
--- a/providers/databricks/src/airflow/providers/databricks/hooks/databricks.py
+++ b/providers/databricks/src/airflow/providers/databricks/hooks/databricks.py
@@ -134,6 +134,9 @@ class RunState:
             and self.state_message == other.state_message
         )
 
+    def __hash__(self):
+        return hash((self.life_cycle_state, self.result_state, 
self.state_message))
+
     def __repr__(self) -> str:
         return str(self.__dict__)
 
@@ -183,6 +186,9 @@ class ClusterState:
     def __eq__(self, other) -> bool:
         return self.state == other.state and self.state_message == 
other.state_message
 
+    def __hash__(self):
+        return hash((self.state, self.state_message))
+
     def __repr__(self) -> str:
         return str(self.__dict__)
 
@@ -244,6 +250,9 @@ class SQLStatementState:
             and self.error_message == other.error_message
         )
 
+    def __hash__(self):
+        return hash((self.state, self.error_code, self.error_message))
+
     def __repr__(self) -> str:
         return str(self.__dict__)
 
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py 
b/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py
index dd895e1c0b7..6817ba53fde 100644
--- a/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py
+++ b/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py
@@ -91,6 +91,9 @@ class Resource(Model):
     def __eq__(self, other):
         return (isinstance(other, self.__class__)) and (self.name == 
other.name)
 
+    def __hash__(self):
+        return hash((self.id, self.name))
+
     def __neq__(self, other):
         return self.name != other.name
 
diff --git 
a/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_sql.py 
b/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_sql.py
index 9f3c92ab376..1c47a022c51 100644
--- a/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_sql.py
+++ b/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_sql.py
@@ -42,6 +42,9 @@ class MockRow:
     def __eq__(self, other):
         return isinstance(other, MockRow) and self.__dict__ == other.__dict__
 
+    def __hash__(self):
+        return hash(self.__dict__)
+
     def __repr__(self):
         return f"MockRow({self.__dict__})"
 
diff --git a/providers/weaviate/tests/unit/weaviate/hooks/test_weaviate.py 
b/providers/weaviate/tests/unit/weaviate/hooks/test_weaviate.py
index 00d4d3f0b44..7cc1f7b794a 100644
--- a/providers/weaviate/tests/unit/weaviate/hooks/test_weaviate.py
+++ b/providers/weaviate/tests/unit/weaviate/hooks/test_weaviate.py
@@ -100,6 +100,9 @@ class MockObject:
             return False
         return self.properties == other.properties and self.uuid == other.uuid
 
+    def __hash__(self):
+        return hash((self.properties, self.uuid))
+
 
 class TestWeaviateHook:
     """
diff --git a/pyproject.toml b/pyproject.toml
index feae5bdd786..fd232dc0a68 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -605,6 +605,7 @@ extend-select = [
     "PLW1507", # Shallow copy of os.environ via copy.copy(os.environ)
     "PLW1508", # Invalid type for environment variable default; expected str 
or None
     "PLW1510", # subprocess.run without explicit check argument
+    "PLW1641", # Object does not implement __hash__ method
     # Per rule enables
     "RUF006", # Checks for asyncio dangling task
     "RUF015", # Checks for unnecessary iterable allocation for first element
diff --git a/task-sdk/src/airflow/sdk/definitions/asset/__init__.py 
b/task-sdk/src/airflow/sdk/definitions/asset/__init__.py
index 3597f016f8b..4f986efa6bb 100644
--- a/task-sdk/src/airflow/sdk/definitions/asset/__init__.py
+++ b/task-sdk/src/airflow/sdk/definitions/asset/__init__.py
@@ -426,6 +426,10 @@ class Asset(os.PathLike, BaseAsset):
         f = attrs.filters.include(*attrs.fields_dict(Asset))
         return attrs.asdict(self, filter=f) == attrs.asdict(other, filter=f)
 
+    def __hash__(self):
+        f = attrs.filters.include(*attrs.fields_dict(Asset))
+        return hash(attrs.asdict(self, filter=f))
+
     @property
     def normalized_uri(self) -> str | None:
         """
diff --git a/task-sdk/src/airflow/sdk/definitions/operator_resources.py 
b/task-sdk/src/airflow/sdk/definitions/operator_resources.py
index 6cb1b551cd3..a00cc84f08a 100644
--- a/task-sdk/src/airflow/sdk/definitions/operator_resources.py
+++ b/task-sdk/src/airflow/sdk/definitions/operator_resources.py
@@ -55,6 +55,9 @@ class Resource:
             return NotImplemented
         return self.__dict__ == other.__dict__
 
+    def __hash__(self):
+        return hash(self.__dict__)
+
     def __repr__(self):
         return str(self.__dict__)
 
@@ -138,6 +141,9 @@ class Resources:
             return NotImplemented
         return self.__dict__ == other.__dict__
 
+    def __hash__(self):
+        return hash(self.__dict__)
+
     def __repr__(self):
         return str(self.__dict__)
 
diff --git a/task-sdk/src/airflow/sdk/definitions/param.py 
b/task-sdk/src/airflow/sdk/definitions/param.py
index 6c61fa88a70..5da589d79e9 100644
--- a/task-sdk/src/airflow/sdk/definitions/param.py
+++ b/task-sdk/src/airflow/sdk/definitions/param.py
@@ -159,6 +159,9 @@ class ParamsDict(MutableMapping[str, Any]):
             return self.dump() == other
         return NotImplemented
 
+    def __hash__(self):
+        return hash(self.dump())
+
     def __copy__(self) -> ParamsDict:
         return ParamsDict(self.__dict, self.suppress_exception)
 
diff --git a/task-sdk/src/airflow/sdk/execution_time/context.py 
b/task-sdk/src/airflow/sdk/execution_time/context.py
index 58bd2521b02..e4a647bab2f 100644
--- a/task-sdk/src/airflow/sdk/execution_time/context.py
+++ b/task-sdk/src/airflow/sdk/execution_time/context.py
@@ -351,6 +351,9 @@ class ConnectionAccessor:
         # All instances of ConnectionAccessor are equal since it is a 
stateless dynamic accessor
         return True
 
+    def __hash__(self):
+        return hash(self.__class__.__name__)
+
     def get(self, conn_id: str, default_conn: Any = None) -> Any:
         from airflow.exceptions import AirflowNotFoundException
 
@@ -376,6 +379,9 @@ class VariableAccessor:
         # All instances of VariableAccessor are equal since it is a stateless 
dynamic accessor
         return True
 
+    def __hash__(self):
+        return hash(self.__class__.__name__)
+
     def __repr__(self) -> str:
         return "<VariableAccessor (dynamic access)>"
 
@@ -412,6 +418,9 @@ class MacrosAccessor:
             return False
         return True
 
+    def __hash__(self):
+        return hash(self.__class__.__name__)
+
 
 class _AssetRefResolutionMixin:
     _asset_ref_cache: dict[AssetRef, AssetUniqueKey] = {}
diff --git a/task-sdk/src/airflow/sdk/execution_time/lazy_sequence.py 
b/task-sdk/src/airflow/sdk/execution_time/lazy_sequence.py
index d8b356870c7..4efb0b71368 100644
--- a/task-sdk/src/airflow/sdk/execution_time/lazy_sequence.py
+++ b/task-sdk/src/airflow/sdk/execution_time/lazy_sequence.py
@@ -81,6 +81,9 @@ class LazyXComSequence(Sequence[T]):
         z = itertools.zip_longest(iter(self), iter(other), fillvalue=object())
         return all(x == y for x, y in z)
 
+    def __hash__(self):
+        return hash((*[item for item in iter(self)],))
+
     def __iter__(self) -> Iterator[T]:
         return LazyXComIterator(seq=self)
 
diff --git a/task-sdk/src/airflow/sdk/io/store.py 
b/task-sdk/src/airflow/sdk/io/store.py
index c38fb81bb82..30a606f3832 100644
--- a/task-sdk/src/airflow/sdk/io/store.py
+++ b/task-sdk/src/airflow/sdk/io/store.py
@@ -120,6 +120,9 @@ class ObjectStore:
         except ValueError:
             return False
 
+    def __hash__(self):
+        return hash((self.conn_id, self.fsid))
+
 
 _STORE_CACHE: dict[str, ObjectStore] = {}
 
diff --git a/task-sdk/tests/task_sdk/bases/test_operator.py 
b/task-sdk/tests/task_sdk/bases/test_operator.py
index 62302ce4888..683bca1d574 100644
--- a/task-sdk/tests/task_sdk/bases/test_operator.py
+++ b/task-sdk/tests/task_sdk/bases/test_operator.py
@@ -63,6 +63,9 @@ class ClassWithCustomAttributes:
     def __eq__(self, other):
         return self.__dict__ == other.__dict__
 
+    def __hash__(self):
+        return hash(self.__dict__)
+
     def __ne__(self, other):
         return not self.__eq__(other)
 

Reply via email to