CBV alternate attach; CompositionView deprecate (#2170)

* Deprecate composition view and add alternate methods to attach CBV

* Add args to CBV attaching
This commit is contained in:
Adam Hopkins 2021-06-21 14:26:42 +03:00 committed by GitHub
parent 80fca9aef7
commit c543d19f8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 138 additions and 8 deletions

View File

@ -1,9 +1,25 @@
from typing import Any, Callable, List from __future__ import annotations
from typing import (
TYPE_CHECKING,
Any,
Callable,
Iterable,
List,
Optional,
Union,
)
from warnings import warn
from sanic.constants import HTTP_METHODS from sanic.constants import HTTP_METHODS
from sanic.exceptions import InvalidUsage from sanic.exceptions import InvalidUsage
if TYPE_CHECKING:
from sanic import Sanic
from sanic.blueprints import Blueprint
class HTTPMethodView: class HTTPMethodView:
"""Simple class based implementation of view for the sanic. """Simple class based implementation of view for the sanic.
You should implement methods (get, post, put, patch, delete) for the class You should implement methods (get, post, put, patch, delete) for the class
@ -40,6 +56,31 @@ class HTTPMethodView:
decorators: List[Callable[[Callable[..., Any]], Callable[..., Any]]] = [] decorators: List[Callable[[Callable[..., Any]], Callable[..., Any]]] = []
def __init_subclass__(
cls,
attach: Optional[Union[Sanic, Blueprint]] = None,
uri: str = "",
methods: Iterable[str] = frozenset({"GET"}),
host: Optional[str] = None,
strict_slashes: Optional[bool] = None,
version: Optional[int] = None,
name: Optional[str] = None,
stream: bool = False,
version_prefix: str = "/v",
) -> None:
if attach:
cls.attach(
attach,
uri=uri,
methods=methods,
host=host,
strict_slashes=strict_slashes,
version=version,
name=name,
stream=stream,
version_prefix=version_prefix,
)
def dispatch_request(self, request, *args, **kwargs): def dispatch_request(self, request, *args, **kwargs):
handler = getattr(self, request.method.lower(), None) handler = getattr(self, request.method.lower(), None)
return handler(request, *args, **kwargs) return handler(request, *args, **kwargs)
@ -65,6 +106,31 @@ class HTTPMethodView:
view.__name__ = cls.__name__ view.__name__ = cls.__name__
return view return view
@classmethod
def attach(
cls,
to: Union[Sanic, Blueprint],
uri: str,
methods: Iterable[str] = frozenset({"GET"}),
host: Optional[str] = None,
strict_slashes: Optional[bool] = None,
version: Optional[int] = None,
name: Optional[str] = None,
stream: bool = False,
version_prefix: str = "/v",
) -> None:
to.add_route(
cls.as_view(),
uri=uri,
methods=methods,
host=host,
strict_slashes=strict_slashes,
version=version,
name=name,
stream=stream,
version_prefix=version_prefix,
)
def stream(func): def stream(func):
func.is_stream = True func.is_stream = True
@ -91,6 +157,11 @@ class CompositionView:
def __init__(self): def __init__(self):
self.handlers = {} self.handlers = {}
self.name = self.__class__.__name__ self.name = self.__class__.__name__
warn(
"CompositionView has been deprecated and will be removed in "
"v21.12. Please update your view to HTTPMethodView.",
DeprecationWarning,
)
def __name__(self): def __name__(self):
return self.name return self.name

View File

@ -77,6 +77,56 @@ def test_with_bp(app):
assert response.text == "I am get method" assert response.text == "I am get method"
def test_with_attach(app):
class DummyView(HTTPMethodView):
def get(self, request):
return text("I am get method")
DummyView.attach(app, "/")
request, response = app.test_client.get("/")
assert response.text == "I am get method"
def test_with_sub_init(app):
class DummyView(HTTPMethodView, attach=app, uri="/"):
def get(self, request):
return text("I am get method")
request, response = app.test_client.get("/")
assert response.text == "I am get method"
def test_with_attach_and_bp(app):
bp = Blueprint("test_text")
class DummyView(HTTPMethodView):
def get(self, request):
return text("I am get method")
DummyView.attach(bp, "/")
app.blueprint(bp)
request, response = app.test_client.get("/")
assert response.text == "I am get method"
def test_with_sub_init_and_bp(app):
bp = Blueprint("test_text")
class DummyView(HTTPMethodView, attach=bp, uri="/"):
def get(self, request):
return text("I am get method")
app.blueprint(bp)
request, response = app.test_client.get("/")
assert response.text == "I am get method"
def test_with_bp_with_url_prefix(app): def test_with_bp_with_url_prefix(app):
bp = Blueprint("test_text", url_prefix="/test1") bp = Blueprint("test_text", url_prefix="/test1")
@ -218,15 +268,15 @@ def test_composition_view_runs_methods_as_expected(app, method):
assert response.status == 200 assert response.status == 200
assert response.text == "first method" assert response.text == "first method"
# response = view(request) response = view(request)
# assert response.body.decode() == "first method" assert response.body.decode() == "first method"
# if method in ["DELETE", "PATCH"]: if method in ["DELETE", "PATCH"]:
# request, response = getattr(app.test_client, method.lower())("/") request, response = getattr(app.test_client, method.lower())("/")
# assert response.text == "second method" assert response.text == "second method"
# response = view(request) response = view(request)
# assert response.body.decode() == "second method" assert response.body.decode() == "second method"
@pytest.mark.parametrize("method", HTTP_METHODS) @pytest.mark.parametrize("method", HTTP_METHODS)
@ -244,3 +294,12 @@ def test_composition_view_rejects_invalid_methods(app, method):
if method in ["DELETE", "PATCH"]: if method in ["DELETE", "PATCH"]:
request, response = getattr(app.test_client, method.lower())("/") request, response = getattr(app.test_client, method.lower())("/")
assert response.status == 405 assert response.status == 405
def test_composition_view_deprecation():
message = (
"CompositionView has been deprecated and will be removed in v21.12. "
"Please update your view to HTTPMethodView."
)
with pytest.warns(DeprecationWarning, match=message):
CompositionView()