Browse Source

Added support for automagic OPTIONS

pull/112/head
Armin Ronacher 15 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 - after request functions are now called in reverse order of
registration. 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 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 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 are handled like the `HTTP RFC`_ (the document describing the HTTP
protocol) demands, so you can completely ignore that part of 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 You have no idea what an HTTP method is? Worry not, here quick
introduction in HTTP methods and why they matter: introduction in HTTP methods and why they matter:
@ -310,6 +311,11 @@ very common:
`DELETE` `DELETE`
Remove the information that the given location. 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 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 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 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 .. versionchanged:: 0.2
`view_func` parameter added. `view_func` parameter added.
.. versionchanged:: 0.6
`OPTIONS` is added automatically as method.
:param rule: the URL rule as string :param rule: the URL rule as string
:param endpoint: the endpoint for the registered URL rule. Flask :param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as 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 :param view_func: the function to call when serving a request to the
provided endpoint provided endpoint
:param options: the options to be forwarded to the underlying :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: if endpoint is None:
assert view_func is not None, 'expected view func if endpoint ' \ assert view_func is not None, 'expected view func if endpoint ' \
'is not provided.' 'is not provided.'
endpoint = view_func.__name__ endpoint = view_func.__name__
options['endpoint'] = endpoint options['endpoint'] = endpoint
options.setdefault('methods', ('GET',)) methods = options.pop('methods', ('GET',))
self.url_map.add(Rule(rule, **options)) 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: if view_func is not None:
self.view_functions[endpoint] = view_func self.view_functions[endpoint] = view_func
@ -539,8 +554,10 @@ class Flask(_PackageBoundObject):
:param rule: the URL rule as string :param rule: the URL rule as string
:param methods: a list of methods this rule should be limited :param methods: a list of methods this rule should be limited
to (``GET``, ``POST`` etc.). By default a rule to (`GET`, `POST` etc.). By default a rule
just listens for ``GET`` (and implicitly ``HEAD``). 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 :param subdomain: specifies the rule for the subdomain in case
subdomain matching is in use. subdomain matching is in use.
:param strict_slashes: can be used to disable the strict slashes :param strict_slashes: can be used to disable the strict slashes
@ -650,7 +667,15 @@ class Flask(_PackageBoundObject):
try: try:
if req.routing_exception is not None: if req.routing_exception is not None:
raise req.routing_exception 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: except HTTPException, e:
return self.handle_http_exception(e) return self.handle_http_exception(e)

5
flask/ctx.py

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

1
flask/helpers.py

@ -15,6 +15,7 @@ import posixpath
import mimetypes import mimetypes
from time import time from time import time
from zlib import adler32 from zlib import adler32
from functools import wraps
# try to load the best simplejson implementation available. If JSON # try to load the best simplejson implementation available. If JSON
# is not installed, we add a failing class. # 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. :attr:`~flask.Flask.request_class` to your subclass.
""" """
#: the endpoint that matched the request. This in combination with #: the internal URL rule that matched the request. This can be
#: :attr:`view_args` can be used to reconstruct the same or a #: useful to inspect which methods are allowed for the URL from
#: modified URL. If an exception happened when matching, this will #: a before/after handler (``request.url_rule.methods``) etc.
#: be `None`. #:
endpoint = None #: .. versionadded:: 0.6
url_rule = None
#: a dict of view arguments that matched the request. If an exception #: a dict of view arguments that matched the request. If an exception
#: happened when matching, this will be `None`. #: happened when matching, this will be `None`.
@ -40,6 +41,16 @@ class Request(RequestBase):
#: something similar. #: something similar.
routing_exception = None 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 @property
def module(self): def module(self):
"""The name of the current module""" """The name of the current module"""

17
tests/flask_tests.py

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

Loading…
Cancel
Save