Skip to content

Commit

Permalink
Merge pull request #77 from wcdolphin/patch/options
Browse files Browse the repository at this point in the history
Patch/options
  • Loading branch information
Cory Dolphin committed Sep 20, 2014
2 parents 3656a4e + 882c2b9 commit c586962
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 61 deletions.
2 changes: 1 addition & 1 deletion _version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.8.1'
__version__ = '1.9.0'
15 changes: 5 additions & 10 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Flask-CORS
==========

|Build Status| |Latest Version| |Downloads| |Supported Python versions|
|License|

A Flask extension for handling Cross Origin Resource Sharing (CORS),
making cross-origin AJAX possible.

Expand All @@ -11,14 +14,6 @@ Questions, comments or improvements? Please create an issue on
`Github <https://github.com/wcdolphin/flask-cors>`__, tweet at
`@wcdolphin <https://twitter.com/wcdolphin>`__ or send me an email.

Flask-CORS
==========

|Build Status| |Latest Version| |Downloads| |Supported Python versions|
|License|

A Flask extension for handling Cross Origin Resource Sharing (CORS),
making cross-origin AJAX possible.

Installation
------------
Expand Down Expand Up @@ -107,6 +102,8 @@ e.g.
Full description of options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: flask_cors.CORS

.. autofunction:: flask_cors.cross_origin


Expand Down Expand Up @@ -149,5 +146,3 @@ a particular view.
:target: https://pypi.python.org/pypi/Flask-Cors/
.. |License| image:: https://pypip.in/license/Flask-Cors/badge.svg
:target: https://pypi.python.org/pypi/Flask-Cors/


6 changes: 3 additions & 3 deletions examples/app_based_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@

cors = CORS(app)

## Equivalent to (but using both is not advised)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}},
headers="Content-Type")
## Equivalent to (but do not use both)
# cors = CORS(app, resources={r"/api/*": {"origins": "*"}},
# headers="Content-Type")


@app.route("/")
Expand Down
120 changes: 73 additions & 47 deletions flask_cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,43 +168,47 @@ def wrapped_function(*args, **kwargs):


class CORS(object):
def __init__(self, app=None, **kwargs):
'''
Initializes Cross Origin Resource sharing for the application. The
arguments are identical to :py:func:`cross_origin`, with the
addition of a `resources` parameter. The resources parameter
defines a series of regular expressions for resource paths to match
and optionally, the associated :py:func:`cross_origin` options
to be applied to the particular resource.
The settings for CORS are determined in the following order:
Resource level settings (e.g when passed as a dictionary)
Keyword argument settings
App level configuration settings (e.g. CORS_*)
Default settings
Note: as it is possible for multiple regular expressions to match a
resource path, the regular expressions are first sorted by length,
from longest to shortest, in order to attempt to match the most
specific regular expression. This allows the definition of a
number of specific resource options, with a wildcard fallback
for all other resources.
:param resources: the series of regular expression and (optionally)
associated CORS options to be applied to the given resource path.
If the argument is a dictionary, it is expected to be of the form:
regexp : dict_of_options
If the argument is a list, it is expected to be a list of regular
expressions, for which the app-wide configured options are applied.
If the argument is a string, it is expected to be a regular
expression for which the app-wide configured options are applied.
:type resources: dict, iterable or string
'''
'''
Initializes Cross Origin Resource sharing for the application. The
arguments are identical to :py:func:`cross_origin`, with the
addition of a `resources` parameter. The resources parameter
defines a series of regular expressions for resource paths to match
and optionally, the associated options
to be applied to the particular resource. These options are
identical to the arguments to :py:func:`cross_origin`.
The settings for CORS are determined in the following order:
Resource level settings (e.g when passed as a dictionary)
Keyword argument settings
App level configuration settings (e.g. CORS_*)
Default settings
Note: as it is possible for multiple regular expressions to match a
resource path, the regular expressions are first sorted by length,
from longest to shortest, in order to attempt to match the most
specific regular expression. This allows the definition of a
number of specific resource options, with a wildcard fallback
for all other resources.
:param resources: the series of regular expression and (optionally)
associated CORS options to be applied to the given resource path.
If the argument is a dictionary, it is expected to be of the form:
regexp : dict_of_options
If the argument is a list, it is expected to be a list of regular
expressions, for which the app-wide configured options are applied.
If the argument is a string, it is expected to be a regular
expression for which the app-wide configured options are applied.
Default :'*'
:type resources: dict, iterable or string
'''

