-
Notifications
You must be signed in to change notification settings - Fork 73
/
arguments.py
147 lines (130 loc) · 6.08 KB
/
arguments.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""Arguments parsing"""
from collections import abc
from copy import deepcopy
from functools import wraps
import http
from webargs.flaskparser import FlaskParser
from .utils import deepupdate
from .spec import DEFAULT_REQUEST_BODY_CONTENT_TYPE
class ArgumentsMixin:
"""Extend Blueprint to add arguments parsing feature"""
ARGUMENTS_PARSER = FlaskParser()
def arguments(
self, schema, *, location='json', content_type=None, required=True,
description=None, example=None, examples=None, **kwargs
):
"""Decorator specifying the schema used to deserialize parameters
:param type|Schema schema: Marshmallow ``Schema`` class or instance
used to deserialize and validate the argument.
:param str location: Location of the argument.
:param str content_type: Content type of the argument.
Should only be used in conjunction with ``json``, ``form`` or
``files`` location.
The default value depends on the location and is set in
``Blueprint.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING``.
This is only used for documentation purpose.
:param bool required: Whether argument is required (default: True).
:param str description: Argument description.
:param dict example: Parameter example.
:param list examples: List of parameter examples.
:param dict kwargs: Keyword arguments passed to the webargs
:meth:`use_args <webargs.core.Parser.use_args>` decorator used
internally.
The `required` and `description` only affect `body` arguments
(OpenAPI 2) or `requestBody` (OpenAPI 3), because the docs expose the
whole schema. For other locations, the schema is turned into an array
of parameters and the required/description value of each parameter item
is taken from the corresponding field in the schema.
The `example` and `examples` parameters are mutually exclusive and
should only be used with OpenAPI 3 and when location is ``json``.
See :doc:`Arguments <arguments>`.
"""
# At this stage, put schema instance in doc dictionary. Il will be
# replaced later on by $ref or json.
parameters = {
'in': location,
'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):
@wraps(func)
def wrapper(*f_args, **f_kwargs):
return func(*f_args, **f_kwargs)
# 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 self.ARGUMENTS_PARSER.use_args(
schema, location=location, **kwargs)(wrapper)
return decorator
def _prepare_arguments_doc(self, doc, doc_info, *, spec, **kwargs):
# This callback should run first as it overrides existing parameters
# in doc. Following callbacks should append to parameters list.
operation = doc_info.get('arguments')
if operation:
parameters = [
p for p in operation['parameters']
if isinstance(p, abc.Mapping)
]
# OAS 2
if spec.openapi_version.major < 3:
for param in parameters:
if param['in'] in (
self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING
):
content_type = (
param.pop('content_type', None) or
self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING[
param['in']]
)
if content_type != DEFAULT_REQUEST_BODY_CONTENT_TYPE:
operation['consumes'] = [content_type, ]
# body and formData are mutually exclusive
break
# OAS 3
else:
for param in parameters:
if param['in'] in (
self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING
):
request_body = {
x: param[x]
for x in ('description', 'required')
if x in param
}
fields = {
x: param.pop(x)
for x in ('schema', 'example', 'examples')
if x in param
}
content_type = (
param.pop('content_type', None) or
self.DEFAULT_LOCATION_CONTENT_TYPE_MAPPING[
param['in']]
)
request_body['content'] = {content_type: fields}
operation['requestBody'] = request_body
# There can be only one requestBody
operation['parameters'].remove(param)
if not operation['parameters']:
del operation['parameters']
break
doc = deepupdate(doc, operation)
return doc