If I set UPLOAD_DIR to a string and enable UPLOAD_KEEP_FILENAME, I get a TypeError. Fix
python-multipart: A TypeError occurs when UPLOAD_DIR is set to a string and UPLOAD_KEEP_FILENAME is enabled
Link: https://github.com/Kludex/python-multipart/pull/335
- Repository: Kludex/python-multipart (main, 212819d, version 0.0.32 and later)
- Type: PR (bug fix)
- Title: Accept a
strUPLOAD_DIRwhenUPLOAD_KEEP_FILENAMEis set
Background
SceneMakerAI’s poc-stt-bench uses FastAPI to receive video and audio uploads, with python-multipart handling the multipart parsing. While reviewing the upload path, I examined the settings of this library’s File class. The configuration table in the File docstring specifies the UPLOAD_DIR type as str, and the FileConfig type hint is str | bytes | None. However, if a str path is provided along with UPLOAD_KEEP_FILENAME=True, an exception occurs during the first write that exceeds the memory limit (MAX_MEMORY_FILE_SIZE). Since the default FastAPI path does not use this setting, this issue only occurs in code that directly uses the File object.
| UPLOAD_DIR | UPLOAD_KEEP_FILENAME | Result |
|---|---|---|
| str | False | Normal |
| bytes | True | Normal |
| str | True | TypeError: Can't mix strings and bytes in path components |
Cause
In python_multipart/multipart.py’s File._get_disk_file() (line 542), the branch for preserving the filename is as follows:
path = os.path.join(file_dir, fname) # type: ignore[arg-type]
Since fname is always bytes, if file_dir is str, os.path.join raises a TypeError. The temporary file branch immediately below handles both str and bytes, but this specific branch was missing, and type: ignore was masking the mypy warning. Since TypeError is not an OSError, it is not converted to FileError. The existing tests did not catch this issue because they only used bytes paths in this combination.
Changes
- Changed to
os.path.join(os.fsencode(file_dir), fname)and removedtype: ignore(1 line). The behavior of thebytespath remains unchanged. - Added
test_str_upload_dir_with_keep_filenametotests/test_file.py.
Verification
- Before the fix: Verified that the new test failed due to the TypeError mentioned above.
- After the fix:
coverage run -m pytest163 passed, coverage 100% (based on the repository’sfail_under=100). ruff format --check,ruff check,mypy --strict,check-sdist, andscripts/rename(nox) all passed.- Run only on Python 3.11. Do not run versions 3.12–3.15 from the CI matrix locally.
Notes
- Duplicate check: Open PR #308, which addresses the same
_get_disk_file(), deals with an AttributeError that occurs whenfile_name=Noneis used; the cause differs from this issue. - Separately verified during investigation: In multipart, the base64/quoted-printable decoders are only finalized for the last part, so truncated base64 in non-final parts passes through without exceptions. This was not included in the scope of this PR.