Permalink
Browse files

Added support for automagic OPTIONS

  • Loading branch information...
1 parent a532568 commit 5e1b1030e8edecb0c9652f2a406933c049ea2397 @mitsuhiko mitsuhiko committed Jul 12, 2010
Showing with 74 additions and 18 deletions.
  1. +3 −0 CHANGES
  2. +7 −1 docs/quickstart.rst
  3. +31 −6 flask/app.py
  4. +3 −2 flask/ctx.py
  5. +1 −0 flask/helpers.py
  6. +16 −5 flask/wrappers.py
  7. +13 −4 tests/flask_tests.py
View
@@ -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
-------------
View
@@ -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
View
@@ -464,22 +464,37 @@ def index():
.. 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
endpoint
: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 @@ def show_post(post_id):
: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 @@ def dispatch_request(self):
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)
View
@@ -38,8 +38,9 @@ def __init__(self, app, environ):
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
View
@@ -15,6 +15,7 @@
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.
View
@@ -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`.
@@ -41,6 +42,16 @@ class Request(RequestBase):
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"""
if self.endpoint and '.' in self.endpoint:
View
@@ -111,6 +111,15 @@ def other():
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,15 +133,15 @@ def more():
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
assert c.post('/more').data == 'POST'
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,15 +157,15 @@ def more():
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
assert c.post('/more').data == 'POST'
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__)

0 comments on commit 5e1b103

Please sign in to comment.