m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
14 KiB

6 months ago
  1. from copy import deepcopy
  2. try:
  3. from collections.abc import MutableSequence
  4. except ImportError:
  5. from collections import MutableSequence
  6. from flask import current_app, request
  7. from werkzeug.datastructures import MultiDict, FileStorage
  8. from werkzeug import exceptions
  9. import flask_restful
  10. import decimal
  11. import six
  12. class Namespace(dict):
  13. def __getattr__(self, name):
  14. try:
  15. return self[name]
  16. except KeyError:
  17. raise AttributeError(name)
  18. def __setattr__(self, name, value):
  19. self[name] = value
  20. _friendly_location = {
  21. u'json': u'the JSON body',
  22. u'form': u'the post body',
  23. u'args': u'the query string',
  24. u'values': u'the post body or the query string',
  25. u'headers': u'the HTTP headers',
  26. u'cookies': u'the request\'s cookies',
  27. u'files': u'an uploaded file',
  28. }
  29. text_type = lambda x: six.text_type(x)
  30. class Argument(object):
  31. """
  32. :param name: Either a name or a list of option strings, e.g. foo or
  33. -f, --foo.
  34. :param default: The value produced if the argument is absent from the
  35. request.
  36. :param dest: The name of the attribute to be added to the object
  37. returned by :meth:`~reqparse.RequestParser.parse_args()`.
  38. :param bool required: Whether or not the argument may be omitted (optionals
  39. only).
  40. :param action: The basic type of action to be taken when this argument
  41. is encountered in the request. Valid options are "store" and "append".
  42. :param ignore: Whether to ignore cases where the argument fails type
  43. conversion
  44. :param type: The type to which the request argument should be
  45. converted. If a type raises an exception, the message in the
  46. error will be returned in the response. Defaults to :class:`unicode`
  47. in python2 and :class:`str` in python3.
  48. :param location: The attributes of the :class:`flask.Request` object
  49. to source the arguments from (ex: headers, args, etc.), can be an
  50. iterator. The last item listed takes precedence in the result set.
  51. :param choices: A container of the allowable values for the argument.
  52. :param help: A brief description of the argument, returned in the
  53. response when the argument is invalid. May optionally contain
  54. an "{error_msg}" interpolation token, which will be replaced with
  55. the text of the error raised by the type converter.
  56. :param bool case_sensitive: Whether argument values in the request are
  57. case sensitive or not (this will convert all values to lowercase)
  58. :param bool store_missing: Whether the arguments default value should
  59. be stored if the argument is missing from the request.
  60. :param bool trim: If enabled, trims whitespace around the argument.
  61. :param bool nullable: If enabled, allows null value in argument.
  62. """
  63. def __init__(self, name, default=None, dest=None, required=False,
  64. ignore=False, type=text_type, location=('json', 'values',),
  65. choices=(), action='store', help=None, operators=('=',),
  66. case_sensitive=True, store_missing=True, trim=False,
  67. nullable=True):
  68. self.name = name
  69. self.default = default
  70. self.dest = dest
  71. self.required = required
  72. self.ignore = ignore
  73. self.location = location
  74. self.type = type
  75. self.choices = choices
  76. self.action = action
  77. self.help = help
  78. self.case_sensitive = case_sensitive
  79. self.operators = operators
  80. self.store_missing = store_missing
  81. self.trim = trim
  82. self.nullable = nullable
  83. def __str__(self):
  84. if len(self.choices) > 5:
  85. choices = self.choices[0:3]
  86. choices.append('...')
  87. choices.append(self.choices[-1])
  88. else:
  89. choices = self.choices
  90. return 'Name: {0}, type: {1}, choices: {2}'.format(self.name, self.type, choices)
  91. def __repr__(self):
  92. return "{0}('{1}', default={2}, dest={3}, required={4}, ignore={5}, location={6}, " \
  93. "type=\"{7}\", choices={8}, action='{9}', help={10}, case_sensitive={11}, " \
  94. "operators={12}, store_missing={13}, trim={14}, nullable={15})".format(
  95. self.__class__.__name__, self.name, self.default, self.dest, self.required, self.ignore, self.location,
  96. self.type, self.choices, self.action, self.help, self.case_sensitive,
  97. self.operators, self.store_missing, self.trim, self.nullable)
  98. def source(self, request):
  99. """Pulls values off the request in the provided location
  100. :param request: The flask request object to parse arguments from
  101. """
  102. if isinstance(self.location, six.string_types):
  103. value = getattr(request, self.location, MultiDict())
  104. if callable(value):
  105. value = value()
  106. if value is not None:
  107. return value
  108. else:
  109. values = MultiDict()
  110. for l in self.location:
  111. value = getattr(request, l, None)
  112. if callable(value):
  113. value = value()
  114. if value is not None:
  115. values.update(value)
  116. return values
  117. return MultiDict()
  118. def convert(self, value, op):
  119. # Don't cast None
  120. if value is None:
  121. if self.nullable:
  122. return None
  123. else:
  124. raise ValueError('Must not be null!')
  125. # and check if we're expecting a filestorage and haven't overridden `type`
  126. # (required because the below instantiation isn't valid for FileStorage)
  127. elif isinstance(value, FileStorage) and self.type == FileStorage:
  128. return value
  129. try:
  130. return self.type(value, self.name, op)
  131. except TypeError:
  132. try:
  133. if self.type is decimal.Decimal:
  134. return self.type(str(value))
  135. else:
  136. return self.type(value, self.name)
  137. except TypeError:
  138. return self.type(value)
  139. def handle_validation_error(self, error, bundle_errors):
  140. """Called when an error is raised while parsing. Aborts the request
  141. with a 400 status and an error message
  142. :param error: the error that was raised
  143. :param bundle_errors: do not abort when first error occurs, return a
  144. dict with the name of the argument and the error message to be
  145. bundled
  146. """
  147. error_str = six.text_type(error)
  148. error_msg = self.help.format(error_msg=error_str) if self.help else error_str
  149. msg = {self.name: error_msg}
  150. if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors:
  151. return error, msg
  152. flask_restful.abort(400, message=msg)
  153. def parse(self, request, bundle_errors=False):
  154. """Parses argument value(s) from the request, converting according to
  155. the argument's type.
  156. :param request: The flask request object to parse arguments from
  157. :param bundle_errors: Do not abort when first error occurs, return a
  158. dict with the name of the argument and the error message to be
  159. bundled
  160. """
  161. source = self.source(request)
  162. results = []
  163. # Sentinels
  164. _not_found = False
  165. _found = True
  166. for operator in self.operators:
  167. name = self.name + operator.replace("=", "", 1)
  168. if name in source:
  169. # Account for MultiDict and regular dict
  170. if hasattr(source, "getlist"):
  171. values = source.getlist(name)
  172. else:
  173. values = source.get(name)
  174. if not (isinstance(values, MutableSequence) and self.action == 'append'):
  175. values = [values]
  176. for value in values:
  177. if hasattr(value, "strip") and self.trim:
  178. value = value.strip()
  179. if hasattr(value, "lower") and not self.case_sensitive:
  180. value = value.lower()
  181. if hasattr(self.choices, "__iter__"):
  182. self.choices = [choice.lower()
  183. for choice in self.choices]
  184. try:
  185. value = self.convert(value, operator)
  186. except Exception as error:
  187. if self.ignore:
  188. continue
  189. return self.handle_validation_error(error, bundle_errors)
  190. if self.choices and value not in self.choices:
  191. if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors:
  192. return self.handle_validation_error(
  193. ValueError(u"{0} is not a valid choice".format(
  194. value)), bundle_errors)
  195. self.handle_validation_error(
  196. ValueError(u"{0} is not a valid choice".format(
  197. value)), bundle_errors)
  198. if name in request.unparsed_arguments:
  199. request.unparsed_arguments.pop(name)
  200. results.append(value)
  201. if not results and self.required:
  202. if isinstance(self.location, six.string_types):
  203. error_msg = u"Missing required parameter in {0}".format(
  204. _friendly_location.get(self.location, self.location)
  205. )
  206. else:
  207. friendly_locations = [_friendly_location.get(loc, loc)
  208. for loc in self.location]
  209. error_msg = u"Missing required parameter in {0}".format(
  210. ' or '.join(friendly_locations)
  211. )
  212. if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors:
  213. return self.handle_validation_error(ValueError(error_msg), bundle_errors)
  214. self.handle_validation_error(ValueError(error_msg), bundle_errors)
  215. if not results:
  216. if callable(self.default):
  217. return self.default(), _not_found
  218. else:
  219. return self.default, _not_found
  220. if self.action == 'append':
  221. return results, _found
  222. if self.action == 'store' or len(results) == 1:
  223. return results[0], _found
  224. return results, _found
  225. class RequestParser(object):
  226. """Enables adding and parsing of multiple arguments in the context of a
  227. single request. Ex::
  228. from flask_restful import reqparse
  229. parser = reqparse.RequestParser()
  230. parser.add_argument('foo')
  231. parser.add_argument('int_bar', type=int)
  232. args = parser.parse_args()
  233. :param bool trim: If enabled, trims whitespace on all arguments in this
  234. parser
  235. :param bool bundle_errors: If enabled, do not abort when first error occurs,
  236. return a dict with the name of the argument and the error message to be
  237. bundled and return all validation errors
  238. """
  239. def __init__(self, argument_class=Argument, namespace_class=Namespace,
  240. trim=False, bundle_errors=False):
  241. self.args = []
  242. self.argument_class = argument_class
  243. self.namespace_class = namespace_class
  244. self.trim = trim
  245. self.bundle_errors = bundle_errors
  246. def add_argument(self, *args, **kwargs):
  247. """Adds an argument to be parsed.
  248. Accepts either a single instance of Argument or arguments to be passed
  249. into :class:`Argument`'s constructor.
  250. See :class:`Argument`'s constructor for documentation on the
  251. available options.
  252. """
  253. if len(args) == 1 and isinstance(args[0], self.argument_class):
  254. self.args.append(args[0])
  255. else:
  256. self.args.append(self.argument_class(*args, **kwargs))
  257. # Do not know what other argument classes are out there
  258. if self.trim and self.argument_class is Argument:
  259. # enable trim for appended element
  260. self.args[-1].trim = kwargs.get('trim', self.trim)
  261. return self
  262. def parse_args(self, req=None, strict=False, http_error_code=400):
  263. """Parse all arguments from the provided request and return the results
  264. as a Namespace
  265. :param req: Can be used to overwrite request from Flask
  266. :param strict: if req includes args not in parser, throw 400 BadRequest exception
  267. :param http_error_code: use custom error code for `flask_restful.abort()`
  268. """
  269. if req is None:
  270. req = request
  271. namespace = self.namespace_class()
  272. # A record of arguments not yet parsed; as each is found
  273. # among self.args, it will be popped out
  274. req.unparsed_arguments = dict(self.argument_class('').source(req)) if strict else {}
  275. errors = {}
  276. for arg in self.args:
  277. value, found = arg.parse(req, self.bundle_errors)
  278. if isinstance(value, ValueError):
  279. errors.update(found)
  280. found = None
  281. if found or arg.store_missing:
  282. namespace[arg.dest or arg.name] = value
  283. if errors:
  284. flask_restful.abort(http_error_code, message=errors)
  285. if strict and req.unparsed_arguments:
  286. raise exceptions.BadRequest('Unknown arguments: %s'
  287. % ', '.join(req.unparsed_arguments.keys()))
  288. return namespace
  289. def copy(self):
  290. """ Creates a copy of this RequestParser with the same set of arguments """
  291. parser_copy = self.__class__(self.argument_class, self.namespace_class)
  292. parser_copy.args = deepcopy(self.args)
  293. parser_copy.trim = self.trim
  294. parser_copy.bundle_errors = self.bundle_errors
  295. return parser_copy
  296. def replace_argument(self, name, *args, **kwargs):
  297. """ Replace the argument matching the given name with a new version. """
  298. new_arg = self.argument_class(name, *args, **kwargs)
  299. for index, arg in enumerate(self.args[:]):
  300. if new_arg.name == arg.name:
  301. del self.args[index]
  302. self.args.append(new_arg)
  303. break
  304. return self
  305. def remove_argument(self, name):
  306. """ Remove the argument matching the given name. """
  307. for index, arg in enumerate(self.args[:]):
  308. if name == arg.name:
  309. del self.args[index]
  310. break
  311. return self