diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f69c879..7ca2cf10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.3] - 2022-02-06 :cat2: +* Adds support for [WebSocket](https://www.neoteroi.dev/blacksheep/websocket/) + ## [1.2.2] - 2021-12-03 :gift: * Fixes wrong mime type in OpenAPI Documentation for the `form` binder (#212) * Adds `OpenAPIEvents` to the class handling the generation of OpenAPI Documentation diff --git a/README.md b/README.md index 2da499bd..da8ebd82 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ Refer to the documentation for more details and examples. functions](https://www.neoteroi.dev/blacksheep/request-handlers/), or [class methods](https://www.neoteroi.dev/blacksheep/controllers/) * [Middlewares](https://www.neoteroi.dev/blacksheep/middlewares/) +* [WebSocket](https://www.neoteroi.dev/blacksheep/websocket/) * [Built-in support for dependency injection](https://www.neoteroi.dev/blacksheep/dependency-injection/) * [Support for automatic binding of route and query parameters to request diff --git a/blacksheep/server/bindings.py b/blacksheep/server/bindings.py index 56baa18d..d3984fc3 100644 --- a/blacksheep/server/bindings.py +++ b/blacksheep/server/bindings.py @@ -58,6 +58,24 @@ def __init__(self, class_name: str, overriding_class_name: str) -> None: ) +class NameAliasAlreadyDefinedException(BindingException): + def __init__(self, alias: str, overriding_class_name: str) -> None: + super().__init__( + f"There is already a name alias defined for '{alias}', " + f"the second type is: {overriding_class_name}" + ) + self.alias = alias + + +class TypeAliasAlreadyDefinedException(BindingException): + def __init__(self, alias: Any, overriding_class_name: str) -> None: + super().__init__( + f"There is already a type alias defined for '{alias.__name__}', " + f"the second type is: {overriding_class_name}" + ) + self.alias = alias + + class BinderNotRegisteredForValueType(BindingException): def __init__(self, value_type: Type["BoundValue"]) -> None: super().__init__( @@ -69,10 +87,23 @@ def __init__(self, value_type: Type["BoundValue"]) -> None: class BinderMeta(type): handlers: Dict[Type[Any], Type["Binder"]] = {} + aliases: Dict[Any, Callable[[Services], "Binder"]] = {} def __init__(cls, name, bases, attr_dict): super().__init__(name, bases, attr_dict) handle = getattr(cls, "handle", None) + name_alias = getattr(cls, "name_alias", None) + type_alias = getattr(cls, "type_alias", None) + + if name_alias: + if name_alias in cls.aliases: + raise NameAliasAlreadyDefinedException(name_alias, name) + cls.aliases[name_alias] = cls.from_alias # type: ignore + + if type_alias: + if type_alias in cls.aliases: + raise TypeAliasAlreadyDefinedException(type_alias, name) + cls.aliases[type_alias] = cls.from_alias # type: ignore if handle: if handle in cls.handlers: @@ -208,14 +239,16 @@ class RequestMethod(BoundValue[str]): """ -class Binder(metaclass=BinderMeta): +class Binder(metaclass=BinderMeta): # type: ignore handle: ClassVar[Type[BoundValue]] + name_alias: ClassVar[str] = "" + type_alias: ClassVar[Any] = None _implicit: bool default: Any def __init__( self, - expected_type: T, + expected_type: Any, name: str = "", implicit: bool = False, required: bool = True, @@ -229,6 +262,10 @@ def __init__( self.converter = converter self.default = empty + @classmethod + def from_alias(cls, services: Services): + return cls() # type: ignore + @property def implicit(self) -> bool: return self._implicit @@ -260,7 +297,7 @@ def generic_iterable_annotation_item_type(self, param_type): return str return item_type - async def get_parameter(self, request: Request) -> Union[T, BoundValue[T]]: + async def get_parameter(self, request: Request) -> Any: """ Gets a parameter to be passed to a request handler. @@ -297,7 +334,7 @@ def example(id: str): return self.handle(value) @abstractmethod - async def get_value(self, request: Request) -> Optional[T]: + async def get_value(self, request: Request) -> Any: """Gets a value from the given request object.""" @@ -789,18 +826,24 @@ async def get_value(self, request: Request) -> Optional[T]: class RequestBinder(Binder): + name_alias = "request" + type_alias = Request + def __init__(self, implicit: bool = True): super().__init__(Request, implicit=implicit) - async def get_value(self, request: Request) -> Optional[T]: + async def get_value(self, request: Request) -> Any: return request class WebSocketBinder(Binder): + name_alias = "websocket" + type_alias = WebSocket + def __init__(self, implicit: bool = True): super().__init__(WebSocket, implicit=implicit) - async def get_value(self, websocket: WebSocket) -> Optional[T]: + async def get_value(self, websocket: WebSocket) -> Optional[WebSocket]: return websocket @@ -820,6 +863,14 @@ async def get_value(self, request: Request) -> Any: return self.exact_object +class ServicesBinder(ExactBinder): + name_alias = "services" + + @classmethod + def from_alias(cls, services: Services) -> "ServicesBinder": + return cls(services) + + class ClientInfoBinder(Binder): handle = ClientInfo diff --git a/blacksheep/server/controllers.py b/blacksheep/server/controllers.py index 5b5df87a..59d4b2eb 100644 --- a/blacksheep/server/controllers.py +++ b/blacksheep/server/controllers.py @@ -46,6 +46,7 @@ trace = router.trace options = router.options connect = router.connect +ws = router.ws class CannotDetermineDefaultViewNameError(RuntimeError): diff --git a/blacksheep/server/normalization.py b/blacksheep/server/normalization.py index 2bdb53ea..69e585bf 100644 --- a/blacksheep/server/normalization.py +++ b/blacksheep/server/normalization.py @@ -26,21 +26,17 @@ from blacksheep.normalization import copy_special_attributes from blacksheep.server import responses from blacksheep.server.routing import Route -from blacksheep.server.websocket import WebSocket from .bindings import ( Binder, BodyBinder, BoundValue, ControllerBinder, - ExactBinder, IdentityBinder, JSONBinder, QueryBinder, - RequestBinder, RouteBinder, ServiceBinder, - WebSocketBinder, empty, get_binder_by_type, ) @@ -282,14 +278,8 @@ def _get_parameter_binder( ) -> Binder: name = parameter.name - if name == "request": - return RequestBinder() - - if name == "websocket": - return WebSocketBinder() - - if name == "services": - return ExactBinder(services) + if name in Binder.aliases: + return Binder.aliases[name](services) original_annotation = parameter.annotation @@ -302,11 +292,8 @@ def _get_parameter_binder( if isinstance(annotation, (str, ForwardRef)): # pragma: no cover raise UnsupportedForwardRefInSignatureError(original_annotation) - if annotation is Request: - return RequestBinder() - - if annotation is WebSocket: - return WebSocketBinder() + if annotation in Binder.aliases: + return Binder.aliases[annotation](services) # 1. is the type annotation of BoundValue[T] type? if _is_bound_value_annotation(annotation): diff --git a/blacksheep/server/openapi/common.py b/blacksheep/server/openapi/common.py index cb1b516d..3429eb0c 100644 --- a/blacksheep/server/openapi/common.py +++ b/blacksheep/server/openapi/common.py @@ -310,6 +310,11 @@ def router_to_paths_dict( routes_dictionary: Dict[str, Dict[str, T]] = {} for method, routes in router.routes.items(): + if b"_" in method: + # Non standard method, used internally to support more scenarios. + # This is used for WebSocket. + continue + for route in routes: key = route.mustache_pattern diff --git a/blacksheep/server/routing.py b/blacksheep/server/routing.py index a11b2705..c82cafb4 100644 --- a/blacksheep/server/routing.py +++ b/blacksheep/server/routing.py @@ -9,18 +9,20 @@ from blacksheep.utils import ensure_bytes, ensure_str __all__ = [ + "HTTPMethod", "Router", "Route", "RouteMatch", "RouteDuplicate", "RegisteredRoute", "RoutesRegistry", - "HTTPMethod", + "RouteMethod", ] -class HTTPMethod: +class RouteMethod: GET = "GET" + GET_WS = "GET_WS" HEAD = "HEAD" POST = "POST" PUT = "PUT" @@ -31,6 +33,10 @@ class HTTPMethod: PATCH = "PATCH" +# for backward compatibility +HTTPMethod = RouteMethod + + _route_all_rx = re.compile(b"\\*") _route_param_rx = re.compile(b"/:([^/]+)") _mustache_route_param_rx = re.compile(b"/{([^}]+)}") @@ -295,64 +301,64 @@ def decorator(fn): return decorator def add_head(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.HEAD, pattern, handler) + self.add(RouteMethod.HEAD, pattern, handler) def add_get(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.GET, pattern, handler) + self.add(RouteMethod.GET, pattern, handler) def add_post(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.POST, pattern, handler) + self.add(RouteMethod.POST, pattern, handler) def add_put(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.PUT, pattern, handler) + self.add(RouteMethod.PUT, pattern, handler) def add_delete(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.DELETE, pattern, handler) + self.add(RouteMethod.DELETE, pattern, handler) def add_trace(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.TRACE, pattern, handler) + self.add(RouteMethod.TRACE, pattern, handler) def add_options(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.OPTIONS, pattern, handler) + self.add(RouteMethod.OPTIONS, pattern, handler) def add_connect(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.CONNECT, pattern, handler) + self.add(RouteMethod.CONNECT, pattern, handler) def add_patch(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.PATCH, pattern, handler) + self.add(RouteMethod.PATCH, pattern, handler) def add_ws(self, pattern: str, handler: Callable[..., Any]) -> None: - self.add(HTTPMethod.GET, pattern, handler) + self.add(RouteMethod.GET_WS, pattern, handler) def head(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.HEAD, pattern) + return self.get_decorator(RouteMethod.HEAD, pattern) def get(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.GET, pattern) + return self.get_decorator(RouteMethod.GET, pattern) def post(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.POST, pattern) + return self.get_decorator(RouteMethod.POST, pattern) def put(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.PUT, pattern) + return self.get_decorator(RouteMethod.PUT, pattern) def delete(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.DELETE, pattern) + return self.get_decorator(RouteMethod.DELETE, pattern) def trace(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.TRACE, pattern) + return self.get_decorator(RouteMethod.TRACE, pattern) def options(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.OPTIONS, pattern) + return self.get_decorator(RouteMethod.OPTIONS, pattern) def connect(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.CONNECT, pattern) + return self.get_decorator(RouteMethod.CONNECT, pattern) def patch(self, pattern: Optional[str] = "/") -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.PATCH, pattern) + return self.get_decorator(RouteMethod.PATCH, pattern) def ws(self, pattern) -> Callable[..., Any]: - return self.get_decorator(HTTPMethod.GET, pattern) + return self.get_decorator(RouteMethod.GET_WS, pattern) class Router(RouterBase): @@ -444,7 +450,7 @@ def get_match(self, method: AnyStr, value: AnyStr) -> Optional[RouteMatch]: return RouteMatch(self._fallback, None) def get_ws_match(self, value: AnyStr) -> Optional[RouteMatch]: - return self.get_match(HTTPMethod.GET, value) + return self.get_match(RouteMethod.GET_WS, value) @lru_cache(maxsize=1200) def get_matching_route(self, method: AnyStr, value: AnyStr) -> Optional[Route]: diff --git a/setup.py b/setup.py index ddcb6830..5f28957e 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def readme(): setup( name="blacksheep", - version="1.2.2", + version="1.2.3", description="Fast web framework and HTTP client for Python asyncio", long_description=readme(), long_description_content_type="text/markdown", @@ -52,7 +52,9 @@ def readme(): ], ext_modules=[ Extension( - "blacksheep.url", ["blacksheep/url.c"], extra_compile_args=COMPILE_ARGS + "blacksheep.url", + ["blacksheep/url.c"], + extra_compile_args=COMPILE_ARGS, ), Extension( "blacksheep.exceptions", @@ -107,6 +109,7 @@ def readme(): "full": [ "cryptography~=35.0.0", "PyJWT~=2.3.0", + "websockets~=10.1", ] }, include_package_data=True, diff --git a/tests/test_bindings.py b/tests/test_bindings.py index 33ca5f51..96be7ad5 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -22,12 +22,14 @@ JSONBinder, MissingBodyError, MissingConverterError, + NameAliasAlreadyDefinedException, QueryBinder, RequestMethodBinder, RequestURLBinder, RouteBinder, ServiceBinder, SyncBinder, + TypeAliasAlreadyDefinedException, get_binder_by_type, ) from blacksheep.url import URL @@ -659,3 +661,35 @@ async def test_request_url_binder(url): parameter = RequestURLBinder() value = await parameter.get_value(request) assert value == URL(url) + + +def test_duplicate_name_alias_raises(): + class FooBinder1(Binder): + name_alias = "foo_absurd_example" + + with pytest.raises(NameAliasAlreadyDefinedException) as duplicate_alias_error: + + class FooBinder2(Binder): + name_alias = "foo_absurd_example" + + assert str(duplicate_alias_error.value) == ( + "There is already a name alias defined for 'foo_absurd_example', " + "the second type is: FooBinder2" + ) + + +def test_duplicate_type_alias_raises(): + class X: + pass + + class XBinder1(Binder): + type_alias = X + + with pytest.raises(TypeAliasAlreadyDefinedException) as duplicate_alias_error: + + class XBinder2(Binder): + type_alias = X + + assert str(duplicate_alias_error.value) == ( + "There is already a type alias defined for 'X', the second type is: XBinder2" + ) diff --git a/tests/test_controllers.py b/tests/test_controllers.py index eba430d2..b562752d 100644 --- a/tests/test_controllers.py +++ b/tests/test_controllers.py @@ -11,6 +11,7 @@ from blacksheep.server.controllers import ApiController, Controller, RoutesRegistry from blacksheep.server.responses import text from blacksheep.server.routing import RouteDuplicate +from blacksheep.server.websocket import WebSocket from blacksheep.testing.helpers import get_example_scope from blacksheep.testing.messages import MockReceive, MockSend from blacksheep.utils import ensure_str @@ -66,6 +67,32 @@ async def foo(self): assert body == "foo" +@pytest.mark.asyncio +async def test_ws_handler_through_controller(app): + app.controllers_router = RoutesRegistry() + ws = app.controllers_router.ws + + called = False + + class Home(Controller): + @ws("/web-socket") + async def foo(self, websocket): + nonlocal called + called = True + assert isinstance(self, Home) + assert isinstance(websocket, WebSocket) + await websocket.accept() + + app.setup_controllers() + await app( + {"type": "websocket", "path": "/web-socket"}, + MockReceive([{"type": "websocket.connect"}]), + MockSend(), + ) + + assert called is True + + @pytest.mark.asyncio @pytest.mark.parametrize( "path_one,path_two", diff --git a/tests/test_normalization.py b/tests/test_normalization.py index 5eba5059..9123efdd 100644 --- a/tests/test_normalization.py +++ b/tests/test_normalization.py @@ -9,6 +9,8 @@ from blacksheep import Request from blacksheep.server.bindings import ( + Binder, + ExactBinder, FromHeader, FromJSON, FromQuery, @@ -18,14 +20,13 @@ IdentityBinder, JSONBinder, QueryBinder, + RequestBinder, RouteBinder, ServiceBinder, ) from blacksheep.server.normalization import ( AmbiguousMethodSignatureError, - ExactBinder, NormalizationError, - RequestBinder, RouteBinderMismatch, UnsupportedSignatureError, _check_union, @@ -634,3 +635,24 @@ def handler(StatusKey: FromRoute[str]): assert isinstance(binders[0], RouteBinder) assert binders[0].parameter_name == "StatusKey" + + +def test_normalization_with_parameter_alias(): + class CustomBinder(Binder): + name_alias = "sunn_o" + + def __init__(self): + super().__init__(str) + + async def get_value(self, request: Request) -> Optional[str]: + return "sunn_o" + + def handler(sunn_o): + ... + + container = Container() + + binders = get_binders(Route("/", handler), container.build_provider()) + assert len(binders) == 1 + + assert isinstance(binders[0], CustomBinder) diff --git a/tests/test_router.py b/tests/test_router.py index e4f6ce90..b06064f5 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -2,12 +2,12 @@ from blacksheep.server.application import Application from blacksheep.server.routing import ( - HTTPMethod, InvalidValuePatternName, Mount, Route, RouteDuplicate, RouteException, + RouteMethod, Router, ) @@ -318,15 +318,15 @@ def b(): router.add_get("/a/*.js", a) router.add_get("/b/*.css", b) - m = router.get_match(HTTPMethod.GET, b"/a/anything/really") + m = router.get_match(RouteMethod.GET, b"/a/anything/really") assert m is None - m = router.get_match(HTTPMethod.GET, b"/a/anything/really.js") + m = router.get_match(RouteMethod.GET, b"/a/anything/really.js") assert m is not None assert m.handler is a assert m.values.get("tail") == "anything/really" - m = router.get_match(HTTPMethod.GET, b"/b/anything/really.css") + m = router.get_match(RouteMethod.GET, b"/b/anything/really.css") assert m is not None assert m.handler is b assert m.values.get("tail") == "anything/really" @@ -352,46 +352,46 @@ def d(): router.add_get("/c/*", c) router.add_get("/d/*", d) - m = router.get_match(HTTPMethod.GET, b"/a") + m = router.get_match(RouteMethod.GET, b"/a") assert m is not None assert m.handler is a assert m.values.get("tail") == "" - m = router.get_match(HTTPMethod.GET, b"/a/") + m = router.get_match(RouteMethod.GET, b"/a/") assert m is not None assert m.handler is a assert m.values.get("tail") == "" - m = router.get_match(HTTPMethod.GET, b"/a/anything/really") + m = router.get_match(RouteMethod.GET, b"/a/anything/really") assert m is not None assert m.handler is a assert m.values.get("tail") == "anything/really" - m = router.get_match(HTTPMethod.GET, b"/b/anything/really") + m = router.get_match(RouteMethod.GET, b"/b/anything/really") assert m is not None assert m.handler is b assert m.values.get("tail") == "anything/really" - m = router.get_match(HTTPMethod.GET, b"/c/anything/really") + m = router.get_match(RouteMethod.GET, b"/c/anything/really") assert m is not None assert m.handler is c assert m.values.get("tail") == "anything/really" - m = router.get_match(HTTPMethod.GET, b"/d/anything/really") + m = router.get_match(RouteMethod.GET, b"/d/anything/really") assert m is not None assert m.handler is d assert m.values.get("tail") == "anything/really" - m = router.get_match(HTTPMethod.POST, b"/a/anything/really") + m = router.get_match(RouteMethod.POST, b"/a/anything/really") assert m is None - m = router.get_match(HTTPMethod.POST, b"/b/anything/really") + m = router.get_match(RouteMethod.POST, b"/b/anything/really") assert m is None - m = router.get_match(HTTPMethod.POST, b"/c/anything/really") + m = router.get_match(RouteMethod.POST, b"/c/anything/really") assert m is None - m = router.get_match(HTTPMethod.POST, b"/d/anything/really") + m = router.get_match(RouteMethod.POST, b"/d/anything/really") assert m is None @@ -435,42 +435,63 @@ def ws(): router.add_delete("/foo", delete_foo) router.add_ws("/ws", ws) - m = router.get_match(HTTPMethod.GET, b"/") + m = router.get_match(RouteMethod.GET, b"/") assert m is not None assert m.handler is home - m = router.get_match(HTTPMethod.TRACE, b"/") + m = router.get_match(RouteMethod.TRACE, b"/") assert m is not None assert m.handler is home_verbose - m = router.get_match(HTTPMethod.CONNECT, b"/") + m = router.get_match(RouteMethod.CONNECT, b"/") assert m is not None assert m.handler is home_connect - m = router.get_match(HTTPMethod.OPTIONS, b"/") + m = router.get_match(RouteMethod.OPTIONS, b"/") assert m is not None assert m.handler is home_options - m = router.get_match(HTTPMethod.POST, b"/") + m = router.get_match(RouteMethod.POST, b"/") assert m is None - m = router.get_match(HTTPMethod.GET, b"/foo") + m = router.get_match(RouteMethod.GET, b"/foo") assert m is not None assert m.handler is get_foo - m = router.get_match(HTTPMethod.POST, b"/foo") + m = router.get_match(RouteMethod.POST, b"/foo") assert m is not None assert m.handler is create_foo - m = router.get_match(HTTPMethod.PATCH, b"/foo") + m = router.get_match(RouteMethod.PATCH, b"/foo") assert m is not None assert m.handler is patch_foo - m = router.get_match(HTTPMethod.DELETE, b"/foo") + m = router.get_match(RouteMethod.DELETE, b"/foo") assert m is not None assert m.handler is delete_foo - m = router.get_match(HTTPMethod.GET, b"/ws") + m = router.get_match(RouteMethod.GET_WS, b"/ws") + assert m is not None + assert m.handler is ws + + +def test_router_match_ws_get_sharing_path(): + router = Router() + + def home(): + ... + + def ws(): + ... + + router.add_get("/", home) + router.add_ws("/", ws) + + m = router.get_match(RouteMethod.GET, b"/") + assert m is not None + assert m.handler is home + + m = router.get_match(RouteMethod.GET_WS, b"/") assert m is not None assert m.handler is ws @@ -510,38 +531,38 @@ def patch_foo(): def delete_foo(): ... - m = router.get_match(HTTPMethod.GET, b"/") + m = router.get_match(RouteMethod.GET, b"/") assert m is not None assert m.handler is home - m = router.get_match(HTTPMethod.TRACE, b"/") + m = router.get_match(RouteMethod.TRACE, b"/") assert m is not None assert m.handler is home_verbose - m = router.get_match(HTTPMethod.CONNECT, b"/") + m = router.get_match(RouteMethod.CONNECT, b"/") assert m is not None assert m.handler is home_connect - m = router.get_match(HTTPMethod.OPTIONS, b"/") + m = router.get_match(RouteMethod.OPTIONS, b"/") assert m is not None assert m.handler is home_options - m = router.get_match(HTTPMethod.POST, b"/") + m = router.get_match(RouteMethod.POST, b"/") assert m is None - m = router.get_match(HTTPMethod.GET, b"/foo") + m = router.get_match(RouteMethod.GET, b"/foo") assert m is not None assert m.handler is get_foo - m = router.get_match(HTTPMethod.POST, b"/foo") + m = router.get_match(RouteMethod.POST, b"/foo") assert m is not None assert m.handler is create_foo - m = router.get_match(HTTPMethod.PATCH, b"/foo") + m = router.get_match(RouteMethod.PATCH, b"/foo") assert m is not None assert m.handler is patch_foo - m = router.get_match(HTTPMethod.DELETE, b"/foo") + m = router.get_match(RouteMethod.DELETE, b"/foo") assert m is not None assert m.handler is delete_foo @@ -558,17 +579,17 @@ def create_foo(): router.add_get("/foo", get_foo) router.add_post("/foo", create_foo) - m = router.get_match(HTTPMethod.GET, b"/foo/") + m = router.get_match(RouteMethod.GET, b"/foo/") assert m is not None assert m.handler is get_foo - m = router.get_match(HTTPMethod.POST, b"/foo/") + m = router.get_match(RouteMethod.POST, b"/foo/") assert m is not None assert m.handler is create_foo - m = router.get_match(HTTPMethod.POST, b"/foo//") + m = router.get_match(RouteMethod.POST, b"/foo//") assert m is None @@ -583,7 +604,7 @@ def not_found_handler(): assert isinstance(router.fallback, Route) assert router.fallback.handler is not_found_handler - m = router.get_match(HTTPMethod.POST, b"/") + m = router.get_match(RouteMethod.POST, b"/") assert m is not None assert m.handler is not_found_handler