Skip to content

Commit

Permalink
Merge pull request #13 from ashleysommer/spf-port
Browse files Browse the repository at this point in the history
Pull in SPF-Port branch
  • Loading branch information
ashleysommer authored Nov 3, 2017
2 parents fee3007 + 69f2dba commit cb3de99
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 146 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ build
dist
sdist
eggs
env
parts
var
develop-eggs
Expand Down Expand Up @@ -42,4 +41,5 @@ pip-log.txt
*.html~
.*.sw*

.idea
.idea/
.directory
16 changes: 8 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
sudo: false
cache:
- pip
- pip
language: python
python:
- '3.5'
- '3.5'
env:
- SANIC=0.6.0
- SANIC=0.6.0 SPF=0.4.1.dev20171103
install:
- pip install -U setuptools pep8 coverage docutils pygments sanic==$SANIC aiohttp
- pip install -U setuptools pep8 coverage docutils pygments aiohttp sanic==$SANIC sanic-plugins-framework>=$SPF
script:
- coverage erase
- nosetests --with-coverage --cover-package=sanic_cors
- python setup.py clean build install
- coverage erase
- nosetests --with-coverage --cover-package=sanic_cors
- python setup.py clean build install
after_success:
- pep8 sanic_cors.py
- pep8 sanic_cors.py
deploy:
provider: pypi
user: ashleysommer
Expand Down
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Change Log

## 0.9.0
Ported Sanic-CORS to use Sanic-Plugins-Framework!

This is a big change. Some major architectural changes needed to occur.

All tests pass, so hopefully there's no fallout in any user facing way.

No longer tracking SANIC version numbers, we are doing our own versioning now.

## 0.6.0.2
Bug fixes, see git commits

## 0.6.0.1
Bug fixes, see git commits

## 0.6.0.0
Update to Sanic 0.6.0

## 0.5.0.0
Update to Sanic 0.5.x

## 0.4.1
Update to Sanic 0.4.1

## 0.1.0
Initial release of Sanic-Cors, ported to Sanic from Flask-Cors v3.0.2

Expand Down
4 changes: 2 additions & 2 deletions examples/view_based_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@


@app.route("/", methods=['GET', 'OPTIONS'])
@cross_origin(app)
@cross_origin(app, automatic_options=True)
def hello_world(request):
'''
This view has CORS enabled for all domains, representing the simplest
Expand Down Expand Up @@ -55,7 +55,7 @@ def hello_world(request):


@app.route("/api/v1/users/create", methods=['GET', 'POST', 'OPTIONS'])
@cross_origin(app, allow_headers=['Content-Type'])
@cross_origin(app, automatic_options=True, allow_headers=['Content-Type'])
def cross_origin_json_post(request):
'''
This view has CORS enabled for all domains, and allows browsers
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
sanic-plugins-framework
sanic>=0.6.0
20 changes: 11 additions & 9 deletions sanic_cors/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import logging
import collections
from datetime import timedelta
from sanic import request, response
from sanic.server import CIDict

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -130,7 +129,6 @@ def get_cors_origins(options, request_origin):
LOG.debug("The request's Origin header does not match any of allowed origins.")
return None


elif options.get('always_send'):
if wildcard:
# If wildcard is in the origins, even if 'send_wildcard' is False,
Expand All @@ -145,7 +143,8 @@ def get_cors_origins(options, request_origin):

# Terminate these steps, return the original request untouched.
else:
LOG.debug("The request did not contain an 'Origin' header. This means the browser or client did not request CORS, ensure the Origin Header is set.")
LOG.debug("The request did not contain an 'Origin' header. "
"This means the browser or client did not request CORS, ensure the Origin Header is set.")
return None


Expand Down Expand Up @@ -193,10 +192,11 @@ def get_cors_headers(options, request_headers, request_method):
# list of methods do not set any additional headers and terminate
# this set of steps.
headers[ACL_ALLOW_HEADERS] = get_allow_headers(options, request_headers.get(ACL_REQUEST_HEADERS))
headers[ACL_MAX_AGE] = str(options.get('max_age')) #sanic cannot handle integers in header values.
headers[ACL_MAX_AGE] = str(options.get('max_age')) # sanic cannot handle integers in header values.
headers[ACL_METHODS] = options.get('methods')
else:
LOG.info("The request's Access-Control-Request-Method header does not match allowed methods. CORS headers will not be applied.")
LOG.info("The request's Access-Control-Request-Method header does not match allowed methods. "
"CORS headers will not be applied.")

