Skip to content

Commit

Permalink
Use HTTPException.description as error message
Browse files Browse the repository at this point in the history
This is basically flask-restful#438 with conflicts resolved and some cleanup

fixes: flask-restful#205, flask-restful#438
  • Loading branch information
joshfriend committed Jun 5, 2015
2 parents 5e72a3f + 2727898 commit 1ccec7d
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 152 deletions.
15 changes: 14 additions & 1 deletion AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ A huge thanks to all of our contributors:


- Alex Gaynor
- Alex M
- Alex Morken
- Andrew Dunham
- Andriy Yurchuk
- Anil Kulkarni
- Antonio Dourado
- Antonio Herraiz
- Artur Rodrigues
Expand All @@ -26,7 +29,6 @@ A huge thanks to all of our contributors:
- Doug Black
- Evan Dale Aromin
- Frank Stratton
- Frank Stratton ☺
- Garret Raziel
- Gary Belvin
- Gilles Dartiguelongue
Expand All @@ -37,9 +39,12 @@ A huge thanks to all of our contributors:
- James Ogura
- Joakim Ekberg
- Johannes
- Jordan Yelloz
- Josh Friend
- Joshua C. Randall
- Joshua Randall
- José Fernández Ramos
- Juan Rossi
- JuneHyeon Bae
- Kamil Gałuszka
- Kevin Burke
Expand All @@ -48,9 +53,12 @@ A huge thanks to all of our contributors:
- Kyle Conroy
- Lance Ingle
- Lars Holm Nielsen
- Luiz Armesto
- Marek Hlobil
- Matt Wright
- Max Peterson
- Maxim
- Michael Hwang
- Miguel Grinberg
- Mihai Tomescu
- Pavel Tyslyatsky
Expand All @@ -61,14 +69,19 @@ A huge thanks to all of our contributors:
- Robert Warner
- Ryan Horn
- Sam Kimbrel
- Sander Sink
- Sasha Baranov
- Saul Diez-Guerra
- Sven-Hendrik Haase
- Usman Ehtesham Gul
- Victor Neo
- Vladimir Pal
- WooParadog
- Yaniv Aknin
- bret barker
- k-funk
- kelvinhammond
- kenjones
- lyschoening
- mailto1587
- mniebla
Expand Down
25 changes: 25 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@ Flask-RESTful Changelog

Here you can see the full list of changes between each Flask-RESTful release.


Version 0.3.3
-------------

Released May 22, 2015

- Disable [challenge on 401](https://github.com/flask-restful/flask-restful/commit/fe53f49bdc28dd83ee3acbeb0a313b411411e377)
by default (**THIS IS A BREAKING CHANGE**, albeit a very small one with behavior that probably no one depended upon. You can easily change this back to the old way).
- Doc fixes ([#404](https://github.com/flask-restful/flask-restful/pull/404), [#406](https://github.com/flask-restful/flask-restful/pull/406), [#436](https://github.com/flask-restful/flask-restful/pull/436), misc. other commits)
- Fix truncation of microseconds in iso8601 datetime output ([#368](https://github.com/flask-restful/flask-restful/pull/405))
- `null` arguments from JSON no longer cast to string ([#390](https://github.com/flask-restful/flask-restful/pull/390))
- Made list fields work with classes ([#409](https://github.com/flask-restful/flask-restful/pull/409))
- Fix `url_for()` when used with Blueprints ([#410](https://github.com/flask-restful/flask-restful/pull/410))
- Add CORS "Access-Control-Expose-Headers" support ([#412](https://github.com/flask-restful/flask-restful/pull/412))
- Fix class references in RequestParser ([#414](https://github.com/flask-restful/flask-restful/pull/414))
- Allow any callables to be used as lazy attributes ([#417](https://github.com/flask-restful/flask-restful/pull/417))
- Fix references to `flask.ext.*` ([#420](https://github.com/flask-restful/flask-restful/issues/420))
- Trim support with fixes ([#428](https://github.com/flask-restful/flask-restful/pull/428))
- Added ability to pass-in parameters into Resource constructors ([#444](https://github.com/flask-restful/flask-restful/pull/444))
- Fix custom type docs on "Intermediate usage" and docstring ([#434](https://github.com/flask-restful/flask-restful/pull/434))
- Fixed problem with `RequestParser.copy` ([#435](https://github.com/flask-restful/flask-restful/pull/435))
- Feature/error bundling ([#431](https://github.com/flask-restful/flask-restful/pull/431))
- Explicitly check the class type for `propagate_exceptions` ([#445](https://github.com/flask-restful/flask-restful/pull/445))
- Remove min. year limit 1900 in `inputs.date` ([#446](https://github.com/flask-restful/flask-restful/pull/446))

Version 0.3.2
-------------

Expand Down
30 changes: 30 additions & 0 deletions docs/intermediate-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,33 @@ Finally, we used the :class:`fields.Url` field type. ::
It takes as its first parameter the name of the endpoint associated with the
urls of the objects in the ``links`` sub-object. Passing ``absolute=True``
ensures that the generated urls will have the hostname included.


Passing Constructor Parameters Into Resources
---------------------------------------------
Your :class:`Resource` implementation may require outside dependencies. Those
dependencies are best passed-in through the constructor to loosely couple each
other. The :meth:`Api.add_resource` method has two keyword arguments:
``resource_class_args`` and ``resource_class_kwargs``. Their values will be forwarded
and passed into your Resource implementation's constructor.

So you could have a :class:`Resource`: ::

from flask_restful import Resource

class TodoNext(Resource):
def __init__(**kwargs):
# smart_engine is a black box dependency
self.smart_engine = kwargs['smart_engine']

def get(self):
return self.smart_engine.next_todo()

You can inject the required dependency into TodoNext like so: ::

smart_engine = SmartEngine()

api.add_resource(TodoNext, '/next',
resource_class_kwargs={ 'smart_engine': smart_engine })

Same idea applies for forwarding `args`.
2 changes: 1 addition & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ Using the :class:`reqparse` module also gives you sane error messages for
free. If an argument fails to pass validation, Flask-RESTful will respond with
a 400 Bad Request and a response highlighting the error. ::

$ curl -d 'rate=foo' http://127.0.0.1:5000/
$ curl -d 'rate=foo' http://127.0.0.1:5000/todos
{'status': 400, 'message': 'foo cannot be converted to int'}


Expand Down
49 changes: 49 additions & 0 deletions docs/reqparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,52 @@ with :meth:`~reqparse.RequestParser.remove_argument`. For example: ::

parser_copy.remove_argument('foo')
# parser_copy no longer has 'foo' argument

Error Handling
--------------

The default way errors are handled by the RequestParser is to abort on the
first error that occurred. This can be beneficial when you have arguments that
might take some time to process. However, often it is nice to have the errors
bundled together and sent back to the client all at once. This behavior can be
specified either at the Flask application level or on the specific RequestParser
instance. To invoke a RequestParser with the bundling errors option, pass in the
argument ``bundle_errors``. For example ::

from flask_restful import RequestParser

parser = RequestParser(bundle_errors=True)
parser.add_argument('foo', type=int, required=True)
parser.add_argument('bar', type=int, required=True)

# If a request comes in not containing both 'foo' and 'bar', the error that
# will come back will look something like this.

{
"message": {
"foo": "foo error message",
"bar": "bar error message"
}
}

# The default behavior would only return the first error

parser = RequestParser()
parser.add_argument('foo', type=int, required=True)
parser.add_argument('bar', type=int, required=True)

{
"message": {
"foo": "foo error message"
}
}

The application configuration key is "BUNDLE_ERRORS". For example ::

from flask import Flask

app = Flask(__name__)
app.config['BUNDLE_ERRRORS'] = True

Note: If ``BUNDLE_ERRORS`` is set on the application, setting ``bundle_errors``
to ``False`` in the RequestParser keyword argument will not work.
52 changes: 37 additions & 15 deletions flask_restful/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class Api(object):
:type decorators: list
:param catch_all_404s: Use :meth:`handle_error`
to handle 404 errors throughout your app
:param serve_challenge_on_401: Whether to serve a challenge response to
clients on receiving 401. This usually leads to a username/password
popup in web browers.
:param url_part_order: A string that controls the order that the pieces
of the url are concatenated when the full url is constructed. 'b'
is the blueprint (or blueprint registration) prefix, 'a' is the api
Expand All @@ -70,13 +73,15 @@ class Api(object):

def __init__(self, app=None, prefix='',
default_mediatype='application/json', decorators=None,
catch_all_404s=False, url_part_order='bae', errors=None):
catch_all_404s=False, serve_challenge_on_401=False,
url_part_order='bae', errors=None):
self.representations = dict(DEFAULT_REPRESENTATIONS)
self.urls = {}
self.prefix = prefix
self.default_mediatype = default_mediatype
self.decorators = decorators if decorators else []
self.catch_all_404s = catch_all_404s
self.serve_challenge_on_401 = serve_challenge_on_401
self.url_part_order = url_part_order
self.errors = errors or {}
self.blueprint_setup = None
Expand Down Expand Up @@ -274,22 +279,28 @@ def handle_error(self, e):
"""
got_request_exception.send(current_app._get_current_object(), exception=e)

if not hasattr(e, 'code') and current_app.propagate_exceptions:
if not isinstance(e, HTTPException) and current_app.propagate_exceptions:
exc_type, exc_value, tb = sys.exc_info()
if exc_value is e:
raise
else:
raise e
code = getattr(e, 'code', 500)
data = getattr(e,
'data',
{'message': getattr(e, 'description', http_status_message(code))}
)

if isinstance(e, HTTPException):
code = e.code
default_data = {
'message': getattr(e, 'description', http_status_message(code))
}
else:
code = 500
default_data = {
'message': http_status_message(code),
}

data = getattr(e, 'data', default_data)
headers = {}

if code >= 500:

# There's currently a bug in Python3 that disallows calling
# logging.exception() when an exception hasn't actually be raised
if sys.exc_info() == (None, None, None):
Expand All @@ -298,15 +309,14 @@ def handle_error(self, e):
current_app.logger.exception("Internal Error")

help_on_404 = current_app.config.get("ERROR_404_HELP", True)
if code == 404 and help_on_404 and ('message' not in data or
data['message'] == HTTP_STATUS_CODES[404]):
if code == 404 and help_on_404:
rules = dict([(re.sub('(<.*>)', '', rule.rule), rule.rule)
for rule in current_app.url_map.iter_rules()])
close_matches = difflib.get_close_matches(request.path, rules.keys())
if close_matches:
# If we already have a message, add punctuation and continue it.
if "message" in data:
data["message"] += ". "
data["message"] = data["message"].rstrip('.') + '. '
else:
data["message"] = ""

Expand Down Expand Up @@ -364,6 +374,14 @@ def add_resource(self, resource, *urls, **kwargs):
Can be used to reference this route in :class:`fields.Url` fields
:type endpoint: str
:param resource_class_args: args to be forwarded to the constructor of
the resource.
:type resource_class_args: tuple
:param resource_class_kwargs: kwargs to be forwarded to the constructor
of the resource.
:type resource_class_kwargs: dict
Additional keyword arguments not specified above will be passed as-is
to :meth:`flask.Flask.add_url_rule`.
Expand Down Expand Up @@ -402,6 +420,8 @@ def decorator(cls):
def _register_view(self, app, resource, *urls, **kwargs):
endpoint = kwargs.pop('endpoint', None) or resource.__name__.lower()
self.endpoints.add(endpoint)
resource_class_args = kwargs.pop('resource_class_args', ())
resource_class_kwargs = kwargs.pop('resource_class_kwargs', {})

if endpoint in app.view_functions.keys():
previous_view_class = app.view_functions[endpoint].__dict__['view_class']
Expand All @@ -412,7 +432,8 @@ def _register_view(self, app, resource, *urls, **kwargs):

resource.mediatypes = self.mediatypes_method() # Hacky
resource.endpoint = endpoint
resource_func = self.output(resource.as_view(endpoint))
resource_func = self.output(resource.as_view(endpoint, *resource_class_args,
**resource_class_kwargs))

for decorator in self.decorators:
resource_func = decorator(resource_func)
Expand Down Expand Up @@ -524,10 +545,11 @@ def wrapper(func):
def unauthorized(self, response):
""" Given a response, change it to ask for credentials """

realm = current_app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful")
challenge = u"{0} realm=\"{1}\"".format("Basic", realm)
if self.serve_challenge_on_401:
realm = current_app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful")
challenge = u"{0} realm=\"{1}\"".format("Basic", realm)

response.headers['WWW-Authenticate'] = challenge
response.headers['WWW-Authenticate'] = challenge
return response


Expand Down
22 changes: 13 additions & 9 deletions flask_restful/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ def iso8601interval(value, argument='argument'):
def date(value):
"""Parse a valid looking date in the format YYYY-mm-dd"""
date = datetime.strptime(value, "%Y-%m-%d")
if date.year < 1900:
raise ValueError(u"Year must be >= 1900")
return date


Expand Down Expand Up @@ -217,15 +215,21 @@ def positive(value, argument='argument'):
return value


def int_range(low, high, value, argument='argument'):
class int_range(object):
""" Restrict input to an integer in a range (inclusive) """
value = _get_integer(value)
if value < low or value > high:
error = ('Invalid {arg}: {val}. {arg} must be within the range {lo} - {hi}'
.format(arg=argument, val=value, lo=low, hi=high))
raise ValueError(error)
def __init__(self, low, high, argument='argument'):
self.low = low
self.high = high
self.argument = argument

return value
def __call__(self, value):
value = _get_integer(value)
if value < self.low or value > self.high:
error = ('Invalid {arg}: {val}. {arg} must be within the range {lo} - {hi}'
.format(arg=self.argument, val=value, lo=self.low, hi=self.high))
raise ValueError(error)

return value


def boolean(value):
Expand Down
Loading

0 comments on commit 1ccec7d

Please sign in to comment.