This is an automated email from the ASF dual-hosted git repository.
eladkal 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 555a47c31e4 [v3-1-test] fix(task-sdk): exclude pathlib.Path from
Resolvable.resolve() in templater (#63306) (#63633)
555a47c31e4 is described below
commit 555a47c31e4ec8c1db6b6cb94003cb431934f029
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Mar 15 14:48:53 2026 +0200
[v3-1-test] fix(task-sdk): exclude pathlib.Path from Resolvable.resolve()
in templater (#63306) (#63633)
pathlib.Path objects have a resolve() method for filesystem resolution
(strict parameter), which conflicts with the Resolvable.resolve(context)
protocol used by the templater. When a pathlib.Path subclass is passed as
a templated field value, the templater incorrectly calls
path.resolve(context), where the context dict is interpreted as
strict=True, causing FileNotFoundError if the path doesn't exist.
This adds an isinstance(value, os.PathLike) guard before calling
resolve() to ensure pathlib.Path objects are returned as-is.
(cherry picked from commit 23dec8d2a6ec3f8ef0a7b6f12255b990ba861ef1)
Closes: #55412
Co-authored-by: Yoann <[email protected]>
---
.../airflow/sdk/definitions/_internal/templater.py | 3 ++-
.../task_sdk/definitions/_internal/test_templater.py | 20 ++++++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/task-sdk/src/airflow/sdk/definitions/_internal/templater.py
b/task-sdk/src/airflow/sdk/definitions/_internal/templater.py
index 62821eb4de7..d116ac9a401 100644
--- a/task-sdk/src/airflow/sdk/definitions/_internal/templater.py
+++ b/task-sdk/src/airflow/sdk/definitions/_internal/templater.py
@@ -19,6 +19,7 @@ from __future__ import annotations
import datetime
import logging
+import os
from collections.abc import Collection, Iterable, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
@@ -178,7 +179,7 @@ class Templater:
if isinstance(value, ObjectStoragePath):
return self._render_object_storage_path(value, context, jinja_env)
- if resolve := getattr(value, "resolve", None):
+ if not isinstance(value, os.PathLike) and (resolve := getattr(value,
"resolve", None)):
return resolve(context)
# Fast path for common built-in collections.
diff --git a/task-sdk/tests/task_sdk/definitions/_internal/test_templater.py
b/task-sdk/tests/task_sdk/definitions/_internal/test_templater.py
index b097e32a4c4..c6569a8b73b 100644
--- a/task-sdk/tests/task_sdk/definitions/_internal/test_templater.py
+++ b/task-sdk/tests/task_sdk/definitions/_internal/test_templater.py
@@ -81,6 +81,26 @@ class TestTemplater:
assert rendered_content == "Hello {{ name }}"
+ def test_render_template_pathlib_path_not_resolved(self):
+ """Test that pathlib.Path objects are not incorrectly resolved via
their resolve() method.
+
+ pathlib.Path has a resolve() method for filesystem resolution, which
should not be
+ confused with the Resolvable.resolve(context) protocol used by the
templater.
+ See: https://github.com/apache/airflow/issues/55412
+ """
+ import pathlib
+
+ templater = Templater()
+ templater.template_ext = []
+ context = {"ds": "2006-02-01"}
+ path = pathlib.PurePosixPath("/some/path/to/file.txt")
+
+ rendered = templater.render_template(path, context)
+
+ # The path should be returned as-is, not passed through
resolve(context)
+ assert rendered == path
+ assert isinstance(rendered, pathlib.PurePosixPath)
+
def test_not_render_file_literal_value(self):
templater = Templater()
templater.template_ext = [".txt"]