This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-1-test by this push:
new 35ea1f2d07b [v3-1-test] Fix .airflowignore order precedence (#56509)
(#56832)
35ea1f2d07b is described below
commit 35ea1f2d07b66669d7b87c01fb4a40248b980ec8
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Oct 19 01:52:48 2025 +0200
[v3-1-test] Fix .airflowignore order precedence (#56509) (#56832)
* Bug fix: proper include-exclude .airflowignore order precedence
* remove lingering commented code
* Remove commented unit test line
(cherry picked from commit da66c417269683ad0f45ada76c85ebd5cc41a9a4)
Co-authored-by: Zach <[email protected]>
---
airflow-core/src/airflow/utils/file.py | 23 +++++++-------
airflow-core/tests/unit/dags/.airflowignore_glob | 7 +++--
.../unit/dags/subdir1/test_explicit_dont_ignore.py | 29 ++++++++++++++++++
.../dags/subdir2/subdir3/should_ignore_this.py | 17 +++++++++++
.../dags/subdir2/subdir3/test_explicit_ignore.py | 16 ++++++++++
airflow-core/tests/unit/utils/test_file.py | 35 +++++++++++++++-------
6 files changed, 102 insertions(+), 25 deletions(-)
diff --git a/airflow-core/src/airflow/utils/file.py
b/airflow-core/src/airflow/utils/file.py
index 21bd5c0511a..d546c0ac352 100644
--- a/airflow-core/src/airflow/utils/file.py
+++ b/airflow-core/src/airflow/utils/file.py
@@ -102,24 +102,21 @@ class _GlobIgnoreRule(NamedTuple):
relative_to = definition_file.parent
ignore_pattern = GitWildMatchPattern(pattern)
- return _GlobIgnoreRule(ignore_pattern, relative_to)
+ return _GlobIgnoreRule(wild_match_pattern=ignore_pattern,
relative_to=relative_to)
@staticmethod
def match(path: Path, rules: list[_IgnoreRule]) -> bool:
- """Match a list of ignore rules against the supplied path."""
+ """Match a list of ignore rules against the supplied path, accounting
for exclusion rules and ordering."""
matched = False
- for r in rules:
- if not isinstance(r, _GlobIgnoreRule):
- raise ValueError(f"_GlobIgnoreRule cannot match rules of type:
{type(r)}")
- rule: _GlobIgnoreRule = r # explicit typing to make mypy play
nicely
+ for rule in rules:
+ if not isinstance(rule, _GlobIgnoreRule):
+ raise ValueError(f"_GlobIgnoreRule cannot match rules of type:
{type(rule)}")
rel_path = str(path.relative_to(rule.relative_to) if
rule.relative_to else path.name)
- matched = rule.wild_match_pattern.match_file(rel_path) is not None
-
- if matched:
- if not rule.wild_match_pattern.include:
- return False
-
- return matched
+ if (
+ rule.wild_match_pattern.include is not None
+ and rule.wild_match_pattern.match_file(rel_path) is not None
+ ):
+ matched = rule.wild_match_pattern.include
return matched
diff --git a/airflow-core/tests/unit/dags/.airflowignore_glob
b/airflow-core/tests/unit/dags/.airflowignore_glob
index b773879d2f0..ee7ee40eebf 100644
--- a/airflow-core/tests/unit/dags/.airflowignore_glob
+++ b/airflow-core/tests/unit/dags/.airflowignore_glob
@@ -5,12 +5,15 @@
*_invalid_* # skip invalid files
# test ignoring files at all levels
-**/*_dont_* # ignore all python files at all levels with "dont"
in their name
-!**/*_negate_ignore.py
+**/*should_ignore_* # ignore all python files at all levels
with "should_ignore" in their name
+subdir2/subdir3/test_explicit_ignore.py # ignore this explicit path
subdir2/**/test_nested*.py # ignore files in subdir2/subdir3
+!subdir2/**/*_negate_ignore.py # do not ignore files ending with
'_negate_ignore.py'
# test matching and ignoring of path separators
subdir1/* # ignore all of subdir1
+!subdir1/test_explicit_dont_ignore.py # do not ignore this explicit path
+!subdir1/*_negate_ignore.py # Do not ignore this one file in subdir1
subdir2*test* # this should not match anything in the subdir2 directory
subdir2?test* # this should not match anything in the subdir2 directory
diff --git a/airflow-core/tests/unit/dags/subdir1/test_explicit_dont_ignore.py
b/airflow-core/tests/unit/dags/subdir1/test_explicit_dont_ignore.py
new file mode 100644
index 00000000000..cbacd930ca0
--- /dev/null
+++ b/airflow-core/tests/unit/dags/subdir1/test_explicit_dont_ignore.py
@@ -0,0 +1,29 @@
+#
+# 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 datetime import datetime
+
+from airflow.models.dag import DAG
+from airflow.providers.standard.operators.bash import BashOperator
+
+DEFAULT_DATE = datetime(2019, 12, 1)
+
+dag = DAG(dag_id="test_dag_explicit_dont_ignore", start_date=DEFAULT_DATE,
schedule=None)
+task = BashOperator(task_id="task1", bash_command='echo "test dag explicitly
dont ignore"', dag=dag)
diff --git a/airflow-core/tests/unit/dags/subdir2/subdir3/should_ignore_this.py
b/airflow-core/tests/unit/dags/subdir2/subdir3/should_ignore_this.py
new file mode 100644
index 00000000000..217e5db9607
--- /dev/null
+++ b/airflow-core/tests/unit/dags/subdir2/subdir3/should_ignore_this.py
@@ -0,0 +1,17 @@
+#
+# 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.
diff --git
a/airflow-core/tests/unit/dags/subdir2/subdir3/test_explicit_ignore.py
b/airflow-core/tests/unit/dags/subdir2/subdir3/test_explicit_ignore.py
new file mode 100644
index 00000000000..13a83393a91
--- /dev/null
+++ b/airflow-core/tests/unit/dags/subdir2/subdir3/test_explicit_ignore.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/airflow-core/tests/unit/utils/test_file.py
b/airflow-core/tests/unit/utils/test_file.py
index 47c89b2466b..ca6c7e534b9 100644
--- a/airflow-core/tests/unit/utils/test_file.py
+++ b/airflow-core/tests/unit/utils/test_file.py
@@ -20,6 +20,7 @@ from __future__ import annotations
import os
import zipfile
from pathlib import Path
+from pprint import pformat
from unittest import mock
import pytest
@@ -125,22 +126,32 @@ class TestListPyFilesPath:
assert all(os.path.basename(file) not in should_ignore for file in
files)
def test_find_path_from_directory_glob_ignore(self):
- should_ignore = [
+ should_ignore = {
+ "should_ignore_this.py",
+ "test_explicit_ignore.py",
"test_invalid_cron.py",
"test_invalid_param.py",
"test_ignore_this.py",
"test_prev_dagrun_dep.py",
"test_nested_dag.py",
- "test_dont_ignore.py",
".airflowignore",
- ]
- should_not_ignore = ["test_on_kill.py", "test_negate_ignore.py",
"test_nested_negate_ignore.py"]
- files = list(find_path_from_directory(TEST_DAGS_FOLDER,
".airflowignore_glob", "glob"))
+ }
+ should_not_ignore = {
+ "test_on_kill.py",
+ "test_negate_ignore.py",
+ "test_dont_ignore_this.py",
+ "test_nested_negate_ignore.py",
+ "test_explicit_dont_ignore.py",
+ }
+ actual_files = list(find_path_from_directory(TEST_DAGS_FOLDER,
".airflowignore_glob", "glob"))
- assert files
- assert all(os.path.basename(file) not in should_ignore for file in
files)
- assert sum(1 for file in files if os.path.basename(file) in
should_not_ignore) == len(
- should_not_ignore
+ assert actual_files
+ assert all(os.path.basename(file) not in should_ignore for file in
actual_files)
+ actual_included_filenames = set(
+ [os.path.basename(f) for f in actual_files if os.path.basename(f)
in should_not_ignore]
+ )
+ assert actual_included_filenames == should_not_ignore, (
+ f"actual_included_filenames:
{pformat(actual_included_filenames)}\nexpected_included_filenames:
{pformat(should_not_ignore)}"
)
def test_find_path_from_directory_respects_symlinks_regexp_ignore(self,
test_dir):
@@ -223,6 +234,8 @@ class TestListPyFilesPath:
# No_dags is empty, _invalid_ is ignored by .airflowignore
ignored_files = {
"no_dags.py",
+ "should_ignore_this.py",
+ "test_explicit_ignore.py",
"test_invalid_cron.py",
"test_invalid_dup_task.py",
"test_ignore_this.py",
@@ -243,7 +256,9 @@ class TestListPyFilesPath:
if file_name not in ignored_files:
expected_files.add(f"{root}/{file_name}")
detected_files = set(list_py_file_paths(TEST_DAG_FOLDER))
- assert detected_files == expected_files
+ assert detected_files == expected_files, (
+ f"Detected files mismatched expected files:\ndetected_files:
{pformat(detected_files)}\nexpected_files: {pformat(expected_files)}"
+ )
@pytest.mark.parametrize(