From 8dd4492ea00f9210b49da9ef0a6a6cd43b6557bd Mon Sep 17 00:00:00 2001 From: Felix R Date: Fri, 21 Nov 2025 13:13:24 +0900 Subject: [PATCH 1/3] chore: Parse the url before inferring type --- pydantic_ai_slim/pydantic_ai/messages.py | 43 ++++++++++++++---------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 9019b81931..3280fb34c1 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -10,6 +10,7 @@ from os import PathLike from pathlib import Path from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeAlias, cast, overload +from urllib.parse import urlparse import pydantic import pydantic_core @@ -228,21 +229,23 @@ def __init__( def _infer_media_type(self) -> VideoMediaType: """Return the media type of the video, based on the url.""" - if self.url.endswith('.mkv'): + # Parse URL path to remove query parameters (e.g., presigned URLs) + path = urlparse(self.url).path + if path.endswith('.mkv'): return 'video/x-matroska' - elif self.url.endswith('.mov'): + elif path.endswith('.mov'): return 'video/quicktime' - elif self.url.endswith('.mp4'): + elif path.endswith('.mp4'): return 'video/mp4' - elif self.url.endswith('.webm'): + elif path.endswith('.webm'): return 'video/webm' - elif self.url.endswith('.flv'): + elif path.endswith('.flv'): return 'video/x-flv' - elif self.url.endswith(('.mpeg', '.mpg')): + elif path.endswith(('.mpeg', '.mpg')): return 'video/mpeg' - elif self.url.endswith('.wmv'): + elif path.endswith('.wmv'): return 'video/x-ms-wmv' - elif self.url.endswith('.three_gp'): + elif path.endswith('.three_gp'): return 'video/3gpp' # Assume that YouTube videos are mp4 because there would be no extension # to infer from. This should not be a problem, as Gemini disregards media @@ -308,17 +311,19 @@ def _infer_media_type(self) -> AudioMediaType: References: - Gemini: https://ai.google.dev/gemini-api/docs/audio#supported-formats """ - if self.url.endswith('.mp3'): + # Parse URL path to remove query parameters (e.g., presigned URLs) + path = urlparse(self.url).path + if path.endswith('.mp3'): return 'audio/mpeg' - if self.url.endswith('.wav'): + if path.endswith('.wav'): return 'audio/wav' - if self.url.endswith('.flac'): + if path.endswith('.flac'): return 'audio/flac' - if self.url.endswith('.oga'): + if path.endswith('.oga'): return 'audio/ogg' - if self.url.endswith('.aiff'): + if path.endswith('.aiff'): return 'audio/aiff' - if self.url.endswith('.aac'): + if path.endswith('.aac'): return 'audio/aac' raise ValueError( @@ -367,13 +372,15 @@ def __init__( def _infer_media_type(self) -> ImageMediaType: """Return the media type of the image, based on the url.""" - if self.url.endswith(('.jpg', '.jpeg')): + # Parse URL path to remove query parameters (e.g., presigned URLs) + path = urlparse(self.url).path + if path.endswith(('.jpg', '.jpeg')): return 'image/jpeg' - elif self.url.endswith('.png'): + elif path.endswith('.png'): return 'image/png' - elif self.url.endswith('.gif'): + elif path.endswith('.gif'): return 'image/gif' - elif self.url.endswith('.webp'): + elif path.endswith('.webp'): return 'image/webp' else: raise ValueError( From 0e3c0897ef12812bc50adc4270dcf59bf16d4738 Mon Sep 17 00:00:00 2001 From: Felix R Date: Fri, 21 Nov 2025 13:13:38 +0900 Subject: [PATCH 2/3] test: verify the url handles query strings --- tests/test_messages.py | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/test_messages.py b/tests/test_messages.py index 943d68fe8c..488d847b62 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -333,6 +333,78 @@ def test_video_url_invalid(): VideoUrl('foobar.potato').media_type +@pytest.mark.parametrize( + 'url,media_type,format', + [ + pytest.param( + 'https://example.com/video.mp4?query=param', + 'video/mp4', + 'mp4', + id='mp4_with_query', + ), + pytest.param( + 'https://example.com/video.webm?X-Amz-Algorithm=AWS4-HMAC-SHA256', + 'video/webm', + 'webm', + id='webm_with_aws_params', + ), + ], +) +def test_video_url_with_query_parameters(url: str, media_type: str, format: str): + """Test that VideoUrl correctly infers media type from URLs with query parameters (e.g., presigned URLs).""" + video_url = VideoUrl(url) + assert video_url.media_type == media_type + assert video_url.format == format + + +@pytest.mark.parametrize( + 'url,media_type,format', + [ + pytest.param( + 'https://example.com/audio.mp3?query=param', + 'audio/mpeg', + 'mp3', + id='mp3_with_query', + ), + pytest.param( + 'https://example.com/audio.wav?X-Amz-Algorithm=AWS4-HMAC-SHA256', + 'audio/wav', + 'wav', + id='wav_with_aws_params', + ), + ], +) +def test_audio_url_with_query_parameters(url: str, media_type: str, format: str): + """Test that AudioUrl correctly infers media type from URLs with query parameters (e.g., presigned URLs).""" + audio_url = AudioUrl(url) + assert audio_url.media_type == media_type + assert audio_url.format == format + + +@pytest.mark.parametrize( + 'url,media_type,format', + [ + pytest.param( + 'https://example.com/image.png?query=param', + 'image/png', + 'png', + id='png_with_query', + ), + pytest.param( + 'https://example.com/image.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256', + 'image/jpeg', + 'jpeg', + id='jpg_with_aws_params', + ), + ], +) +def test_image_url_with_query_parameters(url: str, media_type: str, format: str): + """Test that ImageUrl correctly infers media type from URLs with query parameters (e.g., presigned URLs).""" + image_url = ImageUrl(url) + assert image_url.media_type == media_type + assert image_url.format == format + + def test_thinking_part_delta_apply_to_thinking_part_delta(): """Test lines 768-775: Apply ThinkingPartDelta to another ThinkingPartDelta.""" original_delta = ThinkingPartDelta( From 38db162088df3e9c41ddcf984e5d92ed38cf131e Mon Sep 17 00:00:00 2001 From: Felix R Date: Fri, 21 Nov 2025 18:03:57 +0900 Subject: [PATCH 3/3] chore: remove comments --- pydantic_ai_slim/pydantic_ai/messages.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 3280fb34c1..177eafad50 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -229,7 +229,6 @@ def __init__( def _infer_media_type(self) -> VideoMediaType: """Return the media type of the video, based on the url.""" - # Parse URL path to remove query parameters (e.g., presigned URLs) path = urlparse(self.url).path if path.endswith('.mkv'): return 'video/x-matroska' @@ -311,7 +310,6 @@ def _infer_media_type(self) -> AudioMediaType: References: - Gemini: https://ai.google.dev/gemini-api/docs/audio#supported-formats """ - # Parse URL path to remove query parameters (e.g., presigned URLs) path = urlparse(self.url).path if path.endswith('.mp3'): return 'audio/mpeg' @@ -372,7 +370,6 @@ def __init__( def _infer_media_type(self) -> ImageMediaType: """Return the media type of the image, based on the url.""" - # Parse URL path to remove query parameters (e.g., presigned URLs) path = urlparse(self.url).path if path.endswith(('.jpg', '.jpeg')): return 'image/jpeg'