From e182231fc888b2d41f877d976656043b0d79f8c8 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Fri, 4 Jul 2025 09:51:30 -0400 Subject: [PATCH] Add path expansion to image/audio/file (#1038) * Add path expansion to image/audio/file * Make path tests platform agnostic --- src/fastmcp/utilities/types.py | 7 +++-- tests/utilities/test_types.py | 54 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/fastmcp/utilities/types.py b/src/fastmcp/utilities/types.py index 919f03abd..add9fdc0f 100644 --- a/src/fastmcp/utilities/types.py +++ b/src/fastmcp/utilities/types.py @@ -3,6 +3,7 @@ import base64 import inspect import mimetypes +import os from collections.abc import Callable from functools import lru_cache from pathlib import Path @@ -101,7 +102,7 @@ class Image: if path is not None and data is not None: raise ValueError("Only one of path or data can be provided") - self.path = Path(path) if path else None + self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None self.data = data self._format = format self._mime_type = self._get_mime_type() @@ -160,7 +161,7 @@ class Audio: if path is not None and data is not None: raise ValueError("Only one of path or data can be provided") - self.path = Path(path) if path else None + self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None self.data = data self._format = format self._mime_type = self._get_mime_type() @@ -219,7 +220,7 @@ class File: if path is not None and data is not None: raise ValueError("Only one of path or data can be provided") - self.path = Path(path) if path else None + self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None self.data = data self._format = format self._mime_type = self._get_mime_type() diff --git a/tests/utilities/test_types.py b/tests/utilities/test_types.py index 4ac9109c0..a08e5b6b2 100644 --- a/tests/utilities/test_types.py +++ b/tests/utilities/test_types.py @@ -1,4 +1,7 @@ import base64 +import os +import tempfile +from pathlib import Path from types import EllipsisType from typing import Annotated, Any @@ -131,6 +134,23 @@ class TestImage: assert image.data is None assert image._mime_type == "image/png" + def test_image_path_expansion_with_tilde(self): + """Test that ~ is expanded to the user's home directory.""" + image = Image(path="~/test.png") + assert image.path is not None + assert not str(image.path).startswith("~") + assert str(image.path).startswith(os.path.expanduser("~")) + + def test_image_path_expansion_with_env_var(self, monkeypatch): + """Test that environment variables are expanded.""" + test_dir = tempfile.mkdtemp() + monkeypatch.setenv("TEST_PATH", test_dir) + image = Image(path="$TEST_PATH/test.png") + assert image.path is not None + assert not str(image.path).startswith("$TEST_PATH") + expected_path = Path(test_dir) / "test.png" + assert image.path == expected_path + def test_image_initialization_with_data(self): """Test image initialization with data.""" image = Image(data=b"test") @@ -215,6 +235,23 @@ class TestAudio: assert audio.data is None assert audio._mime_type == "audio/wav" + def test_audio_path_expansion_with_tilde(self): + """Test that ~ is expanded to the user's home directory.""" + audio = Audio(path="~/test.wav") + assert audio.path is not None + assert not str(audio.path).startswith("~") + assert str(audio.path).startswith(os.path.expanduser("~")) + + def test_audio_path_expansion_with_env_var(self, monkeypatch): + """Test that environment variables are expanded.""" + test_dir = tempfile.mkdtemp() + monkeypatch.setenv("TEST_AUDIO_PATH", test_dir) + audio = Audio(path="$TEST_AUDIO_PATH/test.wav") + assert audio.path is not None + assert not str(audio.path).startswith("$TEST_AUDIO_PATH") + expected_path = Path(test_dir) / "test.wav" + assert audio.path == expected_path + def test_audio_initialization_with_data(self): """Test audio initialization with data.""" audio = Audio(data=b"test") @@ -312,6 +349,23 @@ class TestFile: assert file.data is None assert file._mime_type == "text/plain" + def test_file_path_expansion_with_tilde(self): + """Test that ~ is expanded to the user's home directory.""" + file = File(path="~/test.txt") + assert file.path is not None + assert not str(file.path).startswith("~") + assert str(file.path).startswith(os.path.expanduser("~")) + + def test_file_path_expansion_with_env_var(self, monkeypatch): + """Test that environment variables are expanded.""" + test_dir = tempfile.mkdtemp() + monkeypatch.setenv("TEST_FILE_PATH", test_dir) + file = File(path="$TEST_FILE_PATH/test.txt") + assert file.path is not None + assert not str(file.path).startswith("$TEST_FILE_PATH") + expected_path = Path(test_dir) / "test.txt" + assert file.path == expected_path + def test_file_initialization_with_data(self): """Test initialization with data and format.""" test_data = b"test data"