Skip to content

Commit

Permalink
test: add test case for multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
siddhantgoel committed Nov 6, 2024
1 parent 6a698e2 commit c0265f1
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 59 deletions.
46 changes: 36 additions & 10 deletions streaming_form_data/targets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import hashlib
from pathlib import Path
from typing import Callable, List, Optional, Union

import smart_open # type: ignore
from typing import Callable, List, Optional


class BaseTarget:
Expand Down Expand Up @@ -65,28 +66,45 @@ class MultipleTargets(BaseTarget):
"""

def __init__(self, next_target: Callable):
"""
Args:
next_target:
A callable that returns a new target which should be used for the next
input of the multiple inputs allowed for the specific field
"""

self._next_target = next_target

self._targets = []
self.targets: List[BaseTarget] = []
self._validator = None # next_target should have a validator

self._next_multipart_filename: Optional[str] = None
self._next_multipart_content_type: Optional[str] = None

def on_start(self):
target = self._next_target()

self._targets.append(target)
if self._next_multipart_filename is not None:
target.set_multipart_filename(self._next_multipart_filename)
self._next_multipart_filename = None
if self._next_multipart_content_type is not None:
target.set_multipart_filename(self._next_multipart_content_type)
self._next_multipart_content_type = None

self.targets.append(target)
target.start()

def on_data_received(self, chunk: bytes):
self._targets[-1].data_received(chunk)
self.targets[-1].data_received(chunk)

def on_finish(self):
self._targets[-1].finish()
self.targets[-1].finish()

def set_multipart_filename(self, filename: str):
self._targets[-1].set_multipart_filename(filename)
self._next_multipart_filename = filename

def set_multipart_content_type(self, content_type: str):
self._targets[-1].set_multipart_content_type(content_type)
self._next_multipart_content_type = content_type


class NullTarget(BaseTarget):
Expand Down Expand Up @@ -170,7 +188,11 @@ class FileTarget(BaseTarget):
"""

def __init__(
self, filename: str | Callable, allow_overwrite: bool = True, *args, **kwargs
self,
filename: Union[str, Callable],
allow_overwrite: bool = True,
*args,
**kwargs,
):
"""
Args:
Expand Down Expand Up @@ -207,7 +229,7 @@ class DirectoryTarget(BaseTarget):

def __init__(
self,
directory_path: str | Callable,
directory_path: Union[str, Callable],
allow_overwrite: bool = True,
*args,
**kwargs,
Expand Down Expand Up @@ -277,7 +299,11 @@ class SmartOpenTarget(BaseTarget):
"""

def __init__(
self, file_path: str | Callable, mode: str, transport_params=None, **kwargs
self,
file_path: Union[str, Callable],
mode: str,
transport_params=None,
**kwargs,
):
"""
Args:
Expand Down
98 changes: 49 additions & 49 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
DirectoryTarget,
SHA256Target,
ValueTarget,
MultipleTargets,
)
from streaming_form_data.validators import MaxSizeValidator, ValidationError

Expand Down Expand Up @@ -364,9 +365,7 @@ def test_parameter_contains_part_of_delimiter():
Foo
--123
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand Down Expand Up @@ -448,9 +447,7 @@ def test_file_upload():
Content-Disposition: form-data; name="files"; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand All @@ -477,9 +474,7 @@ def test_directory_upload(tmp_path):
Content-Disposition: form-data; name="files"; filename="cd.txt"
Bar
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = DirectoryTarget(tmp_path)

Expand Down Expand Up @@ -508,9 +503,7 @@ def test_unquoted_names():
Content-Disposition: form-data; name=files; filename=ab.txt
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand Down Expand Up @@ -542,9 +535,7 @@ def test_special_filenames():
Content-Disposition: form-data; name=files; filename={}
Foo
--1234--""".format(
filename
)
--1234--""".format(filename)
.replace("\n", "\r\n")
.encode("utf-8")
)
Expand All @@ -567,9 +558,7 @@ def test_boundary_starts_and_ends_with_quotes():
Content-Disposition: form-data; name="files"; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand All @@ -589,11 +578,7 @@ def test_missing_headers():
--1234
Foo
--1234--""".replace(
"\n", "\r\n"
).encode(
"utf-8"
)
--1234--""".replace("\n", "\r\n").encode("utf-8")

target = ValueTarget()

Expand All @@ -613,9 +598,7 @@ def test_invalid_content_disposition():
Content-Disposition: invalid; name="files"; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand All @@ -636,9 +619,7 @@ def test_without_name_parameter():
Content-Disposition: form-data; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand All @@ -659,9 +640,7 @@ def test_data_after_final_boundary():
Foo
--1234--
""".replace(
b"\n", b"\r\n"
)
""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand Down Expand Up @@ -692,9 +671,7 @@ def test_missing_filename_directive():
Foo
--1234--
""".replace(
b"\n", b"\r\n"
)
""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand Down Expand Up @@ -751,9 +728,7 @@ def test_target_exceeds_max_size():
Content-Disposition: form-data; name="files"; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget(validator=MaxSizeValidator(1))

Expand All @@ -775,9 +750,7 @@ def test_file_target_exceeds_max_size(tmp_path):
Content-Disposition: form-data; name="files"; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = FileTarget(tmp_path / "file.txt", validator=MaxSizeValidator(1))

Expand Down Expand Up @@ -854,9 +827,7 @@ def test_extra_headers():
Content-Transfer-Encoding: quoted-printable
Joe owes =80100.
--1234--""".replace(
b"\n", b"\r\n"
)
--1234--""".replace(b"\n", b"\r\n")

target = ValueTarget()

Expand All @@ -870,6 +841,39 @@ def test_extra_headers():
assert target.value == b"Joe owes =80100."


def test_multiple_inputs(tmp_path):
for filename in ("first.txt", "second.txt", "third.txt"):
with open(tmp_path / filename, "w") as file:
file.write(f"{filename}")

encoder = MultipartEncoder(
fields=[
("files", ("files", open(tmp_path / "first.txt", "rb"), "text/plain")),
("files", ("files", open(tmp_path / "second.txt", "rb"), "text/plain")),
("files", ("files", open(tmp_path / "third.txt", "rb"), "text/plain")),
]
)

class next_target:
def __init__(self):
self._index = 0

def __call__(self):
return ValueTarget()

target = MultipleTargets(next_target())

parser = StreamingFormDataParser(headers={"Content-Type": encoder.content_type})
parser.register("files", target)

parser.data_received(encoder.to_string())

assert len(target.targets) == 3
assert target.targets[0].value == b"first.txt"
assert target.targets[1].value == b"second.txt"
assert target.targets[2].value == b"third.txt"


def test_case_insensitive_content_disposition_header():
content_disposition_header = "Content-Disposition"

Expand All @@ -883,11 +887,7 @@ def test_case_insensitive_content_disposition_header():
{header}: form-data; name="files"; filename="ab.txt"
Foo
--1234--""".replace(
b"\n", b"\r\n"
).replace(
b"{header}", header.encode("utf-8")
)
--1234--""".replace(b"\n", b"\r\n").replace(b"{header}", header.encode("utf-8"))

target = ValueTarget()

Expand Down

0 comments on commit c0265f1

Please sign in to comment.