Merge pull request #900 from yunstanford/patch-default-strict-slashes

Patch default strict slashes
This commit is contained in:
Raphael Deem 2017-08-21 00:31:42 -07:00 committed by GitHub
commit fa1a95ae91
5 changed files with 165 additions and 23 deletions

View File

@ -214,3 +214,28 @@ and `recv` methods to send and receive data respectively.
WebSocket support requires the [websockets](https://github.com/aaugustin/websockets)
package by Aymeric Augustin.
## About `strict_slashes`
You can make `routes` strict to trailing slash or not, it's configurable.
```python
# provide default strict_slashes value for all routes
app = Sanic('test_route_strict_slash', strict_slashes=True)
# you can also overwrite strict_slashes value for specific route
@app.get('/get', strict_slashes=False)
def handler(request):
return text('OK')
# It also works for blueprints
bp = Blueprint('test_bp_strict_slash', strict_slashes=True)
@bp.get('/bp/get', strict_slashes=False)
def handler(request):
return text('OK')
app.blueprint(bp)
```

View File

@ -28,7 +28,7 @@ class Sanic:
def __init__(self, name=None, router=None, error_handler=None,
load_env=True, request_class=None,
log_config=LOGGING):
log_config=LOGGING, strict_slashes=False):
if log_config:
logging.config.dictConfig(log_config)
# Only set up a default log handler if the
@ -58,6 +58,7 @@ class Sanic:
self._blueprint_order = []
self.debug = None
self.sock = None
self.strict_slashes = strict_slashes
self.listeners = defaultdict(list)
self.is_running = False
self.is_request_stream = False
@ -111,7 +112,7 @@ class Sanic:
# Decorator
def route(self, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False, stream=False, version=None):
strict_slashes=None, stream=False, version=None):
"""Decorate a function to be registered as a route
:param uri: path of the URL
@ -130,6 +131,9 @@ class Sanic:
if stream:
self.is_request_stream = True
if strict_slashes is None:
strict_slashes = self.strict_slashes
def response(handler):
if stream:
handler.is_stream = stream
@ -141,42 +145,42 @@ class Sanic:
return response
# Shorthand method decorators
def get(self, uri, host=None, strict_slashes=False, version=None):
def get(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=frozenset({"GET"}), host=host,
strict_slashes=strict_slashes, version=version)
def post(self, uri, host=None, strict_slashes=False, stream=False,
def post(self, uri, host=None, strict_slashes=None, stream=False,
version=None):
return self.route(uri, methods=frozenset({"POST"}), host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)
def put(self, uri, host=None, strict_slashes=False, stream=False,
def put(self, uri, host=None, strict_slashes=None, stream=False,
version=None):
return self.route(uri, methods=frozenset({"PUT"}), host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)
def head(self, uri, host=None, strict_slashes=False, version=None):
def head(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
strict_slashes=strict_slashes, version=version)
def options(self, uri, host=None, strict_slashes=False, version=None):
def options(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
strict_slashes=strict_slashes, version=version)
def patch(self, uri, host=None, strict_slashes=False, stream=False,
def patch(self, uri, host=None, strict_slashes=None, stream=False,
version=None):
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)
def delete(self, uri, host=None, strict_slashes=False, version=None):
def delete(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
strict_slashes=strict_slashes, version=version)
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False, version=None):
strict_slashes=None, version=None):
"""A helper method to register class instance or
functions as a handler to the application url
routes.
@ -208,13 +212,16 @@ class Sanic:
stream = True
break
if strict_slashes is None:
strict_slashes = self.strict_slashes
self.route(uri=uri, methods=methods, host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)(handler)
return handler
# Decorator
def websocket(self, uri, host=None, strict_slashes=False,
def websocket(self, uri, host=None, strict_slashes=None,
subprotocols=None):
"""Decorate a function to be registered as a websocket route
:param uri: path of the URL
@ -230,6 +237,9 @@ class Sanic:
if not uri.startswith('/'):
uri = '/' + uri
if strict_slashes is None:
strict_slashes = self.strict_slashes
def response(handler):
async def websocket_handler(request, *args, **kwargs):
request.app = self
@ -261,8 +271,11 @@ class Sanic:
return response
def add_websocket_route(self, handler, uri, host=None,
strict_slashes=False):
strict_slashes=None):
"""A helper method to register a function as a websocket route."""
if strict_slashes is None:
strict_slashes = self.strict_slashes
return self.websocket(uri, host=host,
strict_slashes=strict_slashes)(handler)

View File

@ -14,11 +14,16 @@ FutureStatic = namedtuple('Route',
class Blueprint:
def __init__(self, name, url_prefix=None, host=None, version=None):
def __init__(self, name,
url_prefix=None,
host=None, version=None,
strict_slashes=False):
"""Create a new blueprint
:param name: unique name of the blueprint
:param url_prefix: URL to be prefixed before all route URLs
:param strict_slashes: strict to trailing slash
"""
self.name = name
self.url_prefix = url_prefix
@ -31,6 +36,7 @@ class Blueprint:
self.middlewares = []
self.statics = []
self.version = version
self.strict_slashes = strict_slashes
def register(self, app, options):
"""Register the blueprint to the sanic app."""
@ -94,12 +100,15 @@ class Blueprint:
app.listener(event)(listener)
def route(self, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False, stream=False, version=None):
strict_slashes=None, stream=False, version=None):
"""Create a blueprint route from a decorated function.
:param uri: endpoint at which the route will be accessible.
:param methods: list of acceptable HTTP methods.
"""
if strict_slashes is None:
strict_slashes = self.strict_slashes
def decorator(handler):
route = FutureRoute(
handler, uri, methods, host, strict_slashes, stream, version)
@ -108,7 +117,7 @@ class Blueprint:
return decorator
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False, version=None):
strict_slashes=None, version=None):
"""Create a blueprint route from a function.
:param handler: function for handling uri requests. Accepts function,
@ -125,6 +134,9 @@ class Blueprint:
if getattr(handler.view_class, method.lower(), None):
methods.add(method)
if strict_slashes is None:
strict_slashes = self.strict_slashes
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
@ -133,11 +145,14 @@ class Blueprint:
strict_slashes=strict_slashes, version=version)(handler)
return handler
def websocket(self, uri, host=None, strict_slashes=False, version=None):
def websocket(self, uri, host=None, strict_slashes=None, version=None):
"""Create a blueprint websocket route from a decorated function.
:param uri: endpoint at which the route will be accessible.
"""
if strict_slashes is None:
strict_slashes = self.strict_slashes
def decorator(handler):
route = FutureRoute(handler, uri, [], host, strict_slashes,
False, version)
@ -199,36 +214,36 @@ class Blueprint:
self.statics.append(static)
# Shorthand method decorators
def get(self, uri, host=None, strict_slashes=False, version=None):
def get(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=["GET"], host=host,
strict_slashes=strict_slashes, version=version)
def post(self, uri, host=None, strict_slashes=False, stream=False,
def post(self, uri, host=None, strict_slashes=None, stream=False,
version=None):
return self.route(uri, methods=["POST"], host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)
def put(self, uri, host=None, strict_slashes=False, stream=False,
def put(self, uri, host=None, strict_slashes=None, stream=False,
version=None):
return self.route(uri, methods=["PUT"], host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)
def head(self, uri, host=None, strict_slashes=False, version=None):
def head(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=["HEAD"], host=host,
strict_slashes=strict_slashes, version=version)
def options(self, uri, host=None, strict_slashes=False, version=None):
def options(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=["OPTIONS"], host=host,
strict_slashes=strict_slashes, version=version)
def patch(self, uri, host=None, strict_slashes=False, stream=False,
def patch(self, uri, host=None, strict_slashes=None, stream=False,
version=None):
return self.route(uri, methods=["PATCH"], host=host,
strict_slashes=strict_slashes, stream=stream,
version=version)
def delete(self, uri, host=None, strict_slashes=False, version=None):
def delete(self, uri, host=None, strict_slashes=None, version=None):
return self.route(uri, methods=["DELETE"], host=host,
strict_slashes=strict_slashes, version=version)

View File

@ -78,6 +78,65 @@ def test_bp_strict_slash():
request, response = app.test_client.post('/post')
assert response.status == 404
def test_bp_strict_slash_default_value():
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text', strict_slashes=True)
@bp.get('/get')
def handler(request):
return text('OK')
@bp.post('/post/')
def handler(request):
return text('OK')
app.blueprint(bp)
request, response = app.test_client.get('/get/')
assert response.status == 404
request, response = app.test_client.post('/post')
assert response.status == 404
def test_bp_strict_slash_without_passing_default_value():
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text')
@bp.get('/get')
def handler(request):
return text('OK')
@bp.post('/post/')
def handler(request):
return text('OK')
app.blueprint(bp)
request, response = app.test_client.get('/get/')
assert response.text == 'OK'
request, response = app.test_client.post('/post')
assert response.text == 'OK'
def test_bp_strict_slash_default_value_can_be_overwritten():
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text', strict_slashes=True)
@bp.get('/get', strict_slashes=False)
def handler(request):
return text('OK')
@bp.post('/post/', strict_slashes=False)
def handler(request):
return text('OK')
app.blueprint(bp)
request, response = app.test_client.get('/get/')
assert response.text == 'OK'
request, response = app.test_client.post('/post')
assert response.text == 'OK'
def test_bp_with_url_prefix():
app = Sanic('test_text')

View File

@ -71,6 +71,36 @@ def test_route_strict_slash():
request, response = app.test_client.post('/post')
assert response.status == 404
def test_route_strict_slash_default_value():
app = Sanic('test_route_strict_slash', strict_slashes=True)
@app.get('/get')
def handler(request):
return text('OK')
request, response = app.test_client.get('/get/')
assert response.status == 404
def test_route_strict_slash_without_passing_default_value():
app = Sanic('test_route_strict_slash')
@app.get('/get')
def handler(request):
return text('OK')
request, response = app.test_client.get('/get/')
assert response.text == 'OK'
def test_route_strict_slash_default_value_can_be_overwritten():
app = Sanic('test_route_strict_slash', strict_slashes=True)
@app.get('/get', strict_slashes=False)
def handler(request):
return text('OK')
request, response = app.test_client.get('/get/')
assert response.text == 'OK'
def test_route_optional_slash():
app = Sanic('test_route_optional_slash')