def __init__(self, app=None, **kwargs):
if app is not None:
self.init_app(app, **kwargs)

Expand All @@ -216,21 +220,24 @@ def init_app(self, app, **kwargs):

_kwarg_resources = kwargs.get('resources')
_app_resources = app.config.get('CORS_RESOURCES')
_resources = _kwarg_resources or _app_resources or [r'/*']

# To make the API more consistent with the decorator, allow a resource
# of '*', which is not actually a valid regexp.
_resources = r'.*' if _resources == '*' else _resources
_resources = _kwarg_resources or _app_resources or r'*'

if isinstance(_resources, dict): # sort the regexps by length
resources = sorted(_resources.items(),
key=lambda r: len(r[0]),
reverse=True
)
# To make the API more consistent with the decorator, allow a
# resource of '*', which is not actually a valid regexp.
_resources = map(lambda x: (_re_fix(x[0]), x[1]),
_resources.items())

# Sort by regex length to provide consistency of matching and
# to provide a proxy for specificity of match. E.G. longer
# regular expressions are tried first.
resources = sorted(_resources, key=lambda r: len(r[0]),
reverse=True)

elif isinstance(_resources, string_types):
resources = [(_resources, {})]
resources = [(_re_fix(_resources), {})]
elif isinstance(_resources, collections.Iterable):
resources = [(r, {}) for r in _resources]
resources = [(_re_fix(r), {}) for r in _resources]
else:
raise ValueError("Unexpected value for resources argument.")

Expand Down Expand Up @@ -328,13 +335,25 @@ def _set_cors_headers(resp, options):


def _get_app_kwarg_dict(app=current_app):
'''
Returns the dictionary of CORS specific app configurations.
'''
return dict([
(k.lower().replace('cors_', ''), app.config.get(k))
for k in CONFIG_OPTIONS
if app.config.get(k) is not None
])


def _re_fix(reg):
'''
Replace the invalid regex r'*' with the valid, wildcard regex r'/.*' to
enable the CORS app extension to have a more consistent api with the
decorator.
'''
return r'/.*' if reg == r'*' else reg


def _try_match(pattern, request_origin):
'''
Safely attempts to match a pattern or string to a request origin.
Expand All @@ -348,6 +367,13 @@ def _try_match(pattern, request_origin):


def _flexible_str(obj):
'''
A more flexible str function which intelligently handles
stringifying iterables. The results are lexographically
sorted to ensure generated responses are consistent when
iterables such as Set are used (whose order is usually platform
dependent)
'''
if(not isinstance(obj, string_types)
and isinstance(obj, collections.Iterable)):
return ', '.join(str(item) for item in sorted(obj))
Expand Down
33 changes: 33 additions & 0 deletions tests/test_app_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from tests.base_test import FlaskCorsTestCase, AppConfigTest
from tests.test_origins import OriginsTestCase
from tests.test_options import OptionsTestCase
from flask import Flask, jsonify

try:
Expand Down Expand Up @@ -165,5 +166,37 @@ def index():

for resp in self.iter_responses('/'):
self.assertEqual(resp.status_code, 200)


class AppExtensionOptionsTestCase(OptionsTestCase):
def __init__(self, *args, **kwargs):
super(AppExtensionOptionsTestCase, self).__init__(*args, **kwargs)

def setUp(self):
self.app = Flask(__name__)
CORS(self.app)

def test_defaults(self):
@self.app.route('/test_default')
def test_default():
return 'Welcome!'

super(AppExtensionOptionsTestCase, self).test_defaults()

def test_no_options_and_not_auto(self):
# This test isn't applicable since we the CORS App extension
# Doesn't need to add options handling to view functions, since
# it is called after_request, and will simply process the autogenerated
# Flask OPTIONS response
pass

def test_options_and_not_auto(self):
self.app.config['CORS_AUTOMATIC_OPTIONS'] = False

@self.app.route('/test_options_and_not_auto', methods=['OPTIONS'])
def test_options_and_not_auto():
return 'Welcome!'
super(AppExtensionOptionsTestCase, self).test_options_and_not_auto()

if __name__ == "__main__":
unittest.main()

0 comments on commit c586962

Please sign in to comment.