Version prefix (#2137)
* Add version prefixing * Versioning tests * Testing BP group properties
This commit is contained in:
parent
28ba8e53df
commit
3a6fac7d59
|
@ -58,13 +58,20 @@ class BlueprintGroup(MutableSequence):
|
||||||
app.blueprint(bpg)
|
app.blueprint(bpg)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_blueprints", "_url_prefix", "_version", "_strict_slashes")
|
__slots__ = (
|
||||||
|
"_blueprints",
|
||||||
|
"_url_prefix",
|
||||||
|
"_version",
|
||||||
|
"_strict_slashes",
|
||||||
|
"_version_prefix",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
url_prefix: Optional[str] = None,
|
url_prefix: Optional[str] = None,
|
||||||
version: Optional[Union[int, str, float]] = None,
|
version: Optional[Union[int, str, float]] = None,
|
||||||
strict_slashes: Optional[bool] = None,
|
strict_slashes: Optional[bool] = None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a new Blueprint Group
|
Create a new Blueprint Group
|
||||||
|
@ -77,6 +84,7 @@ class BlueprintGroup(MutableSequence):
|
||||||
self._blueprints: List[Blueprint] = []
|
self._blueprints: List[Blueprint] = []
|
||||||
self._url_prefix = url_prefix
|
self._url_prefix = url_prefix
|
||||||
self._version = version
|
self._version = version
|
||||||
|
self._version_prefix = version_prefix
|
||||||
self._strict_slashes = strict_slashes
|
self._strict_slashes = strict_slashes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -116,6 +124,15 @@ class BlueprintGroup(MutableSequence):
|
||||||
"""
|
"""
|
||||||
return self._strict_slashes
|
return self._strict_slashes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version_prefix(self) -> str:
|
||||||
|
"""
|
||||||
|
Version prefix; defaults to ``/v``
|
||||||
|
|
||||||
|
:return: str
|
||||||
|
"""
|
||||||
|
return self._version_prefix
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""
|
"""
|
||||||
Tun the class Blueprint Group into an Iterable item
|
Tun the class Blueprint Group into an Iterable item
|
||||||
|
@ -186,6 +203,9 @@ class BlueprintGroup(MutableSequence):
|
||||||
for _attr in ["version", "strict_slashes"]:
|
for _attr in ["version", "strict_slashes"]:
|
||||||
if getattr(bp, _attr) is None:
|
if getattr(bp, _attr) is None:
|
||||||
setattr(bp, _attr, getattr(self, _attr))
|
setattr(bp, _attr, getattr(self, _attr))
|
||||||
|
if bp.version_prefix == "/v":
|
||||||
|
bp.version_prefix = self._version_prefix
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|
||||||
def append(self, value: "sanic.Blueprint") -> None:
|
def append(self, value: "sanic.Blueprint") -> None:
|
||||||
|
|
|
@ -72,6 +72,7 @@ class Blueprint(BaseSanic):
|
||||||
host: Optional[str] = None,
|
host: Optional[str] = None,
|
||||||
version: Optional[Union[int, str, float]] = None,
|
version: Optional[Union[int, str, float]] = None,
|
||||||
strict_slashes: Optional[bool] = None,
|
strict_slashes: Optional[bool] = None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@ class Blueprint(BaseSanic):
|
||||||
else url_prefix
|
else url_prefix
|
||||||
)
|
)
|
||||||
self.version = version
|
self.version = version
|
||||||
|
self.version_prefix = version_prefix
|
||||||
self.websocket_routes: List[Route] = []
|
self.websocket_routes: List[Route] = []
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
@ -143,7 +145,13 @@ class Blueprint(BaseSanic):
|
||||||
return super().signal(event, *args, **kwargs)
|
return super().signal(event, *args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group(*blueprints, url_prefix="", version=None, strict_slashes=None):
|
def group(
|
||||||
|
*blueprints,
|
||||||
|
url_prefix="",
|
||||||
|
version=None,
|
||||||
|
strict_slashes=None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a list of blueprints, optionally grouping them under a
|
Create a list of blueprints, optionally grouping them under a
|
||||||
general URL prefix.
|
general URL prefix.
|
||||||
|
@ -169,6 +177,7 @@ class Blueprint(BaseSanic):
|
||||||
url_prefix=url_prefix,
|
url_prefix=url_prefix,
|
||||||
version=version,
|
version=version,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
for bp in chain(blueprints):
|
for bp in chain(blueprints):
|
||||||
bps.append(bp)
|
bps.append(bp)
|
||||||
|
@ -186,6 +195,8 @@ class Blueprint(BaseSanic):
|
||||||
|
|
||||||
self._apps.add(app)
|
self._apps.add(app)
|
||||||
url_prefix = options.get("url_prefix", self.url_prefix)
|
url_prefix = options.get("url_prefix", self.url_prefix)
|
||||||
|
opt_version = options.get("version", None)
|
||||||
|
opt_version_prefix = options.get("version_prefix", self.version_prefix)
|
||||||
|
|
||||||
routes = []
|
routes = []
|
||||||
middleware = []
|
middleware = []
|
||||||
|
@ -200,6 +211,21 @@ class Blueprint(BaseSanic):
|
||||||
# Prepend the blueprint URI prefix if available
|
# Prepend the blueprint URI prefix if available
|
||||||
uri = url_prefix + future.uri if url_prefix else future.uri
|
uri = url_prefix + future.uri if url_prefix else future.uri
|
||||||
|
|
||||||
|
version_prefix = self.version_prefix
|
||||||
|
for prefix in (
|
||||||
|
future.version_prefix,
|
||||||
|
opt_version_prefix,
|
||||||
|
):
|
||||||
|
if prefix and prefix != "/v":
|
||||||
|
version_prefix = prefix
|
||||||
|
break
|
||||||
|
|
||||||
|
version = self.version
|
||||||
|
for v in (future.version, opt_version, self.version):
|
||||||
|
if v is not None:
|
||||||
|
version = v
|
||||||
|
break
|
||||||
|
|
||||||
strict_slashes = (
|
strict_slashes = (
|
||||||
self.strict_slashes
|
self.strict_slashes
|
||||||
if future.strict_slashes is None
|
if future.strict_slashes is None
|
||||||
|
@ -215,13 +241,14 @@ class Blueprint(BaseSanic):
|
||||||
future.host or self.host,
|
future.host or self.host,
|
||||||
strict_slashes,
|
strict_slashes,
|
||||||
future.stream,
|
future.stream,
|
||||||
future.version or self.version,
|
version,
|
||||||
name,
|
name,
|
||||||
future.ignore_body,
|
future.ignore_body,
|
||||||
future.websocket,
|
future.websocket,
|
||||||
future.subprotocols,
|
future.subprotocols,
|
||||||
future.unquote,
|
future.unquote,
|
||||||
future.static,
|
future.static,
|
||||||
|
version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
route = app._apply_route(apply_route)
|
route = app._apply_route(apply_route)
|
||||||
|
|
|
@ -53,6 +53,7 @@ class RouteMixin:
|
||||||
websocket: bool = False,
|
websocket: bool = False,
|
||||||
unquote: bool = False,
|
unquote: bool = False,
|
||||||
static: bool = False,
|
static: bool = False,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Decorate a function to be registered as a route
|
Decorate a function to be registered as a route
|
||||||
|
@ -66,6 +67,8 @@ class RouteMixin:
|
||||||
:param name: user defined route name for url_for
|
:param name: user defined route name for url_for
|
||||||
:param ignore_body: whether the handler should ignore request
|
:param ignore_body: whether the handler should ignore request
|
||||||
body (eg. GET requests)
|
body (eg. GET requests)
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: tuple of routes, decorated function
|
:return: tuple of routes, decorated function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -92,6 +95,7 @@ class RouteMixin:
|
||||||
nonlocal subprotocols
|
nonlocal subprotocols
|
||||||
nonlocal websocket
|
nonlocal websocket
|
||||||
nonlocal static
|
nonlocal static
|
||||||
|
nonlocal version_prefix
|
||||||
|
|
||||||
if isinstance(handler, tuple):
|
if isinstance(handler, tuple):
|
||||||
# if a handler fn is already wrapped in a route, the handler
|
# if a handler fn is already wrapped in a route, the handler
|
||||||
|
@ -128,6 +132,7 @@ class RouteMixin:
|
||||||
subprotocols,
|
subprotocols,
|
||||||
unquote,
|
unquote,
|
||||||
static,
|
static,
|
||||||
|
version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._future_routes.add(route)
|
self._future_routes.add(route)
|
||||||
|
@ -168,6 +173,7 @@ class RouteMixin:
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
stream: bool = False,
|
stream: bool = False,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""A helper method to register class instance or
|
"""A helper method to register class instance or
|
||||||
functions as a handler to the application url
|
functions as a handler to the application url
|
||||||
|
@ -182,6 +188,8 @@ class RouteMixin:
|
||||||
:param version:
|
:param version:
|
||||||
:param name: user defined route name for url_for
|
:param name: user defined route name for url_for
|
||||||
:param stream: boolean specifying if the handler is a stream handler
|
:param stream: boolean specifying if the handler is a stream handler
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: function or class instance
|
:return: function or class instance
|
||||||
"""
|
"""
|
||||||
# Handle HTTPMethodView differently
|
# Handle HTTPMethodView differently
|
||||||
|
@ -214,6 +222,7 @@ class RouteMixin:
|
||||||
stream=stream,
|
stream=stream,
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)(handler)
|
)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
@ -226,6 +235,7 @@ class RouteMixin:
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
ignore_body: bool = True,
|
ignore_body: bool = True,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **GET** *HTTP* method
|
Add an API URL under the **GET** *HTTP* method
|
||||||
|
@ -236,6 +246,8 @@ class RouteMixin:
|
||||||
URLs need to terminate with a */*
|
URLs need to terminate with a */*
|
||||||
:param version: API Version
|
:param version: API Version
|
||||||
:param name: Unique name that can be used to identify the Route
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -246,6 +258,7 @@ class RouteMixin:
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
ignore_body=ignore_body,
|
ignore_body=ignore_body,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def post(
|
def post(
|
||||||
|
@ -256,6 +269,7 @@ class RouteMixin:
|
||||||
stream: bool = False,
|
stream: bool = False,
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **POST** *HTTP* method
|
Add an API URL under the **POST** *HTTP* method
|
||||||
|
@ -266,6 +280,8 @@ class RouteMixin:
|
||||||
URLs need to terminate with a */*
|
URLs need to terminate with a */*
|
||||||
:param version: API Version
|
:param version: API Version
|
||||||
:param name: Unique name that can be used to identify the Route
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -276,6 +292,7 @@ class RouteMixin:
|
||||||
stream=stream,
|
stream=stream,
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def put(
|
def put(
|
||||||
|
@ -286,6 +303,7 @@ class RouteMixin:
|
||||||
stream: bool = False,
|
stream: bool = False,
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **PUT** *HTTP* method
|
Add an API URL under the **PUT** *HTTP* method
|
||||||
|
@ -296,6 +314,8 @@ class RouteMixin:
|
||||||
URLs need to terminate with a */*
|
URLs need to terminate with a */*
|
||||||
:param version: API Version
|
:param version: API Version
|
||||||
:param name: Unique name that can be used to identify the Route
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -306,6 +326,7 @@ class RouteMixin:
|
||||||
stream=stream,
|
stream=stream,
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def head(
|
def head(
|
||||||
|
@ -316,6 +337,7 @@ class RouteMixin:
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
ignore_body: bool = True,
|
ignore_body: bool = True,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **HEAD** *HTTP* method
|
Add an API URL under the **HEAD** *HTTP* method
|
||||||
|
@ -334,6 +356,8 @@ class RouteMixin:
|
||||||
:param ignore_body: whether the handler should ignore request
|
:param ignore_body: whether the handler should ignore request
|
||||||
body (eg. GET requests), defaults to True
|
body (eg. GET requests), defaults to True
|
||||||
:type ignore_body: bool, optional
|
:type ignore_body: bool, optional
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -344,6 +368,7 @@ class RouteMixin:
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
ignore_body=ignore_body,
|
ignore_body=ignore_body,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def options(
|
def options(
|
||||||
|
@ -354,6 +379,7 @@ class RouteMixin:
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
ignore_body: bool = True,
|
ignore_body: bool = True,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **OPTIONS** *HTTP* method
|
Add an API URL under the **OPTIONS** *HTTP* method
|
||||||
|
@ -372,6 +398,8 @@ class RouteMixin:
|
||||||
:param ignore_body: whether the handler should ignore request
|
:param ignore_body: whether the handler should ignore request
|
||||||
body (eg. GET requests), defaults to True
|
body (eg. GET requests), defaults to True
|
||||||
:type ignore_body: bool, optional
|
:type ignore_body: bool, optional
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -382,6 +410,7 @@ class RouteMixin:
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
ignore_body=ignore_body,
|
ignore_body=ignore_body,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def patch(
|
def patch(
|
||||||
|
@ -392,6 +421,7 @@ class RouteMixin:
|
||||||
stream=False,
|
stream=False,
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **PATCH** *HTTP* method
|
Add an API URL under the **PATCH** *HTTP* method
|
||||||
|
@ -412,6 +442,8 @@ class RouteMixin:
|
||||||
:param ignore_body: whether the handler should ignore request
|
:param ignore_body: whether the handler should ignore request
|
||||||
body (eg. GET requests), defaults to True
|
body (eg. GET requests), defaults to True
|
||||||
:type ignore_body: bool, optional
|
:type ignore_body: bool, optional
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -422,6 +454,7 @@ class RouteMixin:
|
||||||
stream=stream,
|
stream=stream,
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete(
|
def delete(
|
||||||
|
@ -432,6 +465,7 @@ class RouteMixin:
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
ignore_body: bool = True,
|
ignore_body: bool = True,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an API URL under the **DELETE** *HTTP* method
|
Add an API URL under the **DELETE** *HTTP* method
|
||||||
|
@ -442,6 +476,8 @@ class RouteMixin:
|
||||||
URLs need to terminate with a */*
|
URLs need to terminate with a */*
|
||||||
:param version: API Version
|
:param version: API Version
|
||||||
:param name: Unique name that can be used to identify the Route
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Object decorated with :func:`route` method
|
:return: Object decorated with :func:`route` method
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -452,6 +488,7 @@ class RouteMixin:
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
ignore_body=ignore_body,
|
ignore_body=ignore_body,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def websocket(
|
def websocket(
|
||||||
|
@ -463,6 +500,7 @@ class RouteMixin:
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
apply: bool = True,
|
apply: bool = True,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Decorate a function to be registered as a websocket route
|
Decorate a function to be registered as a websocket route
|
||||||
|
@ -474,6 +512,8 @@ class RouteMixin:
|
||||||
:param subprotocols: optional list of str with supported subprotocols
|
:param subprotocols: optional list of str with supported subprotocols
|
||||||
:param name: A unique name assigned to the URL so that it can
|
:param name: A unique name assigned to the URL so that it can
|
||||||
be used with :func:`url_for`
|
be used with :func:`url_for`
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: tuple of routes, decorated function
|
:return: tuple of routes, decorated function
|
||||||
"""
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
|
@ -486,6 +526,7 @@ class RouteMixin:
|
||||||
apply=apply,
|
apply=apply,
|
||||||
subprotocols=subprotocols,
|
subprotocols=subprotocols,
|
||||||
websocket=True,
|
websocket=True,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_websocket_route(
|
def add_websocket_route(
|
||||||
|
@ -497,6 +538,7 @@ class RouteMixin:
|
||||||
subprotocols=None,
|
subprotocols=None,
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
version_prefix: str = "/v",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
A helper method to register a function as a websocket route.
|
A helper method to register a function as a websocket route.
|
||||||
|
@ -513,6 +555,8 @@ class RouteMixin:
|
||||||
handshake
|
handshake
|
||||||
:param name: A unique name assigned to the URL so that it can
|
:param name: A unique name assigned to the URL so that it can
|
||||||
be used with :func:`url_for`
|
be used with :func:`url_for`
|
||||||
|
:param version_prefix: URL path that should be before the version
|
||||||
|
value; default: ``/v``
|
||||||
:return: Objected decorated by :func:`websocket`
|
:return: Objected decorated by :func:`websocket`
|
||||||
"""
|
"""
|
||||||
return self.websocket(
|
return self.websocket(
|
||||||
|
@ -522,6 +566,7 @@ class RouteMixin:
|
||||||
subprotocols=subprotocols,
|
subprotocols=subprotocols,
|
||||||
version=version,
|
version=version,
|
||||||
name=name,
|
name=name,
|
||||||
|
version_prefix=version_prefix,
|
||||||
)(handler)
|
)(handler)
|
||||||
|
|
||||||
def static(
|
def static(
|
||||||
|
|
|
@ -23,6 +23,7 @@ class FutureRoute(NamedTuple):
|
||||||
subprotocols: Optional[List[str]]
|
subprotocols: Optional[List[str]]
|
||||||
unquote: bool
|
unquote: bool
|
||||||
static: bool
|
static: bool
|
||||||
|
version_prefix: str
|
||||||
|
|
||||||
|
|
||||||
class FutureListener(NamedTuple):
|
class FutureListener(NamedTuple):
|
||||||
|
|
|
@ -73,6 +73,7 @@ class Router(BaseRouter):
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
unquote: bool = False,
|
unquote: bool = False,
|
||||||
static: bool = False,
|
static: bool = False,
|
||||||
|
version_prefix: str = "/v",
|
||||||
) -> Union[Route, List[Route]]:
|
) -> Union[Route, List[Route]]:
|
||||||
"""
|
"""
|
||||||
Add a handler to the router
|
Add a handler to the router
|
||||||
|
@ -103,7 +104,7 @@ class Router(BaseRouter):
|
||||||
"""
|
"""
|
||||||
if version is not None:
|
if version is not None:
|
||||||
version = str(version).strip("/").lstrip("v")
|
version = str(version).strip("/").lstrip("v")
|
||||||
uri = "/".join([f"/v{version}", uri.lstrip("/")])
|
uri = "/".join([f"{version_prefix}{version}", uri.lstrip("/")])
|
||||||
|
|
||||||
params = dict(
|
params = dict(
|
||||||
path=uri,
|
path=uri,
|
||||||
|
|
|
@ -218,3 +218,32 @@ def test_blueprint_group_insert():
|
||||||
assert group.blueprints[1].strict_slashes is False
|
assert group.blueprints[1].strict_slashes is False
|
||||||
assert group.blueprints[2].strict_slashes is True
|
assert group.blueprints[2].strict_slashes is True
|
||||||
assert group.blueprints[0].url_prefix == "/test"
|
assert group.blueprints[0].url_prefix == "/test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_group_properties():
|
||||||
|
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
|
||||||
|
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
||||||
|
group = Blueprint.group(
|
||||||
|
blueprint_1,
|
||||||
|
blueprint_2,
|
||||||
|
version=1,
|
||||||
|
version_prefix="/api/v",
|
||||||
|
url_prefix="/grouped",
|
||||||
|
strict_slashes=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert group.version_prefix == "/api/v"
|
||||||
|
assert blueprint_1.version_prefix == "/api/v"
|
||||||
|
assert blueprint_2.version_prefix == "/api/v"
|
||||||
|
|
||||||
|
assert group.version == 1
|
||||||
|
assert blueprint_1.version == 1
|
||||||
|
assert blueprint_2.version == 1
|
||||||
|
|
||||||
|
assert group.strict_slashes
|
||||||
|
assert blueprint_1.strict_slashes
|
||||||
|
assert blueprint_2.strict_slashes
|
||||||
|
|
||||||
|
assert group.url_prefix == "/grouped"
|
||||||
|
assert blueprint_1.url_prefix == "/grouped/bp1"
|
||||||
|
assert blueprint_2.url_prefix == "/grouped/bp2"
|
||||||
|
|
141
tests/test_versioning.py
Normal file
141
tests/test_versioning.py
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from sanic import Blueprint, text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def handler():
|
||||||
|
def handler(_):
|
||||||
|
return text("Done.")
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
|
||||||
|
def test_route(app, handler):
|
||||||
|
app.route("/", version=1)(handler)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1)
|
||||||
|
bp.route("/")(handler)
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_use_route(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1)
|
||||||
|
bp.route("/", version=1.1)(handler)
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1.1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_group(app, handler):
|
||||||
|
bp = Blueprint(__file__)
|
||||||
|
bp.route("/")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1)
|
||||||
|
app.blueprint(group)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_group_use_bp(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1.1)
|
||||||
|
bp.route("/")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1)
|
||||||
|
app.blueprint(group)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1.1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_group_use_registration(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1.1)
|
||||||
|
bp.route("/")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1)
|
||||||
|
app.blueprint(group, version=1.2)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1.2")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_group_use_route(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1.1)
|
||||||
|
bp.route("/", version=1.3)(handler)
|
||||||
|
group = Blueprint.group(bp, version=1)
|
||||||
|
app.blueprint(group, version=1.2)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/v1.3")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_route(app, handler):
|
||||||
|
app.route("/", version=1, version_prefix="/api/v")(handler)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_bp(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1, version_prefix="/api/v")
|
||||||
|
bp.route("/")(handler)
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_bp_use_route(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1, version_prefix="/ignore/v")
|
||||||
|
bp.route("/", version=1.1, version_prefix="/api/v")(handler)
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1.1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_bp_group(app, handler):
|
||||||
|
bp = Blueprint(__file__)
|
||||||
|
bp.route("/")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1, version_prefix="/api/v")
|
||||||
|
app.blueprint(group)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_bp_group_use_bp(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1.1, version_prefix="/api/v")
|
||||||
|
bp.route("/")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1, version_prefix="/ignore/v")
|
||||||
|
app.blueprint(group)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1.1")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_bp_group_use_registration(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1.1, version_prefix="/alsoignore/v")
|
||||||
|
bp.route("/")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1, version_prefix="/ignore/v")
|
||||||
|
app.blueprint(group, version=1.2, version_prefix="/api/v")
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1.2")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_prefix_bp_group_use_route(app, handler):
|
||||||
|
bp = Blueprint(__file__, version=1.1, version_prefix="/alsoignore/v")
|
||||||
|
bp.route("/", version=1.3, version_prefix="/api/v")(handler)
|
||||||
|
group = Blueprint.group(bp, version=1, version_prefix="/ignore/v")
|
||||||
|
app.blueprint(group, version=1.2, version_prefix="/stillignoring/v")
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/api/v1.3")
|
||||||
|
assert response.status == 200
|
Loading…
Reference in New Issue
Block a user