From 1081d9016e1d60e5b8328b03f6811e3be110d6ae Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 10:43:08 +0000 Subject: [PATCH 01/29] Fix mypy warning "Incompatible default for argument "openapi_file_path" (default has type "None", argument has type "str")" --- robyn/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/__init__.py b/robyn/__init__.py index f5222acd..4ee9f6b8 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -42,7 +42,7 @@ def __init__( self, file_object: str, config: Config = Config(), - openapi_file_path: str = None, + openapi_file_path: Optional[str] = None, openapi: OpenAPI = OpenAPI(), dependencies: DependencyMap = DependencyMap(), ) -> None: From 4e8dc438180d13af94df34261bdc8bd962beb732 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 11:54:00 +0000 Subject: [PATCH 02/29] fix event_handler type --- robyn/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/__init__.py b/robyn/__init__.py index 4ee9f6b8..02da8896 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -79,7 +79,7 @@ def __init__( self.response_headers: Headers = Headers({}) self.excluded_response_headers_paths: Optional[List[str]] = None self.directories: List[Directory] = [] - self.event_handlers = {} + self.event_handlers: dict = {} self.exception_handler: Optional[Callable] = None self.authentication_handler: Optional[AuthenticationHandler] = None From f97e49daf9f643ccb5f4df3f53f3f5f5299fb127 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 11:59:19 +0000 Subject: [PATCH 03/29] Fix subrouter http methods to match Robyn with auth_required --- robyn/__init__.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/robyn/__init__.py b/robyn/__init__.py index 02da8896..154909cf 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -596,29 +596,29 @@ def __init__(self, file_object: str, prefix: str = "", config: Config = Config() def __add_prefix(self, endpoint: str): return f"{self.prefix}{endpoint}" - def get(self, endpoint: str, const: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["get"]): - return super().get(endpoint=self.__add_prefix(endpoint), const=const, openapi_name=openapi_name, openapi_tags=openapi_tags) + def get(self, endpoint: str, const: bool = False, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["get"]): + return super().get(endpoint=self.__add_prefix(endpoint), const=const, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def post(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["post"]): - return super().post(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def post(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["post"]): + return super().post(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def put(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["put"]): - return super().put(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def put(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["put"]): + return super().put(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def delete(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["delete"]): - return super().delete(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def delete(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["delete"]): + return super().delete(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def patch(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["patch"]): - return super().patch(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def patch(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["patch"]): + return super().patch(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def head(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["head"]): - return super().head(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def head(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["head"]): + return super().head(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def trace(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["trace"]): - return super().trace(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def trace(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["trace"]): + return super().trace(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) - def options(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["options"]): - return super().options(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) + def options(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["options"]): + return super().options(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) def ALLOW_CORS(app: Robyn, origins: List[str]): From e78c6f9dc7c1cefa56916e115414e0695af8d96f Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 12:22:10 +0000 Subject: [PATCH 04/29] work around for mutiprocess typing from https://github.com/uqfoundation/multiprocess/issues/128#issuecomment-2188208560 --- robyn/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/robyn/__init__.py b/robyn/__init__.py index 154909cf..ad0906ad 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -3,9 +3,13 @@ import os import socket from pathlib import Path -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING + +if TYPE_CHECKING: + import multiprocessing as mp +else: + import multiprocess as mp -import multiprocess as mp from nestd import get_all_nested from robyn import status_codes From 75cefeb5d7e41c18c91922671046abeb73e097f0 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 12:43:47 +0000 Subject: [PATCH 05/29] added comment on why double import multiprocess(ing) --- robyn/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/robyn/__init__.py b/robyn/__init__.py index ad0906ad..b9db4207 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -5,6 +5,8 @@ from pathlib import Path from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING +# Workaround while multiprocess does not support mypy type checking +# see https://github.com/uqfoundation/multiprocess/issues/128#issuecomment-2188208560 if TYPE_CHECKING: import multiprocessing as mp else: From b04fec7ffc5cbef7300a3d3adb000fc8d905cc41 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 15:37:26 +0000 Subject: [PATCH 06/29] failing tests for subrouter authentication: AuthenticationNotConfiguredError: Authentication is not configured. Use app.configure_authentication() to configure it. --- integration_tests/subroutes/di_subrouter.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/integration_tests/subroutes/di_subrouter.py b/integration_tests/subroutes/di_subrouter.py index 0b7de6da..a0236342 100644 --- a/integration_tests/subroutes/di_subrouter.py +++ b/integration_tests/subroutes/di_subrouter.py @@ -15,3 +15,20 @@ def sync_subrouter_route_dependency(r: Request, router_dependencies, global_depe @di_subrouter.get("/subrouter_global_di") def sync_subrouter_global_dependency(global_dependencies): return global_dependencies["GLOBAL_DEPENDENCY"] + + +# ===== Authentication ===== + + +@di_subrouter.get("/subrouter_router_di/sync/auth", auth_required=True) +def sync_subrouter_auth(request: Request): + assert request.identity is not None + assert request.identity.claims == {"key": "value"} + return "authenticated" + + +@di_subrouter.get("/subrouter_router_di/async/auth", auth_required=True) +async def async_subrouter_auth(request: Request): + assert request.identity is not None + assert request.identity.claims == {"key": "value"} + return "authenticated" From fb192cd74296061e9d52662042bc9800126896ec Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 15:37:33 +0000 Subject: [PATCH 07/29] failing tests for subrouter authentication: AuthenticationNotConfiguredError: Authentication is not configured. Use app.configure_authentication() to configure it. --- .../test_subrouter_authentication.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 integration_tests/test_subrouter_authentication.py diff --git a/integration_tests/test_subrouter_authentication.py b/integration_tests/test_subrouter_authentication.py new file mode 100644 index 00000000..77bdf7ef --- /dev/null +++ b/integration_tests/test_subrouter_authentication.py @@ -0,0 +1,42 @@ +import pytest + +from integration_tests.helpers.http_methods_helpers import get + + +@pytest.mark.benchmark +@pytest.mark.parametrize("function_type", ["sync", "async"]) +def test_valid_subroute_authentication(session, function_type: str): + r = get(f"/sub_router/{function_type}/auth", headers={"Authorization": "Bearer valid"}) + assert r.text == "authenticated" + + +@pytest.mark.benchmark +@pytest.mark.parametrize("function_type", ["sync", "async"]) +def test_invalid_subroute_authentication_token(session, function_type: str): + r = get( + f"/sub_router/{function_type}/auth", + headers={"Authorization": "Bearer invalid"}, + should_check_response=False, + ) + assert r.status_code == 401 + assert r.headers.get("WWW-Authenticate") == "BearerGetter" + + +@pytest.mark.benchmark +@pytest.mark.parametrize("function_type", ["sync", "async"]) +def test_invalid_subroute_authentication_header(session, function_type: str): + r = get( + f"/sub_router/{function_type}/auth", + headers={"Authorization": "Bear valid"}, + should_check_response=False, + ) + assert r.status_code == 401 + assert r.headers.get("WWW-Authenticate") == "BearerGetter" + + +@pytest.mark.benchmark +@pytest.mark.parametrize("function_type", ["sync", "async"]) +def test_invalid_subroute_authentication_no_token(session, function_type: str): + r = get(f"/sub_router/{function_type}/auth", should_check_response=False) + assert r.status_code == 401 + assert r.headers.get("WWW-Authenticate") == "BearerGetter" From 02535849c9d4c94df51117c1c5643752aecab322 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 15:37:41 +0000 Subject: [PATCH 08/29] failing tests for subrouter authentication: AuthenticationNotConfiguredError: Authentication is not configured. Use app.configure_authentication() to configure it. --- integration_tests/subroutes/__init__.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/integration_tests/subroutes/__init__.py b/integration_tests/subroutes/__init__.py index 3ebab110..adc8d94c 100644 --- a/integration_tests/subroutes/__init__.py +++ b/integration_tests/subroutes/__init__.py @@ -1,4 +1,4 @@ -from robyn import SubRouter, WebSocket, jsonify +from robyn import SubRouter, WebSocket, jsonify, Request from .di_subrouter import di_subrouter @@ -68,3 +68,20 @@ def head_foo(): def sample_subrouter_openapi_endpoint(): """Get subrouter openapi""" return 200 + + +# ===== Authentication ===== + + +@sub_router.get("/sync/auth", auth_required=True) +def sync_auth(request: Request): + assert request.identity is not None + assert request.identity.claims == {"key": "value"} + return "authenticated" + + +@sub_router.get("/async/auth", auth_required=True) +async def async_auth(request: Request): + assert request.identity is not None + assert request.identity.claims == {"key": "value"} + return "authenticated" From 8d5f65473a7954d2fb8112d5ed0bae543744c3b4 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 18:21:38 +0000 Subject: [PATCH 09/29] use headers.contain instead of in headers --- robyn/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/authentication.py b/robyn/authentication.py index 5dd76af5..dc01edf7 100644 --- a/robyn/authentication.py +++ b/robyn/authentication.py @@ -81,7 +81,7 @@ class BearerGetter(TokenGetter): @classmethod def get_token(cls, request: Request) -> Optional[str]: - if "authorization" in request.headers: + if request.headers.contains("authorization"): authorization_header = request.headers.get("authorization") else: authorization_header = None From 22f3d50c9752b57d1b35c13d9eba1d71f7778462 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 18:42:57 +0000 Subject: [PATCH 10/29] mypy detected os.cpu_count can return none in which case we tried to multiple None by 2 --- robyn/argument_parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/robyn/argument_parser.py b/robyn/argument_parser.py index 88c93ae7..2232c55b 100644 --- a/robyn/argument_parser.py +++ b/robyn/argument_parser.py @@ -103,7 +103,8 @@ def __init__(self) -> None: if self.fast: # doing this here before every other check # so that processes, workers and log_level can be overridden - self.processes = self.processes or ((os.cpu_count() * 2) + 1) or 1 + cpu_count: int = os.cpu_count() or 1 + self.processes = self.processes or ((cpu_count * 2) + 1) or 1 self.workers = self.workers or 2 self.log_level = self.log_level or "WARNING" From cbcbd630ff9e37abc84de3b89771e92ce0668ab0 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 19:56:14 +0000 Subject: [PATCH 11/29] add return type None to def __init__ so get type checking inside the method --- robyn/dependency_injection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/dependency_injection.py b/robyn/dependency_injection.py index 4cb18e4b..c1442db6 100644 --- a/robyn/dependency_injection.py +++ b/robyn/dependency_injection.py @@ -4,7 +4,7 @@ class DependencyMap: - def __init__(self): + def __init__(self) -> None: self.global_dependency_map: dict[str, Any] = {} # {'router': {'dependency_name': dependency_class} self.router_dependency_map: dict[str, dict[str, Any]] = {} From 13282d4346156af4a2e8777daa2f07fda0d5d04b Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 20:18:12 +0000 Subject: [PATCH 12/29] Add list type to self.built_rust_binaries --- robyn/reloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/reloader.py b/robyn/reloader.py index bef6197c..ddb52636 100644 --- a/robyn/reloader.py +++ b/robyn/reloader.py @@ -118,7 +118,7 @@ def __init__(self, file_path: str, directory_path: str) -> None: self.file_path = file_path self.directory_path = directory_path self.process = None # Keep track of the subprocess - self.built_rust_binaries = [] # Keep track of the built rust binaries + self.built_rust_binaries: List = [] # Keep track of the built rust binaries self.last_reload = time.time() # Keep track of the last reload. EventHandler is initialized with the process. From f98216457f76dbfe34bf993efd515ac707ac4052 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 20:22:45 +0000 Subject: [PATCH 13/29] Fixed some mypy issues in openapi.py but more remain --- robyn/openapi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index 5d4868c3..a6c5bf1b 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, TypedDict -from robyn.responses import FileResponse, html +from robyn.responses import html from robyn.robyn import QueryParams, Response from robyn.types import Body @@ -239,13 +239,13 @@ def get_path_obj( query_params: Optional[TypedDict], request_body: Optional[TypedDict], return_annotation: Optional[TypedDict], - ) -> (str, dict): + ) -> tuple[str, dict]: """ Get the "path" openapi object according to spec @param endpoint: str the endpoint to be added @param name: str the name of the endpoint - @param description: Optional[str] short description of the endpoint (to be fetched from the endpoint defenition by default) + @param description: Optional[str] short description of the endpoint (to be fetched from the endpoint definition by default) @param tags: List[str] for grouping of endpoints @param query_params: Optional[TypedDict] query params for the function @param request_body: Optional[TypedDict] request body for the function @@ -328,7 +328,7 @@ def get_path_obj( openapi_path_object["requestBody"] = request_body_object - response_schema = {} + response_schema: dict = {} response_type = "text/plain" if return_annotation and return_annotation is not Response: @@ -416,7 +416,7 @@ def override_openapi(self, openapi_json_spec_path: Path): self.openapi_spec = dict(json_file_content) self.openapi_file_override = True - def get_openapi_docs_page(self) -> FileResponse: + def get_openapi_docs_page(self) -> Response: """ Handler to the swagger html page to be deployed to the endpoint `/docs` @return: FileResponse the swagger html page From 38d7c6076d161a542618a365f5bbb21baa1be049 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Mon, 11 Nov 2024 20:23:46 +0000 Subject: [PATCH 14/29] Fixed some mypy issues but some remain (Line 196&197) --- robyn/processpool.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/robyn/processpool.py b/robyn/processpool.py index 1391d21b..9f485f0e 100644 --- a/robyn/processpool.py +++ b/robyn/processpool.py @@ -2,9 +2,14 @@ import signal import sys import webbrowser -from typing import Dict, List, Optional +from typing import Dict, List, Optional, TYPE_CHECKING -from multiprocess import Process +# Workaround while multiprocess does not support mypy type checking +# see https://github.com/uqfoundation/multiprocess/issues/128#issuecomment-2188208560 +if TYPE_CHECKING: + from multiprocessing import Process +else: + from multiprocess import Process from robyn.events import Events from robyn.logger import logger @@ -80,7 +85,7 @@ def init_processpool( response_headers: Headers, excluded_response_headers_paths: Optional[List[str]], ) -> List[Process]: - process_pool = [] + process_pool: List = [] if sys.platform.startswith("win32") or processes == 1: spawn_process( directories, From 4a222bd8878533bc7faabc680865c4b8b48a98fc Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Tue, 12 Nov 2024 03:41:26 +0000 Subject: [PATCH 15/29] correct get_openapi_docs_page to return a FileResponse --- robyn/openapi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index a6c5bf1b..17799108 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -7,8 +7,8 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, TypedDict -from robyn.responses import html -from robyn.robyn import QueryParams, Response +from robyn.responses import FileResponse +from robyn.robyn import QueryParams, Response, Headers from robyn.types import Body @@ -416,14 +416,14 @@ def override_openapi(self, openapi_json_spec_path: Path): self.openapi_spec = dict(json_file_content) self.openapi_file_override = True - def get_openapi_docs_page(self) -> Response: + def get_openapi_docs_page(self) -> FileResponse: """ Handler to the swagger html page to be deployed to the endpoint `/docs` @return: FileResponse the swagger html page """ with resources.open_text("robyn", "swagger.html") as path: html_file = path.read() - return html(html_file) + return FileResponse(html_file, headers=Headers({"Content-Type": "text/html"})) def get_openapi_config(self) -> dict: """ From df289ec13f7781fe7348a640a341f3de7d917c17 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Tue, 12 Nov 2024 03:59:55 +0000 Subject: [PATCH 16/29] ignore type for multiprocess and nestd --- robyn/__init__.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/robyn/__init__.py b/robyn/__init__.py index 9df4be8f..82e3184a 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -3,16 +3,10 @@ import os import socket from pathlib import Path -from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Tuple, Union -# Workaround while multiprocess does not support mypy type checking -# see https://github.com/uqfoundation/multiprocess/issues/128#issuecomment-2188208560 -if TYPE_CHECKING: - import multiprocessing as mp -else: - import multiprocess as mp - -from nestd import get_all_nested +import multiprocess as mp # type: ignore +from nestd import get_all_nested # type: ignore from robyn import status_codes from robyn.argument_parser import Config From 562a05dc5a30e1da7ebab4e2051f6662a842f6e3 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Tue, 12 Nov 2024 04:00:01 +0000 Subject: [PATCH 17/29] ignore type for multiprocess --- robyn/processpool.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/robyn/processpool.py b/robyn/processpool.py index 9f485f0e..54673889 100644 --- a/robyn/processpool.py +++ b/robyn/processpool.py @@ -2,14 +2,9 @@ import signal import sys import webbrowser -from typing import Dict, List, Optional, TYPE_CHECKING - -# Workaround while multiprocess does not support mypy type checking -# see https://github.com/uqfoundation/multiprocess/issues/128#issuecomment-2188208560 -if TYPE_CHECKING: - from multiprocessing import Process -else: - from multiprocess import Process +from typing import Dict, List, Optional + +from multiprocess import Process # type: ignore from robyn.events import Events from robyn.logger import logger From 719c34480fd273ec8de9e6be08c3e0f40c605017 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Wed, 13 Nov 2024 20:26:45 +0000 Subject: [PATCH 18/29] Use typing.Tuple for Python 3.8 compatibility --- robyn/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index 17799108..98942f58 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -239,7 +239,7 @@ def get_path_obj( query_params: Optional[TypedDict], request_body: Optional[TypedDict], return_annotation: Optional[TypedDict], - ) -> tuple[str, dict]: + ) -> typing.Tuple[str, dict]: """ Get the "path" openapi object according to spec From 947d4dbb30e1aacec3588e23c0504305346f914f Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Thu, 14 Nov 2024 00:09:10 +0000 Subject: [PATCH 19/29] Fix return type to match Response from html instead of FileResponse --- robyn/openapi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index 98942f58..8df96367 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -7,8 +7,8 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, TypedDict -from robyn.responses import FileResponse -from robyn.robyn import QueryParams, Response, Headers +from robyn.responses import html +from robyn.robyn import QueryParams, Response from robyn.types import Body @@ -416,14 +416,14 @@ def override_openapi(self, openapi_json_spec_path: Path): self.openapi_spec = dict(json_file_content) self.openapi_file_override = True - def get_openapi_docs_page(self) -> FileResponse: + def get_openapi_docs_page(self) -> Response: """ Handler to the swagger html page to be deployed to the endpoint `/docs` @return: FileResponse the swagger html page """ with resources.open_text("robyn", "swagger.html") as path: html_file = path.read() - return FileResponse(html_file, headers=Headers({"Content-Type": "text/html"})) + return html(html_file) def get_openapi_config(self) -> dict: """ From e350e5c8d322516fe70fb3da3b2cb0cb2d39782f Mon Sep 17 00:00:00 2001 From: Sanskar Jethi <29942790+sansyrox@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:48:03 +0000 Subject: [PATCH 20/29] Update robyn/openapi.py --- robyn/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index 8df96367..a718b78c 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -5,7 +5,7 @@ from importlib import resources from inspect import Signature from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, TypedDict +from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict from robyn.responses import html from robyn.robyn import QueryParams, Response From 4712d1ff548286b81f1fa485c12c9edc9425cfbe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:48:09 +0000 Subject: [PATCH 21/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- robyn/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index a718b78c..8df96367 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -5,7 +5,7 @@ from importlib import resources from inspect import Signature from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict +from typing import Any, Callable, Dict, List, Optional, TypedDict from robyn.responses import html from robyn.robyn import QueryParams, Response From 7221ce412f769c2d1b48f8fc93ece09b90f51391 Mon Sep 17 00:00:00 2001 From: Sanskar Jethi <29942790+sansyrox@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:48:23 +0000 Subject: [PATCH 22/29] Update robyn/openapi.py --- robyn/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index 8df96367..2008cef7 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -239,7 +239,7 @@ def get_path_obj( query_params: Optional[TypedDict], request_body: Optional[TypedDict], return_annotation: Optional[TypedDict], - ) -> typing.Tuple[str, dict]: + ) -> Tuple[str, dict]: """ Get the "path" openapi object according to spec From d3de8e766fcf230a85c3663caafaf002776fe8b9 Mon Sep 17 00:00:00 2001 From: Sanskar Jethi <29942790+sansyrox@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:48:46 +0000 Subject: [PATCH 23/29] Update robyn/openapi.py --- robyn/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robyn/openapi.py b/robyn/openapi.py index 2008cef7..be5aeecb 100644 --- a/robyn/openapi.py +++ b/robyn/openapi.py @@ -5,7 +5,7 @@ from importlib import resources from inspect import Signature from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, TypedDict +from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict from robyn.responses import html from robyn.robyn import QueryParams, Response From 8ea3de670fea8389a77289f2272367b90106740b Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Thu, 14 Nov 2024 02:26:52 +0000 Subject: [PATCH 24/29] Delete integration_tests/test_subrouter_authentication.py This should fix CI --- .../test_subrouter_authentication.py | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 integration_tests/test_subrouter_authentication.py diff --git a/integration_tests/test_subrouter_authentication.py b/integration_tests/test_subrouter_authentication.py deleted file mode 100644 index 77bdf7ef..00000000 --- a/integration_tests/test_subrouter_authentication.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest - -from integration_tests.helpers.http_methods_helpers import get - - -@pytest.mark.benchmark -@pytest.mark.parametrize("function_type", ["sync", "async"]) -def test_valid_subroute_authentication(session, function_type: str): - r = get(f"/sub_router/{function_type}/auth", headers={"Authorization": "Bearer valid"}) - assert r.text == "authenticated" - - -@pytest.mark.benchmark -@pytest.mark.parametrize("function_type", ["sync", "async"]) -def test_invalid_subroute_authentication_token(session, function_type: str): - r = get( - f"/sub_router/{function_type}/auth", - headers={"Authorization": "Bearer invalid"}, - should_check_response=False, - ) - assert r.status_code == 401 - assert r.headers.get("WWW-Authenticate") == "BearerGetter" - - -@pytest.mark.benchmark -@pytest.mark.parametrize("function_type", ["sync", "async"]) -def test_invalid_subroute_authentication_header(session, function_type: str): - r = get( - f"/sub_router/{function_type}/auth", - headers={"Authorization": "Bear valid"}, - should_check_response=False, - ) - assert r.status_code == 401 - assert r.headers.get("WWW-Authenticate") == "BearerGetter" - - -@pytest.mark.benchmark -@pytest.mark.parametrize("function_type", ["sync", "async"]) -def test_invalid_subroute_authentication_no_token(session, function_type: str): - r = get(f"/sub_router/{function_type}/auth", should_check_response=False) - assert r.status_code == 401 - assert r.headers.get("WWW-Authenticate") == "BearerGetter" From fd4cc8054db789bac2e51b0109cb042fe35426e4 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Fri, 15 Nov 2024 17:45:11 +0000 Subject: [PATCH 25/29] Delete integration_tests/subroutes/__init__.py --- integration_tests/subroutes/__init__.py | 87 ------------------------- 1 file changed, 87 deletions(-) delete mode 100644 integration_tests/subroutes/__init__.py diff --git a/integration_tests/subroutes/__init__.py b/integration_tests/subroutes/__init__.py deleted file mode 100644 index adc8d94c..00000000 --- a/integration_tests/subroutes/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -from robyn import SubRouter, WebSocket, jsonify, Request - -from .di_subrouter import di_subrouter - -sub_router = SubRouter(__name__, prefix="/sub_router") - -websocket = WebSocket(sub_router, "/ws") - -__all__ = ["sub_router", "websocket", "di_subrouter"] - - -@websocket.on("connect") -async def connect(ws): - return "Hello world, from ws" - - -@websocket.on("message") -async def message(): - return "Message" - - -@websocket.on("close") -async def close(ws): - return jsonify({"message": "closed"}) - - -@sub_router.get("/foo") -def get_foo(): - return {"message": "foo"} - - -@sub_router.post("/foo") -def post_foo(): - return {"message": "foo"} - - -@sub_router.put("/foo") -def put_foo(): - return {"message": "foo"} - - -@sub_router.delete("/foo") -def delete_foo(): - return {"message": "foo"} - - -@sub_router.patch("/foo") -def patch_foo(): - return {"message": "foo"} - - -@sub_router.options("/foo") -def option_foo(): - return {"message": "foo"} - - -@sub_router.trace("/foo") -def trace_foo(): - return {"message": "foo"} - - -@sub_router.head("/foo") -def head_foo(): - return {"message": "foo"} - - -@sub_router.post("/openapi_test", openapi_tags=["test subrouter tag"]) -def sample_subrouter_openapi_endpoint(): - """Get subrouter openapi""" - return 200 - - -# ===== Authentication ===== - - -@sub_router.get("/sync/auth", auth_required=True) -def sync_auth(request: Request): - assert request.identity is not None - assert request.identity.claims == {"key": "value"} - return "authenticated" - - -@sub_router.get("/async/auth", auth_required=True) -async def async_auth(request: Request): - assert request.identity is not None - assert request.identity.claims == {"key": "value"} - return "authenticated" From 1885e4a821b07f5dde7b0a57ed403fafd8ed1f4a Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Fri, 15 Nov 2024 17:47:30 +0000 Subject: [PATCH 26/29] Delete integration_tests/subroutes/di_subrouter.py --- integration_tests/subroutes/di_subrouter.py | 34 --------------------- 1 file changed, 34 deletions(-) delete mode 100644 integration_tests/subroutes/di_subrouter.py diff --git a/integration_tests/subroutes/di_subrouter.py b/integration_tests/subroutes/di_subrouter.py deleted file mode 100644 index a0236342..00000000 --- a/integration_tests/subroutes/di_subrouter.py +++ /dev/null @@ -1,34 +0,0 @@ -from robyn import Request, SubRouter - -di_subrouter = SubRouter(__file__, "/di_subrouter") -GLOBAL_DEPENDENCY = "GLOBAL DEPENDENCY OVERRIDE" -ROUTER_DEPENDENCY = "ROUTER DEPENDENCY" -di_subrouter.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY) -di_subrouter.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY) - - -@di_subrouter.get("/subrouter_router_di") -def sync_subrouter_route_dependency(r: Request, router_dependencies, global_dependencies): - return router_dependencies["ROUTER_DEPENDENCY"] - - -@di_subrouter.get("/subrouter_global_di") -def sync_subrouter_global_dependency(global_dependencies): - return global_dependencies["GLOBAL_DEPENDENCY"] - - -# ===== Authentication ===== - - -@di_subrouter.get("/subrouter_router_di/sync/auth", auth_required=True) -def sync_subrouter_auth(request: Request): - assert request.identity is not None - assert request.identity.claims == {"key": "value"} - return "authenticated" - - -@di_subrouter.get("/subrouter_router_di/async/auth", auth_required=True) -async def async_subrouter_auth(request: Request): - assert request.identity is not None - assert request.identity.claims == {"key": "value"} - return "authenticated" From aef60aadac8b8d035d9b5e6040644f2f291e5900 Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Fri, 15 Nov 2024 17:53:35 +0000 Subject: [PATCH 27/29] Restore subroute integration --- integration_tests/subroutes/__init__.py | 70 +++++++++++++++++++++ integration_tests/subroutes/di-subrouter.py | 17 +++++ 2 files changed, 87 insertions(+) create mode 100644 integration_tests/subroutes/__init__.py create mode 100644 integration_tests/subroutes/di-subrouter.py diff --git a/integration_tests/subroutes/__init__.py b/integration_tests/subroutes/__init__.py new file mode 100644 index 00000000..3ebab110 --- /dev/null +++ b/integration_tests/subroutes/__init__.py @@ -0,0 +1,70 @@ +from robyn import SubRouter, WebSocket, jsonify + +from .di_subrouter import di_subrouter + +sub_router = SubRouter(__name__, prefix="/sub_router") + +websocket = WebSocket(sub_router, "/ws") + +__all__ = ["sub_router", "websocket", "di_subrouter"] + + +@websocket.on("connect") +async def connect(ws): + return "Hello world, from ws" + + +@websocket.on("message") +async def message(): + return "Message" + + +@websocket.on("close") +async def close(ws): + return jsonify({"message": "closed"}) + + +@sub_router.get("/foo") +def get_foo(): + return {"message": "foo"} + + +@sub_router.post("/foo") +def post_foo(): + return {"message": "foo"} + + +@sub_router.put("/foo") +def put_foo(): + return {"message": "foo"} + + +@sub_router.delete("/foo") +def delete_foo(): + return {"message": "foo"} + + +@sub_router.patch("/foo") +def patch_foo(): + return {"message": "foo"} + + +@sub_router.options("/foo") +def option_foo(): + return {"message": "foo"} + + +@sub_router.trace("/foo") +def trace_foo(): + return {"message": "foo"} + + +@sub_router.head("/foo") +def head_foo(): + return {"message": "foo"} + + +@sub_router.post("/openapi_test", openapi_tags=["test subrouter tag"]) +def sample_subrouter_openapi_endpoint(): + """Get subrouter openapi""" + return 200 diff --git a/integration_tests/subroutes/di-subrouter.py b/integration_tests/subroutes/di-subrouter.py new file mode 100644 index 00000000..0b7de6da --- /dev/null +++ b/integration_tests/subroutes/di-subrouter.py @@ -0,0 +1,17 @@ +from robyn import Request, SubRouter + +di_subrouter = SubRouter(__file__, "/di_subrouter") +GLOBAL_DEPENDENCY = "GLOBAL DEPENDENCY OVERRIDE" +ROUTER_DEPENDENCY = "ROUTER DEPENDENCY" +di_subrouter.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY) +di_subrouter.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY) + + +@di_subrouter.get("/subrouter_router_di") +def sync_subrouter_route_dependency(r: Request, router_dependencies, global_dependencies): + return router_dependencies["ROUTER_DEPENDENCY"] + + +@di_subrouter.get("/subrouter_global_di") +def sync_subrouter_global_dependency(global_dependencies): + return global_dependencies["GLOBAL_DEPENDENCY"] From f8494d0e145a4dd8bed54d12c230b280a7a34d6c Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Fri, 15 Nov 2024 17:58:38 +0000 Subject: [PATCH 28/29] Remove changes to subroute http methods --- robyn/__init__.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/robyn/__init__.py b/robyn/__init__.py index 82e3184a..0808dc08 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -596,29 +596,29 @@ def __init__(self, file_object: str, prefix: str = "", config: Config = Config() def __add_prefix(self, endpoint: str): return f"{self.prefix}{endpoint}" - def get(self, endpoint: str, const: bool = False, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["get"]): - return super().get(endpoint=self.__add_prefix(endpoint), const=const, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def get(self, endpoint: str, const: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["get"]): + return super().get(endpoint=self.__add_prefix(endpoint), const=const, openapi_name=openapi_name, openapi_tags=openapi_tags) - def post(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["post"]): - return super().post(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def post(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["post"]): + return super().post(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) - def put(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["put"]): - return super().put(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def put(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["put"]): + return super().put(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) - def delete(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["delete"]): - return super().delete(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def delete(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["delete"]): + return super().delete(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) - def patch(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["patch"]): - return super().patch(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def patch(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["patch"]): + return super().patch(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) - def head(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["head"]): - return super().head(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def head(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["head"]): + return super().head(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) - def trace(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["trace"]): - return super().trace(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def trace(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["trace"]): + return super().trace(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) - def options(self, endpoint: str, auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] = ["options"]): - return super().options(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags) + def options(self, endpoint: str, openapi_name: str = "", openapi_tags: List[str] = ["options"]): + return super().options(endpoint=self.__add_prefix(endpoint), openapi_name=openapi_name, openapi_tags=openapi_tags) def ALLOW_CORS(app: Robyn, origins: Union[List[str], str]): From cc7ee0461d535e7242ecc224b259b9a39e95188f Mon Sep 17 00:00:00 2001 From: Dave Warnock Date: Fri, 15 Nov 2024 18:02:32 +0000 Subject: [PATCH 29/29] Oops used - instead of _ --- integration_tests/subroutes/{di-subrouter.py => di_subrouter.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename integration_tests/subroutes/{di-subrouter.py => di_subrouter.py} (100%) diff --git a/integration_tests/subroutes/di-subrouter.py b/integration_tests/subroutes/di_subrouter.py similarity index 100% rename from integration_tests/subroutes/di-subrouter.py rename to integration_tests/subroutes/di_subrouter.py