fix(audio): read PathLike content inside (filename, content) tuples

calculate_request_duration() dropped pathlib.Path (PathLike) content when an
audio upload was supplied as (filename, Path(...)) or
(filename, Path(...), content_type). The tuple branch only handled bytes and
file-like objects, so file_content stayed None and the local duration fallback
returned None even though the upload machinery accepts PathLike.

Add a PathLike branch in the tuple handling that reads the bytes from disk,
mirroring the existing bare-Path branch. Add a regression test covering the
tuple-with-Path and tuple-with-Path-and-content-type input shapes.

Fixes #49805
This commit is contained in:
AaronG 2026-11-03 23:50:43 +08:00
parent baddc67041
commit 7f626f3309
2 changed files with 34 additions and 0 deletions

View file

@ -311,6 +311,13 @@ def calculate_request_duration(file: FileTypes) -> float | None:
content: Final = file[1]
if isinstance(content, bytes):
file_content = content
elif isinstance(content, os.PathLike):
# pathlib.Path (or any PathLike) inside a (filename, content)
# tuple: read the bytes from disk, mirroring the bare-Path
# branch above. Without this the duration reader silently
# returns None for `(filename, Path(...))` uploads.
with open(str(content), "rb") as file_handle:
file_content = file_handle.read()
elif hasattr(content, "read") and not isinstance(content, (str, os.PathLike)):
# File-like object in tuple
current_pos: Final = getattr(content, "tell", lambda: None)()

View file

@ -340,6 +340,33 @@ class TestCalculateRequestDuration:
wav_header
), "File position should be restored to original position"
def test_pathlib_path_inside_tuple(self, tmp_path):
"""
Regression test: calculate_request_duration must read PathLike content
inside a (filename, content[, content_type]) tuple.
A transcription upload may be supplied as (filename, pathlib.Path(...))
or (filename, pathlib.Path(...), content_type). The duration reader used
to ignore PathLike tuple contents and silently return None, even though
the upload machinery accepts them.
"""
import wave
wav_path = tmp_path / "sample.wav"
with wave.open(str(wav_path), "wb") as audio:
audio.setnchannels(1)
audio.setsampwidth(2)
audio.setframerate(8000)
# 2 seconds of 16-bit mono audio at 8 kHz.
audio.writeframes(b"\0" * 32000)
# Same file in three accepted shapes must all report ~2.0s.
assert calculate_request_duration(wav_path) == pytest.approx(2.0)
assert calculate_request_duration((str(wav_path), wav_path)) == pytest.approx(2.0)
assert calculate_request_duration(
(str(wav_path), wav_path, "audio/wav")
) == pytest.approx(2.0)
class TestGetAudioFileContentHash:
"""Test the get_audio_file_content_hash function for cache key generation"""