diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fd26aafb..b5d3f767 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,53 @@ Features: * Add ``force_all`` argument to ``use_args`` and ``use_kwargs`` (:issue:`252`, :issue:`307`). Thanks :user:`piroux` for reporting. +* The ``status_code`` and ``headers`` arguments to ``ValidationError`` + are deprecated. Pass ``error_status_code`` and ``error_headers`` to + `Parser.parse`, `Parser.use_args`, and `Parser.use_kwargs` instead. + (:issue:`327`, :issue:`336`). +* Custom error handlers receive ``error_status_code`` and ``error_headers`` arguments. + (:issue:`327`). + +.. code-block:: python + + # <4.2.0 + @parser.error_handler + def handle_error(error, req, schema): + raise CustomError(error.messages) + + + class MyParser(FlaskParser): + def handle_error(self, error, req, schema): + # ... + raise CustomError(error.messages) + + + # >=4.2.0 + @parser.error_handler + def handle_error(error, req, schema, status_code, headers): + raise CustomError(error.messages) + + + # OR + + + @parser.error_handler + def handle_error(error, **kwargs): + raise CustomError(error.messages) + + + class MyParser(FlaskParser): + def handle_error(self, error, req, schema, status_code, headers): + # ... + raise CustomError(error.messages) + + # OR + + def handle_error(self, error, req, **kwargs): + # ... + raise CustomError(error.messages) + +Legacy error handlers will be supported until version 5.0.0. 4.1.3 (2018-12-02) ****************** diff --git a/docs/quickstart.rst b/docs/quickstart.rst index aeb5b330..ac662cc6 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -200,7 +200,7 @@ Then decorate that function with :func:`Parser.error_handler 3: + self.error_callback( + error, req, schema, error_status_code, error_headers + ) + else: # Backwards compat with webargs<=4.2.0 + warnings.warn( + "Error handler functions should include error_status_code and " + "error_headers args, or include **kwargs in the signature", + DeprecationWarning, + ) + self.error_callback(error, req, schema) else: - self.handle_error(error, req, schema) + if len(get_func_args(self.handle_error)) > 3: + self.handle_error(error, req, schema, error_status_code, error_headers) + else: + warnings.warn( + "handle_error methods should include error_status_code and " + "error_headers args, or include **kwargs in the signature", + DeprecationWarning, + ) + self.handle_error(error, req, schema) def _validate_arguments(self, data, validators): for validator in validators: @@ -368,7 +429,16 @@ def _get_schema(self, argmap, req): ) return schema - def parse(self, argmap, req=None, locations=None, validate=None, force_all=False): + def parse( + self, + argmap, + req=None, + locations=None, + validate=None, + force_all=False, + error_status_code=None, + error_headers=None, + ): """Main request parsing method. :param argmap: Either a `marshmallow.Schema`, a `dict` @@ -381,6 +451,12 @@ def parse(self, argmap, req=None, locations=None, validate=None, force_all=False :param callable validate: Validation function or list of validation functions that receives the dictionary of parsed arguments. Validator either returns a boolean or raises a :exc:`ValidationError`. + :param bool force_all: If `True`, missing arguments will be replaced with + `missing `. + :param int error_status_code: Status code passed to error handler functions when + a `ValidationError` is raised. + :param dict error_headers: Headers passed to error handler functions when a + a `ValidationError` is raised. :return: A dictionary of parsed arguments """ @@ -395,7 +471,9 @@ def parse(self, argmap, req=None, locations=None, validate=None, force_all=False data = result.data if MARSHMALLOW_VERSION_INFO[0] < 3 else result self._validate_arguments(data, validators) except ma.exceptions.ValidationError as error: - self._on_validation_error(error, req, schema) + self._on_validation_error( + error, req, schema, error_status_code, error_headers + ) finally: self.clear_cache() if force_all: @@ -435,6 +513,8 @@ def use_args( as_kwargs=False, validate=None, force_all=None, + error_status_code=None, + error_headers=None, ): """Decorator that injects parsed arguments into a view function or method. @@ -482,6 +562,8 @@ def wrapper(*args, **kwargs): locations=locations, validate=validate, force_all=force_all_, + error_status_code=error_status_code, + error_headers=error_headers, ) if as_kwargs: kwargs.update(parsed_args) @@ -539,8 +621,9 @@ def decorator(func): def error_handler(self, func): """Decorator that registers a custom error handling function. The - function should received the raised error, request object, and the - `marshmallow.Schema` instance used to parse the request. Overrides + function should receive the raised error, request object, + `marshmallow.Schema` instance used to parse the request, error status code, + and headers to use for the error response. Overrides the parser's ``handle_error`` method. Example: :: @@ -555,7 +638,7 @@ class CustomError(Exception): @parser.error_handler - def handle_error(error, req, schema): + def handle_error(error, req, schema, status_code, headers): raise CustomError(error.messages) :param callable func: The error callback to register. @@ -601,7 +684,9 @@ def parse_files(self, req, name, arg): """ return missing - def handle_error(self, error, req, schema): + def handle_error( + self, error, req, schema, error_status_code=None, error_headers=None + ): """Called if an error occurs while parsing args. By default, just logs and raises ``error``. """ diff --git a/webargs/falconparser.py b/webargs/falconparser.py index 3a88a67c..705fcf1e 100644 --- a/webargs/falconparser.py +++ b/webargs/falconparser.py @@ -140,9 +140,9 @@ def parse_files(self, req, name, field): "Parsing files not yet supported by {0}".format(self.__class__.__name__) ) - def handle_error(self, error, req, schema): + def handle_error(self, error, req, schema, error_status_code, error_headers): """Handles errors during parsing.""" - status = status_map.get(error.status_code) + status = error_status_code or status_map.get(error.status_code) if status is None: raise LookupError("Status code {0} not supported".format(error.status_code)) raise HTTPError(status, errors=error.messages) diff --git a/webargs/flaskparser.py b/webargs/flaskparser.py index e1136e60..0405b441 100644 --- a/webargs/flaskparser.py +++ b/webargs/flaskparser.py @@ -93,11 +93,13 @@ def parse_files(self, req, name, field): """Pull a file from the request.""" return core.get_value(req.files, name, field) - def handle_error(self, error, req, schema): + def handle_error(self, error, req, schema, error_status_code, error_headers): """Handles errors during parsing. Aborts the current HTTP request and responds with a 422 error. """ - status_code = getattr(error, "status_code", self.DEFAULT_VALIDATION_STATUS) + status_code = error_status_code or getattr( + error, "status_code", self.DEFAULT_VALIDATION_STATUS + ) abort(status_code, exc=error, messages=error.messages, schema=schema) def get_default_request(self): diff --git a/webargs/pyramidparser.py b/webargs/pyramidparser.py index 98dbb6f8..556a9ff3 100644 --- a/webargs/pyramidparser.py +++ b/webargs/pyramidparser.py @@ -73,11 +73,11 @@ def parse_matchdict(self, req, name, field): """Pull a value from the request's `matchdict`.""" return core.get_value(req.matchdict, name, field) - def handle_error(self, error, req, schema): + def handle_error(self, error, req, schema, error_status_code, error_headers): """Handles errors during parsing. Aborts the current HTTP request and responds with a 400 error. """ - status_code = getattr(error, "status_code", 422) + status_code = error_status_code or getattr(error, "status_code", 422) raise exception_response(status_code, detail=text_type(error)) def use_args( diff --git a/webargs/tornadoparser.py b/webargs/tornadoparser.py index d74579a6..24a2fe89 100644 --- a/webargs/tornadoparser.py +++ b/webargs/tornadoparser.py @@ -113,11 +113,13 @@ def parse_files(self, req, name, field): """Pull a file from the request.""" return get_value(req.files, name, field) - def handle_error(self, error, req, schema): + def handle_error(self, error, req, schema, error_status_code, error_headers): """Handles errors during parsing. Raises a `tornado.web.HTTPError` with a 400 error. """ - status_code = getattr(error, "status_code", core.DEFAULT_VALIDATION_STATUS) + status_code = error_status_code or getattr( + error, "status_code", core.DEFAULT_VALIDATION_STATUS + ) if status_code == 422: reason = "Unprocessable Entity" else: