Browse Source

Added support for automagic OPTIONS

pull/112/head
Armin Ronacher 14 years ago
parent
commit
5e1b1030e8
  1. 3
      CHANGES
  2. 8
      docs/quickstart.rst
  3. 37
      flask/app.py
  4. 5
      flask/ctx.py
  5. 1
      flask/helpers.py
  6. 21
      flask/wrappers.py
  7. 17
      tests/flask_tests.py

3
CHANGES

@ -10,6 +10,9 @@ Release date to be announced, codename to be decided.
- after request functions are now called in reverse order of
registration.
- OPTIONS is now automatically implemented by Flask unless the
application explictly adds 'OPTIONS' as method to the URL rule.
In this case no automatic OPTIONS handling kicks in.
Version 0.5.1
-------------

8
docs/quickstart.rst

@ -269,7 +269,8 @@ If `GET` is present, `HEAD` will be added automatically for you. You
don't have to deal with that. It will also make sure that `HEAD` requests
are handled like the `HTTP RFC`_ (the document describing the HTTP
protocol) demands, so you can completely ignore that part of the HTTP
specification.
specification. Likewise as of Flask 0.6, `OPTIONS` is implemented for you
as well automatically.
You have no idea what an HTTP method is? Worry not, here quick
introduction in HTTP methods and why they matter:
@ -310,6 +311,11 @@ very common:
`DELETE`
Remove the information that the given location.
`OPTIONS`
Provides a quick way for a requesting client to figure out which
methods are supported by this URL. Starting with Flask 0.6, this
is implemented for you automatically.
Now the interesting part is that in HTML4 and XHTML1, the only methods a
form might submit to the server are `GET` and `POST`. But with JavaScript
and future HTML standards you can use other methods as well. Furthermore

37
flask/app.py

@ -464,6 +464,9 @@ class Flask(_PackageBoundObject):
.. versionchanged:: 0.2
`view_func` parameter added.
.. versionchanged:: 0.6
`OPTIONS` is added automatically as method.
:param rule: the URL rule as string
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
@ -471,15 +474,27 @@ class Flask(_PackageBoundObject):
:param view_func: the function to call when serving a request to the
provided endpoint
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
is a list of methods this rule should be limited
to (`GET`, `POST` etc.). By default a rule
just listens for `GET` (and implicitly `HEAD`).
Starting with Flask 0.6, `OPTIONS` is implicitly
added and handled by the standard request handling.
"""
if endpoint is None:
assert view_func is not None, 'expected view func if endpoint ' \
'is not provided.'
endpoint = view_func.__name__
options['endpoint'] = endpoint
options.setdefault('methods', ('GET',))
self.url_map.add(Rule(rule, **options))
methods = options.pop('methods', ('GET',))
provide_automatic_options = False
if 'OPTIONS' not in methods:
methods = tuple(methods) + ('OPTIONS',)
provide_automatic_options = True
rule = Rule(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
if view_func is not None:
self.view_functions[endpoint] = view_func
@ -539,8 +554,10 @@ class Flask(_PackageBoundObject):
:param rule: the URL rule as string
:param methods: a list of methods this rule should be limited
to (``GET``, ``POST`` etc.). By default a rule
just listens for ``GET`` (and implicitly ``HEAD``).
to (`GET`, `POST` etc.). By default a rule
just listens for `GET` (and implicitly `HEAD`).
Starting with Flask 0.6, `OPTIONS` is implicitly
added and handled by the standard request handling.
:param subdomain: specifies the rule for the subdomain in case
subdomain matching is in use.
:param strict_slashes: can be used to disable the strict slashes
@ -650,7 +667,15 @@ class Flask(_PackageBoundObject):
try:
if req.routing_exception is not None:
raise req.routing_exception
return self.view_functions[req.endpoint](**req.view_args)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if rule.provide_automatic_options and req.method == 'OPTIONS':
rv = self.response_class()
rv.allow.update(rule.methods)
return rv
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)
except HTTPException, e:
return self.handle_http_exception(e)

5
flask/ctx.py

@ -38,8 +38,9 @@ class _RequestContext(object):
self.flashes = None
try:
self.request.endpoint, self.request.view_args = \
self.url_adapter.match()
url_rule, self.request.view_args = \
self.url_adapter.match(return_rule=True)
self.request.url_rule = url_rule
except HTTPException, e:
self.request.routing_exception = e

1
flask/helpers.py

@ -15,6 +15,7 @@ import posixpath
import mimetypes
from time import time
from zlib import adler32
from functools import wraps
# try to load the best simplejson implementation available. If JSON
# is not installed, we add a failing class.

21
flask/wrappers.py

@ -24,11 +24,12 @@ class Request(RequestBase):
:attr:`~flask.Flask.request_class` to your subclass.
"""
#: the endpoint that matched the request. This in combination with
#: :attr:`view_args` can be used to reconstruct the same or a
#: modified URL. If an exception happened when matching, this will
#: be `None`.
endpoint = None
#: the internal URL rule that matched the request. This can be
#: useful to inspect which methods are allowed for the URL from
#: a before/after handler (``request.url_rule.methods``) etc.
#:
#: .. versionadded:: 0.6
url_rule = None
#: a dict of view arguments that matched the request. If an exception
#: happened when matching, this will be `None`.
@ -40,6 +41,16 @@ class Request(RequestBase):
#: something similar.
routing_exception = None
@property
def endpoint(self):
"""The endpoint that matched the request. This in combination with
:attr:`view_args` can be used to reconstruct the same or a
modified URL. If an exception happened when matching, this will
be `None`.
"""
if self.url_rule is not None:
return self.url_rule.endpoint
@property
def module(self):
"""The name of the current module"""

17
tests/flask_tests.py

@ -111,6 +111,15 @@ class ContextTestCase(unittest.TestCase):
class BasicFunctionalityTestCase(unittest.TestCase):
def test_options_work(self):
app = flask.Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
return 'Hello World'
rv = app.test_client().open('/', method='OPTIONS')
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
assert rv.data == ''
def test_request_dispatching(self):
app = flask.Flask(__name__)
@app.route('/')
@ -124,7 +133,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert c.get('/').data == 'GET'
rv = c.post('/')
assert rv.status_code == 405
assert sorted(rv.allow) == ['GET', 'HEAD']
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
rv = c.head('/')
assert rv.status_code == 200
assert not rv.data # head truncates
@ -132,7 +141,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert c.get('/more').data == 'GET'
rv = c.delete('/more')
assert rv.status_code == 405
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
def test_url_mapping(self):
app = flask.Flask(__name__)
@ -148,7 +157,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert c.get('/').data == 'GET'
rv = c.post('/')
assert rv.status_code == 405
assert sorted(rv.allow) == ['GET', 'HEAD']
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
rv = c.head('/')
assert rv.status_code == 200
assert not rv.data # head truncates
@ -156,7 +165,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert c.get('/more').data == 'GET'
rv = c.delete('/more')
assert rv.status_code == 405
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
def test_session(self):
app = flask.Flask(__name__)

Loading…
Cancel
Save