marshmallow-code / marshmallow-code/flask-smorest

Support for mixed multipart forms

Open
#557 7 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
717
Forks
77
Avg merge
7h 49m
Merged PRs (30d)
3

Description

@lafrech if you have a moment, please drop a note here if you support addressing the documented mixed multipart limitation in principle. If you do, I'll prepare a formal PR from the code below that @claudius-kienle contributed, unless he volunteers to do so

No hard feelings either way, and no rush whatsoever. I agree with your initial reaction, that this did not seem to be needing prioritization. It's an edge case, and a pattern that can be worked around easily in most cases

## Background

Issue #46 was originally created as an ask for supporting multiple files in multipart requests. Today, multiple files are indeed supported and this is well documented 🙏

At some point, that issue was taken a little off-course with questions about other multipart scenarios- specifically mixed file and variable requests

I created _this_ issue specifically for consideration of changes to the (documented) behavior/limitation for mixed multipart requests

## The Problem

Currently, multipart requests that contain both files and variables _work_ (that is, the decorated function gets what it should) _but_ the OpenAPI documentation is not emitted fully. This is well-known and made clear to users, despite it being considered a reasonably rare pattern/edge-case

## The Solution

The following was informally contributed by @claudius-kienle via the aforementioned issue

It has been used but not fully reviewed or properly tested

It parses mixed multipart form fields (files and variables) _and_ generates the correct documentation

It can be added to the `ArgumentsMixin` class and then used in place of the existing `ArgumentsMixin::arguments` method which, in practice, is applied as a decorator after being mixed in to the Blueprint class

Note, I did not write this code, but noticed that having it implemented this way (as an additional method, rather than a patch to the existing `arguments` method) allows it to function as "opt-in", which reduces the risk of breaking current users

```py
def multipart_arguments(self,
schema,
*,
content_type=None,
required=True,
description=None,
example=None,
examples=None,
**kwargs):
# At this stage, put schema instance in doc dictionary. Il will be
# replaced later on by $ref or json.
parameters = {
'in': 'files',
'required': required,
'schema': schema,
}
if content_type is not None:
parameters['content_type'] = content_type
if example is not None:
parameters['example'] = example
if examples is not None:
parameters['examples'] = examples
if description is not None:
parameters['description'] = description

error_status_code = kwargs.get('error_status_code', self.ARGUMENTS_PARSER.DEFAULT_VALIDATION_STATUS)

def decorator(func):

arg_parser = self.ARGUMENTS_PARSER.use_args(schema, location='files', **kwargs)

@wraps(func)
def wrapper(*f_args, **f_kwargs):
req = self.ARGUMENTS_PARSER.get_default_request()
from werkzeug.datastructures import ImmutableMultiDict

data = []
orig_files = req.files
orig_form = req.form
for key in req.files:
data.append((key, req.files[key]))
for key in req.form:
data.append((key, req.form[key]))
req.form = None
req.files = ImmutableMultiDict(data)

try:
result = arg_parser(func)(*f_args, **f_kwargs)
except Exception as e:
req.files = orig_files
req.form = orig_form
raise e

req.files = orig_files
req.form = orig_form

return result

# Add parameter to parameters list in doc info in function object
# The deepcopy avoids modifying the wrapped function doc
wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {}))
docs = wrapper._apidoc.setdefault('arguments', {})
docs.setdefault('parameters', []).append(parameters)
docs.setdefault('responses', {})[error_status_code] = http.HTTPStatus(error_status_code).name

# Call use_args (from webargs) to inject params in function
return wrapper

return decorator
```

_Originally posted by @claudius-kienle in https://github.com/marshmallow-code/flask-smorest/issues/46#issuecomment-1353828622_

As far as I know, there was no PR created for this. As such, there is no proposed documentation update, no test-cases, no formal or informal code review

However, I would love to get rid of the ugly `git+https://...` from my project `require` statement at some point. I would also like to not meed to worry about occasionally checking if any sync is needed from upstream

Thanks for your help on this @lafrech, regardless of where it goes from here

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the ArgumentsMixin class and its existing arguments method, then review the contributed multipart_arguments implementation in the issue. Add focused tests for mixed file and variable requests and verify that the generated OpenAPI documentation includes both; update the relevant documentation if the behavior becomes supported.

Written by the indexing model from the issue text.

Assessment

Tech stack
flask, openapi, python
Domain
api, documentation
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.