From 1ee364717f4805f5cb017eeac4e4c23eb7637625 Mon Sep 17 00:00:00 2001 From: Yoann <60654707+YoannAbriel@users.noreply.github.com> Date: Sun, 15 Mar 2026 05:03:05 -0700 Subject: [PATCH] [v3-1-test] fix(task-sdk): exclude pathlib.Path from Resolvable.resolve() in templater (#63306) 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) Co-authored-by: Yoann <60654707+YoannAbriel@users.noreply.github.com> Closes: #55412 --- .../sdk/definitions/_internal/templater.py | 3 ++- .../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 62821eb4de7f4..d116ac9a401cf 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 @@ 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 @@ def render_template( 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 b097e32a4c409..c6569a8b73b8e 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 @@ def test_not_render_literal_value(self): 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"]