Merge pull request #900 from yunstanford/patch-default-strict-slashes
Patch default strict slashes
This commit is contained in:
		| @@ -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) | ||||
| ``` | ||||
|   | ||||
							
								
								
									
										37
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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') | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Raphael Deem
					Raphael Deem