# http://www.w3.org/TR/cors/#resource-implementation
if options.get('vary_header'):
Expand All @@ -213,7 +213,7 @@ def get_cors_headers(options, request_headers, request_method):
return CIDict((k, v) for k, v in headers.items() if v)


def set_cors_headers(req, resp, options):
def set_cors_headers(req, resp, context, options):
"""
Performs the actual evaluation of Sanic-CORS options and actually
modifies the response object.
Expand All @@ -224,10 +224,11 @@ def set_cors_headers(req, resp, options):
"""

request_context = context.request
# If CORS has already been evaluated via the decorator, skip
if isinstance(req.headers, (dict, CIDict)) and SANIC_CORS_EVALUATED in req.headers:
evaluated = request_context.get(SANIC_CORS_EVALUATED, False)
if evaluated:
LOG.debug('CORS have been already evaluated, skipping')
del req.headers[SANIC_CORS_EVALUATED]
return resp

# `resp` can be None in the case of using Websockets
Expand Down Expand Up @@ -351,6 +352,7 @@ def ensure_iterable(inst):
else:
return inst


def sanitize_regex_param(param):
return [re_fix(x) for x in ensure_iterable(param)]

Expand All @@ -363,7 +365,7 @@ def serialize_options(opts):

for key in opts.keys():
if key not in DEFAULT_OPTIONS:
LOG.warn("Unknown option passed to Sanic-CORS: %s", key)
LOG.warning("Unknown option passed to Sanic-CORS: %s", key)

# Ensure origins is a list of allowed origins with at least one entry.
options['origins'] = sanitize_regex_param(options.get('origins'))
Expand Down
63 changes: 21 additions & 42 deletions sanic_cors/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
:copyright: (c) 2017 by Ashley Sommer (based on flask-cors by Cory Dolphin).
:license: MIT, see LICENSE for more details.
"""
import asyncio
from functools import update_wrapper
from inspect import isawaitable

from spf import SanicPluginsFramework
from .core import *
from .extension import cors

LOG = logging.getLogger(__name__)
#LOG = logging.getLogger(__name__)


def cross_origin(app, *args, **kwargs):
Expand Down Expand Up @@ -103,43 +106,19 @@ def cross_origin(app, *args, **kwargs):
"""
_options = kwargs

def decorator(f):
nonlocal _options
LOG.debug("Enabling %s for cross_origin using options:%s", f, _options)

# Sanic does not have the same automatic OPTIONS handling that Flask does,
# and Sanic does not allow other middleware to alter the allowed methods on a route
# So this decorator cannot work the same as it does in Flask-CORS.
#
# # If True, intercept OPTIONS requests by modifying the view function,
# # replicating Sanic's default behavior, and wrapping the response with
# # CORS headers.
# #
# # If f.provide_automatic_options is unset or True, Sanic's route
# # decorator (which is actually wraps the function object we return)
# # intercepts OPTIONS handling, and requests will not have CORS headers
# if _options.get('automatic_options', True):
# f.required_methods = getattr(f, 'required_methods', set())
# f.required_methods.add('OPTIONS')
# f.provide_automatic_options = False

async def wrapped_function(req, *args, **kwargs):
nonlocal _options
# Handle setting of Sanic-Cors parameters
options = get_cors_options(app, _options)

if options.get('automatic_options') and req.method == 'OPTIONS':
resp = response.HTTPResponse()
else:
resp = f(req, *args, **kwargs)
if resp is not None: # `resp` can be None in the case of using Websockets
while asyncio.iscoroutine(resp):
resp = await resp
if resp is not None:
set_cors_headers(req, resp, options)
req.headers[SANIC_CORS_EVALUATED] = "1"
return resp

return update_wrapper(wrapped_function, f)
return decorator
_real_decorator = cors.decorate(app, *args, run_middleware=False, with_context=False, **kwargs)

def wrapped_decorator(f):
spf = SanicPluginsFramework(app) # get the singleton from the app
try:
plugin = spf.register_plugin(cors, skip_reg=True)
except ValueError as e:
# this is normal, if this plugin has been registered previously
assert e.args and len(e.args) > 1
plugin = e.args[1]
context = cors.get_context_from_spf(spf)
log = context.log
log(logging.DEBUG, "Enabled {:s} for cross_origin using options: {}".format(str(f), str(_options)))
return _real_decorator(f)

return wrapped_decorator
Loading

0 comments on commit cb3de99

Please sign in to comment.