You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by el...@apache.org on 2026/03/15 12:03:13 UTC

(airflow) branch main updated: fix(task-sdk): exclude pathlib.Path from Resolvable.resolve() in templater (#63306)

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

eladkal 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 23dec8d2a6e fix(task-sdk): exclude pathlib.Path from Resolvable.resolve() in templater (#63306)
23dec8d2a6e is described below

commit 23dec8d2a6ec3f8ef0a7b6f12255b990ba861ef1
Author: Yoann <60...@users.noreply.github.com>
AuthorDate: Sun Mar 15 05:03:05 2026 -0700

    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.
    
    Closes: #55412
---
 .../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 faf97bdd3f8..f094ccd6b28 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
@@ -186,7 +187,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 5f37a71712b..bcce3c89547 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"]