diff --git a/python_multipart/multipart.py b/python_multipart/multipart.py index d50e5b3..5f0dfe8 100644 --- a/python_multipart/multipart.py +++ b/python_multipart/multipart.py @@ -409,8 +409,10 @@ def __init__( # Extract just the basename to avoid directory traversal basename = os.path.basename(file_name) base, ext = os.path.splitext(basename) - self._file_base = base - self._ext = ext + else: + base, ext = b"", b"" + self._file_base = base + self._ext = ext @property def field_name(self) -> bytes | None: diff --git a/tests/test_file.py b/tests/test_file.py index a2aa134..562d2f1 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -3,6 +3,32 @@ from python_multipart.multipart import File +def test_none_file_name_with_keep_extensions_no_attribute_error() -> None: + """File(file_name=None) must not raise AttributeError when flushed to disk + with UPLOAD_KEEP_EXTENSIONS=True (regression: _ext was not initialised for + the None branch).""" + file = File(None, config={"UPLOAD_KEEP_EXTENSIONS": True, "MAX_MEMORY_FILE_SIZE": 0}) + try: + # Writing one byte exceeds MAX_MEMORY_FILE_SIZE=0, triggering flush_to_disk() + # which previously accessed self._ext -> AttributeError. + file.write(b"x") + finally: + file.close() + + +def test_none_file_name_with_keep_filename_no_attribute_error() -> None: + """File(file_name=None) must not raise AttributeError when flushed to disk + with UPLOAD_KEEP_FILENAME=True (regression: _file_base was not initialised + for the None branch).""" + file = File(None, config={"UPLOAD_KEEP_FILENAME": True, "MAX_MEMORY_FILE_SIZE": 0}) + try: + # This reaches the UPLOAD_DIR-is-None branch, which accesses self._ext + # via the suffix assignment — previously an AttributeError. + file.write(b"x") + finally: + file.close() + + def test_upload_dir_with_leading_slash_in_filename(tmp_path: Path) -> None: upload_dir = tmp_path / "upload" upload_dir.mkdir()