This is an automated email from the ASF dual-hosted git repository.
mobuchowski pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new a3bf92fe855 Fix lineage entities sharing mutable defaults across
instances (#72509)
a3bf92fe855 is described below
commit a3bf92fe855e1d97acf9ee8712582df222e98add
Author: Keith <[email protected]>
AuthorDate: Thu Sep 10 06:51:50 2026 +0900
Fix lineage entities sharing mutable defaults across instances (#72509)
Bare mutable defaults on attrs classes are evaluated once and shared
class-wide, so tagging one Table (or Column) silently mutated the
tags/columns/owners/extra of every other instance — corrupting lineage
metadata whenever more than one entity was in play.
---
.../providers/common/compat/lineage/entities.py | 10 ++---
.../unit/common/compat/lineage/test_entities.py | 47 ++++++++++++++++++++++
2 files changed, 52 insertions(+), 5 deletions(-)
diff --git
a/providers/common/compat/src/airflow/providers/common/compat/lineage/entities.py
b/providers/common/compat/src/airflow/providers/common/compat/lineage/entities.py
index 74023f4ddba..f6103f796af 100644
---
a/providers/common/compat/src/airflow/providers/common/compat/lineage/entities.py
+++
b/providers/common/compat/src/airflow/providers/common/compat/lineage/entities.py
@@ -61,7 +61,7 @@ class Column:
name: str = attr.ib()
description: str | None = None
data_type: str = attr.ib()
- tags: list[Tag] = []
+ tags: list[Tag] = attr.Factory(list)
template_fields: ClassVar = ("name", "description", "data_type", "tags")
@@ -83,11 +83,11 @@ class Table:
database: str = attr.ib()
cluster: str = attr.ib()
name: str = attr.ib()
- tags: list[Tag] = []
+ tags: list[Tag] = attr.Factory(list)
description: str | None = None
- columns: list[Column] = []
- owners: list[User] = []
- extra: dict[str, Any] = {}
+ columns: list[Column] = attr.Factory(list)
+ owners: list[User] = attr.Factory(list)
+ extra: dict[str, Any] = attr.Factory(dict)
type_hint: str | None = None
template_fields: ClassVar = (
diff --git
a/providers/common/compat/tests/unit/common/compat/lineage/test_entities.py
b/providers/common/compat/tests/unit/common/compat/lineage/test_entities.py
new file mode 100644
index 00000000000..9ec0ea72675
--- /dev/null
+++ b/providers/common/compat/tests/unit/common/compat/lineage/test_entities.py
@@ -0,0 +1,47 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from airflow.providers.common.compat.lineage.entities import Column, Table,
Tag, User
+
+
+class TestMutableDefaultsAreNotShared:
+ """Each instance must get its own container; a bare ``= []`` default is
shared class-wide."""
+
+ def test_column_tags_are_isolated_between_instances(self):
+ first = Column(name="a", data_type="INT")
+ second = Column(name="b", data_type="INT")
+
+ first.tags.append(Tag(tag_name="pii"))
+
+ assert first.tags == [Tag(tag_name="pii")]
+ assert second.tags == []
+ assert first.tags is not second.tags
+
+ def test_table_containers_are_isolated_between_instances(self):
+ first = Table(database="db", cluster="cluster", name="first")
+ second = Table(database="db", cluster="cluster", name="second")
+
+ first.tags.append(Tag(tag_name="pii"))
+ first.columns.append(Column(name="id", data_type="INT"))
+ first.owners.append(User(email="[email protected]"))
+ first.extra["source"] = "warehouse"
+
+ assert second.tags == []
+ assert second.columns == []
+ assert second.owners == []
+ assert second.extra == {}