python-restx / flask-restx

Fork of Flask-RESTPlus: Fully featured framework for fast, easy and documented API development with Flask
https://flask-restx.readthedocs.io/en/latest/
Other
2.16k stars 335 forks source link

Flask restx multipart/form request with file and body documented properly with swagger #470

Closed mkorycinski closed 2 years ago

mkorycinski commented 2 years ago

Hi,

I am trying to implement an endpoint which will take both formData (a list of files, to be more precise) and a body as JSON. My code looks as follows:

Multiple file param in another module:

def authorization_param(ns: Namespace, parser: Optional[RequestParser] = None) -> RequestParser:
    if not parser:
        parser = ns.parser()
    parser.add_argument('Authorization', location='headers', required=False, default='Bearer ')
    return parser

def multiple_file_param(arg_name: str, ns: Namespace, parser: Optional[RequestParser] = None) -> RequestParser:
    if not parser:
        parser = ns.parser()
    parser.add_argument(arg_name, type=FileStorage, location='files', required=True, action='append')
    return parser

Model:

some_form_model = api.model('form', {'field': fields.String())

And the endpoint itself:

ns = Namespace('sth', description='Some stuff'))
auth_param = authorization_param(ns=ns)
file_param = multiple_file_param(arg_name='File', ns=ns)

@ns.route('/files')
@ns.expect(auth_param)
class PreprocessFiles(Resource):
    @ns.response(code=201, description='Job created', model=some_model)
    @ns.response(code=400, description='Bad request', model=None)
    @ns.response(code=401, description='Authentication Error', model=None)
    @ns.response(code=403, description='Forbidden', model=None)
    @ns.response(
        code=422,
        description='Input data validation Error',
        model=some_model
    )
    @ns.expect(some_form_model)
    @ns.expect(file_param)
    def post(self):
        payload = request.get_json()
        # do some stuff..
        return {'text': 'ok'}, 201

The endpoint is registered in an API object:

api.add_namespace(ns)

My problem is that in swagger I get either input body or file parameter, depending on the order of decorators I use. If I try to pass both form model and file param into one ns.expect as so:

@ns.expect(some_form_model, file_param)

I get the following error in the console, and the schema is not rendered:

2022-08-26 12:19:45.764 ERROR flask_restx.api api.__schema__: Unable to render schema
Traceback (most recent call last):
  File "D:\Project\venv\lib\site-packages\flask_restx\api.py", line 571, in __schema__
    self._schema = Swagger(self).as_dict()
  File "D:\Project\venv\lib\site-packages\flask_restx\swagger.py", line 239, in as_dict
    serialized = self.serialize_resource(
  File "D:\Project\venv\lib\site-packages\flask_restx\swagger.py", line 446, in serialize_resource
    path[method] = self.serialize_operation(doc, method)
  File "D:\Project\venv\lib\site-packages\flask_restx\swagger.py", line 469, in serialize_operation
    if any(p["type"] == "file" for p in all_params):
  File "D:\Project\venv\lib\site-packages\flask_restx\swagger.py", line 469, in <genexpr>
    if any(p["type"] == "file" for p in all_params):
KeyError: 'type'

Is there any way to go around this? I would really like to have good swagger docs for the front-end folks.

Thanks in advance!

Best, Mateusz

mkorycinski commented 2 years ago

I have described this as a bug #471 .