Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
defnull opened this issue Nov 30, 2024 · 0 comments
Open

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

defnull opened this issue Nov 30, 2024 · 0 comments
Labels
Discussion Needs feedback Feature Feature requests or other non-bugs

Comments

@defnull
Copy link
Owner

defnull commented Nov 30, 2024

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.

@defnull defnull added Feature Feature requests or other non-bugs Discussion Needs feedback labels Nov 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Needs feedback Feature Feature requests or other non-bugs
Projects
None yet
Development

No branches or pull requests

1 participant