diff --git a/docs/features.md b/docs/features.md index fe4c17703..2add9bac8 100644 --- a/docs/features.md +++ b/docs/features.md @@ -559,6 +559,18 @@ app.add_view("/", View) ``` +## Route Registration + +Instead of using the decorators, you can also add routes with a function: + +```python +async def hello(request): + return "Hello World" + +app.add_route("GET", "/hello", hello) +``` + +This works for all HTTP methods. ## Allow CORS diff --git a/integration_tests/base_routes.py b/integration_tests/base_routes.py index 4eeea0344..e3563ef2b 100644 --- a/integration_tests/base_routes.py +++ b/integration_tests/base_routes.py @@ -4,7 +4,15 @@ from collections import defaultdict from typing import Optional -from robyn import WS, Robyn, Request, Response, jsonify, serve_file, serve_html +from robyn import ( + Request, + Response, + Robyn, + WS, + jsonify, + serve_file, + serve_html, +) from robyn.authentication import AuthenticationHandler, BearerGetter, Identity from robyn.templating import JinjaTemplate @@ -714,6 +722,24 @@ async def async_auth(request: Request): # ===== Main ===== +def sync_without_decorator(): + return "Success!" + + +async def async_without_decorator(): + return "Success!" + + +app.add_route("GET", "/sync/get/no_dec", sync_without_decorator) +app.add_route("PUT", "/sync/put/no_dec", sync_without_decorator) +app.add_route("POST", "/sync/post/no_dec", sync_without_decorator) +app.add_route("GET", "/async/get/no_dec", async_without_decorator) +app.add_route("PUT", "/async/put/no_dec", async_without_decorator) +app.add_route("POST", "/async/post/no_dec", async_without_decorator) + +# ===== Main ===== + + def main(): app.add_response_header("server", "robyn") app.add_directory( diff --git a/integration_tests/test_add_route_without_decorator.py b/integration_tests/test_add_route_without_decorator.py new file mode 100644 index 000000000..bdb4c1a85 --- /dev/null +++ b/integration_tests/test_add_route_without_decorator.py @@ -0,0 +1,21 @@ +from collections.abc import Callable +import pytest +from integration_tests.helpers.http_methods_helpers import get, post, put + + +@pytest.mark.benchmark +@pytest.mark.usefixtures("session") +@pytest.mark.parametrize( + "route,method", + [ + ("/sync/get/no_dec", get), + ("/async/get/no_dec", get), + ("/sync/put/no_dec", put), + ("/async/put/no_dec", put), + ("/sync/post/no_dec", post), + ("/async/post/no_dec", post), + ], +) +def test_exception_handling(route: str, method: Callable): + r = method(route, expected_status_code=200) + assert r.text == "Success!" diff --git a/robyn/__init__.py b/robyn/__init__.py index a229b27cb..ca54a5e2f 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -2,7 +2,7 @@ import logging import multiprocess as mp import os -from typing import Callable, List, Optional, Tuple +from typing import Callable, List, Optional, Tuple, Union from nestd import get_all_nested @@ -15,7 +15,14 @@ from robyn.logger import logger from robyn.processpool import run_processes from robyn.responses import serve_file, serve_html -from robyn.robyn import FunctionInfo, HttpMethod, Request, Response, get_version, jsonify +from robyn.robyn import ( + FunctionInfo, + HttpMethod, + Request, + Response, + get_version, + jsonify, +) from robyn.router import MiddlewareRouter, MiddlewareType, Router, WebSocketRouter from robyn.types import Directory, Header from robyn import status_codes @@ -58,16 +65,16 @@ def __init__(self, file_object: str, config: Config = Config()) -> None: self.exception_handler: Optional[Callable] = None self.authentication_handler: Optional[AuthenticationHandler] = None - def _add_route( + def add_route( self, - route_type: HttpMethod, + route_type: Union[HttpMethod, str], endpoint: str, handler: Callable, is_const: bool = False, auth_required: bool = False, ): """ - This is base handler for all the route decorators + Connect a URI to a handler :param route_type str: route type between GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/TRACE :param endpoint str: endpoint for the route added @@ -80,6 +87,17 @@ def _add_route( """ if auth_required: self.middleware_router.add_auth_middleware(endpoint)(handler) + if isinstance(route_type, str): + http_methods = { + "GET": HttpMethod.GET, + "POST": HttpMethod.POST, + "PUT": HttpMethod.PUT, + "DELETE": HttpMethod.DELETE, + "PATCH": HttpMethod.PATCH, + "HEAD": HttpMethod.HEAD, + "OPTIONS": HttpMethod.OPTIONS, + } + route_type = http_methods[route_type] return self.router.add_route( route_type, endpoint, handler, is_const, self.exception_handler @@ -205,7 +223,7 @@ def get_functions(view) -> List[Tuple[HttpMethod, Callable]]: handlers = get_functions(view) for route_type, handler in handlers: - self._add_route(route_type, endpoint, handler, const) + self.add_route(route_type, endpoint, handler, const) def view(self, endpoint: str, const: bool = False): """ @@ -227,7 +245,7 @@ def get(self, endpoint: str, const: bool = False, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.GET, endpoint, handler, const, auth_required ) @@ -241,7 +259,7 @@ def post(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.POST, endpoint, handler, auth_required=auth_required ) @@ -255,7 +273,7 @@ def put(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.PUT, endpoint, handler, auth_required=auth_required ) @@ -269,7 +287,7 @@ def delete(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.DELETE, endpoint, handler, auth_required=auth_required ) @@ -283,7 +301,7 @@ def patch(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.PATCH, endpoint, handler, auth_required=auth_required ) @@ -297,7 +315,7 @@ def head(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.HEAD, endpoint, handler, auth_required=auth_required ) @@ -311,7 +329,7 @@ def options(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.OPTIONS, endpoint, handler, auth_required=auth_required ) @@ -325,7 +343,7 @@ def connect(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.CONNECT, endpoint, handler, auth_required=auth_required ) @@ -339,7 +357,7 @@ def trace(self, endpoint: str, auth_required: bool = False): """ def inner(handler): - return self._add_route( + return self.add_route( HttpMethod.TRACE, endpoint, handler, auth_required=auth_required )