Skip to content

Add functionality to generate valid multipart/form-data #71

@defnull

Description

@defnull

Provide a MultipartBuilder that can be used to produce valid multipart/form-data and offers an API that avoids user errors as much as possible. This can be used in tests or HTTP clients. Having both parsers and generators in one library seems to be a good fit.

API Idea

R = TypeVar("R")
_identity = lambda x: x

class MultipartBuilder(Generic[R]):
    def __init__(self, boundary: str, write_func: Callable[[Union[bytes,bytearray]], R] = _identity):
        " Create a new builder. "

    def add_part(self, name: str, filename: Optional[str] = None, content_type: Optional[str] = None) -> R:
        "Start a new text field or file upload."

    def write(self, chunk: Union[str, bytes, bytearray]) -> R:
        "Write a chunk of data to the current field."

    def close(self) -> R:
        "Write the final delimiter"

The write_func is responsible for writing a single bytearray to the target stream, or return a value that represents the intent to do so. The builder functions return whatever the write function returns. This allows this builder to be used in both blocking and non-blocking environments. If you pass in an async function, then the return value R will be a coroutine you can await.

# Blocking
def blocking(target: io.BufferedWriter):
    with MultipartBuilder("--foo", target.write) as builder:
        builder.add_part("name", "filename.txt")
        builder.write("content")

# Async
async def non_blocking(target: asyncio.StreamWriter):
    async with MultipartBuilder("--foo", target.write) as builder:
        await builder.add_part("name", "filename.txt")
        await builder.write("content")

# SansIO
def sans_io() -> Iterator[bytes]:
    builder = MultipartBuilder("--foo", lambda x: x)
    yield builder.add_part("name", "filename.txt")
    yield builder.write("content")
    yield builder.close()

Not sure if this is a good idea, though. It might be a little bit to clever and having dedicated APIs for AsyncMultipartBuilder and SansIOMultipartBuilder with proper method signatures may be more intuitive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DiscussionNeeds feedbackFeatureFeature requests or other non-bugs

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions