From 295128b1dcb45def3e90145dc94c588e6cce29d1 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Sat, 23 Nov 2024 16:44:31 +0530 Subject: [PATCH 1/6] feat: adds handling for eventlet/gevent - Adds AsyncHandler (sub)classes to manage async_to_sync across frameworks - Adds a new param to configure async to Supertokens --- supertokens_python/__init__.py | 11 +- supertokens_python/async_to_sync_wrapper.py | 162 +++++++++++++++++++- supertokens_python/supertokens.py | 13 ++ 3 files changed, 182 insertions(+), 4 deletions(-) diff --git a/supertokens_python/__init__.py b/supertokens_python/__init__.py index 43d3573b2..ca146df79 100644 --- a/supertokens_python/__init__.py +++ b/supertokens_python/__init__.py @@ -16,6 +16,7 @@ from typing_extensions import Literal +from supertokens_python import async_to_sync_wrapper from supertokens_python.framework.request import BaseRequest from supertokens_python.types import RecipeUserId @@ -36,9 +37,17 @@ def init( mode: Optional[Literal["asgi", "wsgi"]] = None, telemetry: Optional[bool] = None, debug: Optional[bool] = None, + async_handler: Optional[async_to_sync_wrapper.ConcreteAsyncHandler] = None, ): return Supertokens.init( - app_info, framework, supertokens_config, recipe_list, mode, telemetry, debug + app_info=app_info, + framework=framework, + supertokens_config=supertokens_config, + recipe_list=recipe_list, + mode=mode, + telemetry=telemetry, + debug=debug, + async_handler=async_handler, ) diff --git a/supertokens_python/async_to_sync_wrapper.py b/supertokens_python/async_to_sync_wrapper.py index 8e019336d..125fe787f 100644 --- a/supertokens_python/async_to_sync_wrapper.py +++ b/supertokens_python/async_to_sync_wrapper.py @@ -12,8 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. +from abc import ABC, abstractmethod import asyncio -from typing import Any, Coroutine, TypeVar +from enum import Enum +from threading import Thread +from typing import Any, Coroutine, Optional, TypeVar, Union from os import getenv _T = TypeVar("_T") @@ -40,6 +43,159 @@ def create_or_get_event_loop() -> asyncio.AbstractEventLoop: raise ex +class AsyncType(Enum): + asyncio = "asyncio" + gevent = "gevent" + eventlet = "eventlet" + + +class _AsyncHandler(ABC): + async_type: AsyncType + create_loop_thread: bool + loop: Optional[asyncio.AbstractEventLoop] + + def __init__( + self, + create_loop_thread: bool, + loop: Optional[asyncio.AbstractEventLoop], + ): + # TODO: Add checks on the socket to see if it's patched by Gevent/Eventlet + # TODO: Consider setting the type of loop (thread/normal) and base sync implementation on that + + if loop is not None: + if create_loop_thread: + raise ValueError("Pass either `loop` or `create_loop_thread`, not both") + + self.loop = loop + self.create_loop_thread = create_loop_thread + self._create_loop_thread() + self._register_loop() + + def _create_loop_thread(self): + if self.create_loop_thread: + self.loop = asyncio.new_event_loop() + loop_thread = Thread(target=self.loop.run_forever, daemon=True) + loop_thread.start() + + def _register_loop(self): + import nest_asyncio # type: ignore + + if self.loop is None: + nest_asyncio.apply() # type: ignore + else: + # Need to set the event loop before `nest_asyncio.apply` + asyncio.set_event_loop(loop=self.loop) + nest_asyncio.apply(loop=self.loop) # type: ignore + + @abstractmethod + def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: + pass + + def _default_run_as_sync( + self, + coroutine: Coroutine[Any, Any, _T], + loop: Optional[asyncio.AbstractEventLoop], + ) -> _T: + if loop is None: + loop = create_or_get_event_loop() + + return loop.run_until_complete(coroutine) + + +class DefaultHandler(_AsyncHandler): + async_type = AsyncType.asyncio + + def __init__(self): + super().__init__(create_loop_thread=False, loop=None) + + def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: + return super()._default_run_as_sync(coroutine, self.loop) + + +class AsyncioHandler(_AsyncHandler): + async_type = AsyncType.asyncio + + def __init__( + self, + create_loop_thread: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): + # NOTE: Creating a non-threaded loop and storing it causes asyncio context issues. + # Handles missing loops similar to `DefaultHandler` + super().__init__(create_loop_thread=create_loop_thread, loop=loop) + + def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: + if self.loop is None: + return super()._default_run_as_sync(coroutine, self.loop) + + future = asyncio.run_coroutine_threadsafe(coroutine, self.loop) + return future.result() + + +class GeventHandler(_AsyncHandler): + async_type = AsyncType.gevent + + def __init__( + self, + create_loop_thread: bool = True, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): + super().__init__(create_loop_thread=create_loop_thread, loop=loop) + + def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: + if self.loop is None: + return super()._default_run_as_sync(coroutine, self.loop) + + from gevent.event import Event # type: ignore + + future = asyncio.run_coroutine_threadsafe(coroutine, self.loop) + event = Event() # type: ignore + future.add_done_callback(lambda _: event.set()) # type: ignore + event.wait() # type: ignore + return future.result() + + +class EventletHandler(_AsyncHandler): + async_type = AsyncType.eventlet + + def __init__( + self, + create_loop_thread: bool = True, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): + if not create_loop_thread: + raise ValueError( + "Cannot use eventlet with Supertokens without a dedicated event loop thread. " + "Please set `create_loop_thread=True`." + ) + + super().__init__(create_loop_thread=create_loop_thread, loop=loop) + + def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: + if self.loop is None: + raise ValueError( + "Cannot use eventlet with Supertokens without a dedicated event loop thread. " + "Please set `create_loop_thread=True`." + ) + + from eventlet.event import Event # type: ignore + + future = asyncio.run_coroutine_threadsafe(coroutine, loop=self.loop) + event = Event() # type: ignore + future.add_done_callback(lambda _: event.send()) # type: ignore + event.wait() # type: ignore + return future.result() + + def sync(co: Coroutine[Any, Any, _T]) -> _T: - loop = create_or_get_event_loop() - return loop.run_until_complete(co) + from supertokens_python import supertokens + + st = supertokens.Supertokens.get_instance() + handler = st.async_handler + + return handler.run_as_sync(co) + + +ConcreteAsyncHandler = Union[ + DefaultHandler, AsyncioHandler, GeventHandler, EventletHandler +] diff --git a/supertokens_python/supertokens.py b/supertokens_python/supertokens.py index 6aae8aa9b..dd46c4a9f 100644 --- a/supertokens_python/supertokens.py +++ b/supertokens_python/supertokens.py @@ -19,6 +19,10 @@ from typing_extensions import Literal +from supertokens_python.async_to_sync_wrapper import ( + ConcreteAsyncHandler, + DefaultHandler, +) from supertokens_python.logger import ( get_maybe_none_as_str, log_debug_message, @@ -211,7 +215,14 @@ def __init__( mode: Optional[Literal["asgi", "wsgi"]], telemetry: Optional[bool], debug: Optional[bool], + async_handler: Optional[ConcreteAsyncHandler], ): + # Handling async setup before anything else is initialized to prevent event loop issues + if async_handler is None: + async_handler = DefaultHandler() + + self.async_handler = async_handler + if not isinstance(app_info, InputAppInfo): # type: ignore raise ValueError("app_info must be an instance of InputAppInfo") @@ -301,6 +312,7 @@ def init( mode: Optional[Literal["asgi", "wsgi"]], telemetry: Optional[bool], debug: Optional[bool], + async_handler: Optional[ConcreteAsyncHandler], ): if Supertokens.__instance is None: Supertokens.__instance = Supertokens( @@ -311,6 +323,7 @@ def init( mode, telemetry, debug, + async_handler=async_handler, ) PostSTInitCallbacks.run_post_init_callbacks() From 409e6647fbd3faa2bb8da6e36a0b8e7b2b0b84e0 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Sat, 23 Nov 2024 17:38:31 +0530 Subject: [PATCH 2/6] refactor: splits async_to_sync_wrapper into a module - Separates logical groups of utils into modules - Disables pylint cyclic checks on supertokens lazy import --- supertokens_python/__init__.py | 4 +- supertokens_python/async_to_sync/__init__.py | 0 supertokens_python/async_to_sync/base.py | 26 +++++ .../handler.py} | 32 +----- supertokens_python/async_to_sync/utils.py | 37 +++++++ .../framework/flask/flask_middleware.py | 16 ++- supertokens_python/querier.py | 2 +- .../recipe/accountlinking/syncio/__init__.py | 2 +- .../recipe/emailpassword/syncio/__init__.py | 2 +- .../emailverification/syncio/__init__.py | 2 +- .../recipe/jwt/syncio/__init__.py | 2 +- .../recipe/multifactorauth/syncio/__init__.py | 2 +- .../recipe/multitenancy/syncio/__init__.py | 2 +- .../recipe/openid/syncio/__init__.py | 2 +- .../recipe/passwordless/syncio/__init__.py | 2 +- .../framework/django/syncio/__init__.py | 2 +- .../session/framework/flask/__init__.py | 2 +- .../recipe/session/interfaces.py | 2 +- .../recipe/session/syncio/__init__.py | 2 +- .../recipe/thirdparty/syncio/__init__.py | 2 +- .../recipe/totp/syncio/__init__.py | 2 +- .../recipe/usermetadata/syncio/__init__.py | 2 +- .../recipe/userroles/syncio/__init__.py | 2 +- supertokens_python/supertokens.py | 2 +- supertokens_python/syncio/__init__.py | 2 +- tests/Flask/test_gevent.py | 103 ++++++++++++++++++ .../django2x/polls/views.py | 2 +- .../drf_sync/polls/views.py | 2 +- tests/frontendIntegration/flask-server/app.py | 2 +- tests/test-server/accountlinking.py | 5 +- tests/test-server/emailverification.py | 4 +- tests/test-server/multifactorauth.py | 6 +- 32 files changed, 211 insertions(+), 66 deletions(-) create mode 100644 supertokens_python/async_to_sync/__init__.py create mode 100644 supertokens_python/async_to_sync/base.py rename supertokens_python/{async_to_sync_wrapper.py => async_to_sync/handler.py} (87%) create mode 100644 supertokens_python/async_to_sync/utils.py create mode 100644 tests/Flask/test_gevent.py diff --git a/supertokens_python/__init__.py b/supertokens_python/__init__.py index ca146df79..0048da2a3 100644 --- a/supertokens_python/__init__.py +++ b/supertokens_python/__init__.py @@ -16,7 +16,7 @@ from typing_extensions import Literal -from supertokens_python import async_to_sync_wrapper +from supertokens_python.async_to_sync.handler import ConcreteAsyncHandler from supertokens_python.framework.request import BaseRequest from supertokens_python.types import RecipeUserId @@ -37,7 +37,7 @@ def init( mode: Optional[Literal["asgi", "wsgi"]] = None, telemetry: Optional[bool] = None, debug: Optional[bool] = None, - async_handler: Optional[async_to_sync_wrapper.ConcreteAsyncHandler] = None, + async_handler: Optional[ConcreteAsyncHandler] = None, ): return Supertokens.init( app_info=app_info, diff --git a/supertokens_python/async_to_sync/__init__.py b/supertokens_python/async_to_sync/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/supertokens_python/async_to_sync/base.py b/supertokens_python/async_to_sync/base.py new file mode 100644 index 000000000..f796aebf6 --- /dev/null +++ b/supertokens_python/async_to_sync/base.py @@ -0,0 +1,26 @@ +# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +# +# This software is licensed under the Apache License, Version 2.0 (the +# "License") as published by the Apache Software Foundation. +# +# You may not use this file except in compliance with the License. You may +# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from typing import Any, Coroutine, TypeVar + +_T = TypeVar("_T") + + +def sync(co: Coroutine[Any, Any, _T]) -> _T: + from supertokens_python import supertokens # pylint: disable=cyclic-import + + st = supertokens.Supertokens.get_instance() + handler = st.async_handler + + return handler.run_as_sync(co) diff --git a/supertokens_python/async_to_sync_wrapper.py b/supertokens_python/async_to_sync/handler.py similarity index 87% rename from supertokens_python/async_to_sync_wrapper.py rename to supertokens_python/async_to_sync/handler.py index 125fe787f..63414103c 100644 --- a/supertokens_python/async_to_sync_wrapper.py +++ b/supertokens_python/async_to_sync/handler.py @@ -17,32 +17,11 @@ from enum import Enum from threading import Thread from typing import Any, Coroutine, Optional, TypeVar, Union -from os import getenv +from supertokens_python.async_to_sync.utils import create_or_get_event_loop _T = TypeVar("_T") -def nest_asyncio_enabled(): - return getenv("SUPERTOKENS_NEST_ASYNCIO", "") == "1" - - -def create_or_get_event_loop() -> asyncio.AbstractEventLoop: - try: - return asyncio.get_event_loop() - except Exception as ex: - if "There is no current event loop in thread" in str(ex): - loop = asyncio.new_event_loop() - - if nest_asyncio_enabled(): - import nest_asyncio # type: ignore - - nest_asyncio.apply(loop) # type: ignore - - asyncio.set_event_loop(loop) - return loop - raise ex - - class AsyncType(Enum): asyncio = "asyncio" gevent = "gevent" @@ -187,15 +166,6 @@ def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: return future.result() -def sync(co: Coroutine[Any, Any, _T]) -> _T: - from supertokens_python import supertokens - - st = supertokens.Supertokens.get_instance() - handler = st.async_handler - - return handler.run_as_sync(co) - - ConcreteAsyncHandler = Union[ DefaultHandler, AsyncioHandler, GeventHandler, EventletHandler ] diff --git a/supertokens_python/async_to_sync/utils.py b/supertokens_python/async_to_sync/utils.py new file mode 100644 index 000000000..31d009d4d --- /dev/null +++ b/supertokens_python/async_to_sync/utils.py @@ -0,0 +1,37 @@ +# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. +# +# This software is licensed under the Apache License, Version 2.0 (the +# "License") as published by the Apache Software Foundation. +# +# You may not use this file except in compliance with the License. You may +# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import asyncio +from os import getenv + + +def nest_asyncio_enabled(): + return getenv("SUPERTOKENS_NEST_ASYNCIO", "") == "1" + + +def create_or_get_event_loop() -> asyncio.AbstractEventLoop: + try: + return asyncio.get_event_loop() + except Exception as ex: + if "There is no current event loop in thread" in str(ex): + loop = asyncio.new_event_loop() + + if nest_asyncio_enabled(): + import nest_asyncio # type: ignore + + nest_asyncio.apply(loop) # type: ignore + + asyncio.set_event_loop(loop) + return loop + raise ex diff --git a/supertokens_python/framework/flask/flask_middleware.py b/supertokens_python/framework/flask/flask_middleware.py index d4eff2eaf..7a1a2c7ee 100644 --- a/supertokens_python/framework/flask/flask_middleware.py +++ b/supertokens_python/framework/flask/flask_middleware.py @@ -16,7 +16,7 @@ import json from typing import TYPE_CHECKING, Union -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.framework import BaseResponse if TYPE_CHECKING: @@ -54,13 +54,14 @@ def _(): user_context = default_user_context(request_) result: Union[BaseResponse, None] = sync( - st.middleware(request_, response_, user_context) + st.middleware(request_, response_, user_context), ) + # result = await st.middleware(request_, response_, user_context) if result is not None: if isinstance(result, FlaskResponse): return result.response - raise Exception("Should never come here") + raise Exception(f"Should never come here, {type(result)=}") return None @app.after_request @@ -109,8 +110,15 @@ def _(error: Exception): error, FlaskResponse(response), user_context, - ) + ), ) + # result: BaseResponse = await st.handle_supertokens_error( + # base_request, + # error, + # FlaskResponse(response), + # user_context, + # ) + if isinstance(result, FlaskResponse): return result.response raise Exception("Should never come here") diff --git a/supertokens_python/querier.py b/supertokens_python/querier.py index 69945493d..764963609 100644 --- a/supertokens_python/querier.py +++ b/supertokens_python/querier.py @@ -38,7 +38,7 @@ from .process_state import PROCESS_STATE, ProcessState from .utils import find_max_version, is_4xx_error, is_5xx_error from sniffio import AsyncLibraryNotFoundError -from supertokens_python.async_to_sync_wrapper import create_or_get_event_loop +from supertokens_python.async_to_sync.utils import create_or_get_event_loop from supertokens_python.utils import get_timestamp_ms diff --git a/supertokens_python/recipe/accountlinking/syncio/__init__.py b/supertokens_python/recipe/accountlinking/syncio/__init__.py index 6de893c1f..16a133932 100644 --- a/supertokens_python/recipe/accountlinking/syncio/__init__.py +++ b/supertokens_python/recipe/accountlinking/syncio/__init__.py @@ -13,7 +13,7 @@ # under the License. from typing import Any, Dict, Optional -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from ..types import AccountInfoWithRecipeId from supertokens_python.types import RecipeUserId diff --git a/supertokens_python/recipe/emailpassword/syncio/__init__.py b/supertokens_python/recipe/emailpassword/syncio/__init__.py index 1681589cb..f42330643 100644 --- a/supertokens_python/recipe/emailpassword/syncio/__init__.py +++ b/supertokens_python/recipe/emailpassword/syncio/__init__.py @@ -14,7 +14,7 @@ from typing import Any, Dict, Union, Optional from typing_extensions import Literal -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.emailpassword.interfaces import ( SignUpOkResult, diff --git a/supertokens_python/recipe/emailverification/syncio/__init__.py b/supertokens_python/recipe/emailverification/syncio/__init__.py index 914f86498..3122a4ced 100644 --- a/supertokens_python/recipe/emailverification/syncio/__init__.py +++ b/supertokens_python/recipe/emailverification/syncio/__init__.py @@ -14,7 +14,7 @@ from typing import Any, Dict, Optional, Union -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.emailverification.types import EmailTemplateVars from supertokens_python.types import RecipeUserId diff --git a/supertokens_python/recipe/jwt/syncio/__init__.py b/supertokens_python/recipe/jwt/syncio/__init__.py index 8befd40a8..269cce1b6 100644 --- a/supertokens_python/recipe/jwt/syncio/__init__.py +++ b/supertokens_python/recipe/jwt/syncio/__init__.py @@ -13,7 +13,7 @@ # under the License. from typing import Any, Dict, Union, Optional -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.jwt import asyncio from supertokens_python.recipe.jwt.interfaces import ( CreateJwtOkResult, diff --git a/supertokens_python/recipe/multifactorauth/syncio/__init__.py b/supertokens_python/recipe/multifactorauth/syncio/__init__.py index 8268fd19a..ebf1b06c9 100644 --- a/supertokens_python/recipe/multifactorauth/syncio/__init__.py +++ b/supertokens_python/recipe/multifactorauth/syncio/__init__.py @@ -17,7 +17,7 @@ from typing import Any, Dict, Optional, List from supertokens_python.recipe.session import SessionContainer -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync def assert_allowed_to_setup_factor_else_throw_invalid_claim_error( diff --git a/supertokens_python/recipe/multitenancy/syncio/__init__.py b/supertokens_python/recipe/multitenancy/syncio/__init__.py index 5448f2612..25d80e96e 100644 --- a/supertokens_python/recipe/multitenancy/syncio/__init__.py +++ b/supertokens_python/recipe/multitenancy/syncio/__init__.py @@ -14,7 +14,7 @@ from __future__ import annotations from typing import Any, Dict, Optional, TYPE_CHECKING -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate from supertokens_python.types import RecipeUserId diff --git a/supertokens_python/recipe/openid/syncio/__init__.py b/supertokens_python/recipe/openid/syncio/__init__.py index a74b0c544..f371f114b 100644 --- a/supertokens_python/recipe/openid/syncio/__init__.py +++ b/supertokens_python/recipe/openid/syncio/__init__.py @@ -13,7 +13,7 @@ # under the License. from typing import Any, Dict, Union, Optional -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.openid import asyncio from supertokens_python.recipe.openid.interfaces import ( GetOpenIdDiscoveryConfigurationResult, diff --git a/supertokens_python/recipe/passwordless/syncio/__init__.py b/supertokens_python/recipe/passwordless/syncio/__init__.py index e5a0105a4..3a298fa86 100644 --- a/supertokens_python/recipe/passwordless/syncio/__init__.py +++ b/supertokens_python/recipe/passwordless/syncio/__init__.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. from typing import Any, Dict, List, Optional, Union -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.auth_utils import LinkingToSessionUserFailedError from supertokens_python.recipe.passwordless import asyncio from supertokens_python.recipe.session import SessionContainer diff --git a/supertokens_python/recipe/session/framework/django/syncio/__init__.py b/supertokens_python/recipe/session/framework/django/syncio/__init__.py index 99b9b52ad..ce8fbd4ae 100644 --- a/supertokens_python/recipe/session/framework/django/syncio/__init__.py +++ b/supertokens_python/recipe/session/framework/django/syncio/__init__.py @@ -15,7 +15,7 @@ from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional from supertokens_python import Supertokens -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.exceptions import SuperTokensError from supertokens_python.framework.django.django_request import DjangoRequest from supertokens_python.framework.django.django_response import DjangoResponse diff --git a/supertokens_python/recipe/session/framework/flask/__init__.py b/supertokens_python/recipe/session/framework/flask/__init__.py index 5e5137d15..e88d29efa 100644 --- a/supertokens_python/recipe/session/framework/flask/__init__.py +++ b/supertokens_python/recipe/session/framework/flask/__init__.py @@ -15,7 +15,7 @@ from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional from supertokens_python import Supertokens -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.framework.flask.flask_request import FlaskRequest from supertokens_python.framework.flask.flask_response import FlaskResponse from supertokens_python.recipe.session import SessionRecipe, SessionContainer diff --git a/supertokens_python/recipe/session/interfaces.py b/supertokens_python/recipe/session/interfaces.py index 0ffe24518..43c9b0e62 100644 --- a/supertokens_python/recipe/session/interfaces.py +++ b/supertokens_python/recipe/session/interfaces.py @@ -27,7 +27,7 @@ ) from typing_extensions import TypedDict -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.types import ( APIResponse, GeneralErrorResponse, diff --git a/supertokens_python/recipe/session/syncio/__init__.py b/supertokens_python/recipe/session/syncio/__init__.py index 50eec7a11..9ed602146 100644 --- a/supertokens_python/recipe/session/syncio/__init__.py +++ b/supertokens_python/recipe/session/syncio/__init__.py @@ -14,7 +14,7 @@ from __future__ import annotations from typing import Any, Dict, List, Union, Callable, Optional, TypeVar -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.openid.interfaces import ( GetOpenIdDiscoveryConfigurationResult, ) diff --git a/supertokens_python/recipe/thirdparty/syncio/__init__.py b/supertokens_python/recipe/thirdparty/syncio/__init__.py index 4481c8131..ecd2e2a56 100644 --- a/supertokens_python/recipe/thirdparty/syncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/syncio/__init__.py @@ -13,7 +13,7 @@ # under the License. from typing import Any, Dict, Optional, Union -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.auth_utils import LinkingToSessionUserFailedError from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.thirdparty.interfaces import ( diff --git a/supertokens_python/recipe/totp/syncio/__init__.py b/supertokens_python/recipe/totp/syncio/__init__.py index 24eb4d2ed..2cc3dce9b 100644 --- a/supertokens_python/recipe/totp/syncio/__init__.py +++ b/supertokens_python/recipe/totp/syncio/__init__.py @@ -16,7 +16,7 @@ from typing import Any, Dict, Union, Optional -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.totp.types import ( CreateDeviceOkResult, diff --git a/supertokens_python/recipe/usermetadata/syncio/__init__.py b/supertokens_python/recipe/usermetadata/syncio/__init__.py index aa2dc9c2b..8544a22eb 100644 --- a/supertokens_python/recipe/usermetadata/syncio/__init__.py +++ b/supertokens_python/recipe/usermetadata/syncio/__init__.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Union -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync def get_user_metadata(user_id: str, user_context: Union[Dict[str, Any], None] = None): diff --git a/supertokens_python/recipe/userroles/syncio/__init__.py b/supertokens_python/recipe/userroles/syncio/__init__.py index 8cb30ad82..6c255d5df 100644 --- a/supertokens_python/recipe/userroles/syncio/__init__.py +++ b/supertokens_python/recipe/userroles/syncio/__init__.py @@ -14,7 +14,7 @@ from typing import Any, Dict, List, Union -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.userroles.interfaces import ( AddRoleToUserOkResult, CreateNewRoleOrAddPermissionsOkResult, diff --git a/supertokens_python/supertokens.py b/supertokens_python/supertokens.py index dd46c4a9f..2ed88ad7e 100644 --- a/supertokens_python/supertokens.py +++ b/supertokens_python/supertokens.py @@ -19,7 +19,7 @@ from typing_extensions import Literal -from supertokens_python.async_to_sync_wrapper import ( +from supertokens_python.async_to_sync.handler import ( ConcreteAsyncHandler, DefaultHandler, ) diff --git a/supertokens_python/syncio/__init__.py b/supertokens_python/syncio/__init__.py index e7b50292f..a8815a852 100644 --- a/supertokens_python/syncio/__init__.py +++ b/supertokens_python/syncio/__init__.py @@ -14,7 +14,7 @@ from typing import Dict, List, Optional, Union, Any from supertokens_python import Supertokens -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.interfaces import ( CreateUserIdMappingOkResult, DeleteUserIdMappingOkResult, diff --git a/tests/Flask/test_gevent.py b/tests/Flask/test_gevent.py new file mode 100644 index 000000000..477238ff8 --- /dev/null +++ b/tests/Flask/test_gevent.py @@ -0,0 +1,103 @@ +# import gevent.monkey +# gevent.monkey.patch_all() + + +# from tests.utils import start_st +# import pytest +# from flask import Flask, g, jsonify, make_response, request +# from supertokens_python import InputAppInfo, SupertokensConfig, init +# from supertokens_python.framework.flask import Middleware +# from typing import Any +# import json +# from typing import Any, Dict, Union +# from base64 import b64encode + +# import pytest +# from _pytest.fixtures import fixture +# from flask import Flask, g, jsonify, make_response, request +# from supertokens_python import InputAppInfo, SupertokensConfig, init +# from supertokens_python.framework.flask import Middleware +# from supertokens_python.recipe import emailpassword, session, thirdparty +# from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions +# from supertokens_python.recipe.session import SessionContainer +# from supertokens_python.recipe.session.framework.flask import verify_session +# from supertokens_python.recipe.session.syncio import ( +# create_new_session, +# create_new_session_without_request_response, +# get_session, +# refresh_session, +# revoke_session, +# ) +# from supertokens_python.types import RecipeUserId +# from tests.Flask.utils import extract_all_cookies +# from tests.utils import ( +# TEST_ACCESS_TOKEN_MAX_AGE_CONFIG_KEY, +# TEST_ACCESS_TOKEN_MAX_AGE_VALUE, +# TEST_ACCESS_TOKEN_PATH_CONFIG_KEY, +# TEST_ACCESS_TOKEN_PATH_VALUE, +# TEST_COOKIE_DOMAIN_CONFIG_KEY, +# TEST_COOKIE_DOMAIN_VALUE, +# TEST_COOKIE_SAME_SITE_CONFIG_KEY, +# TEST_COOKIE_SECURE_CONFIG_KEY, +# TEST_DRIVER_CONFIG_ACCESS_TOKEN_PATH, +# TEST_DRIVER_CONFIG_COOKIE_DOMAIN, +# TEST_DRIVER_CONFIG_COOKIE_SAME_SITE, +# TEST_DRIVER_CONFIG_REFRESH_TOKEN_PATH, +# TEST_REFRESH_TOKEN_MAX_AGE_CONFIG_KEY, +# TEST_REFRESH_TOKEN_MAX_AGE_VALUE, +# TEST_REFRESH_TOKEN_PATH_CONFIG_KEY, +# TEST_REFRESH_TOKEN_PATH_KEY_VALUE, +# clean_st, +# reset, +# set_key_value_in_config, +# setup_st, +# start_st, +# create_users, +# ) +# from supertokens_python.recipe.dashboard import DashboardRecipe, InputOverrideConfig +# from supertokens_python.recipe.dashboard.interfaces import RecipeInterface +# from supertokens_python.framework import BaseRequest +# from supertokens_python.querier import Querier +# from supertokens_python.utils import is_version_gte +# from supertokens_python.recipe.passwordless import PasswordlessRecipe, ContactConfig +# from supertokens_python.recipe.dashboard.utils import DashboardConfig + + +# @pytest.fixture(scope="function") # type: ignore +# def flask_app(): +# app = Flask(__name__) +# app.app_context().push() +# Middleware(app) + +# app.testing = True +# init( +# supertokens_config=SupertokensConfig("http://localhost:3567"), +# app_info=InputAppInfo( +# app_name="SuperTokens Demo", +# api_domain="http://api.supertokens.io", +# website_domain="http://supertokens.io", +# api_base_path="/auth", +# ), +# framework="flask", +# recipe_list=[ +# session.init(), +# ], +# ) + +# @app.route("/test") # type: ignore +# def t(): # type: ignore +# return jsonify({}) + +# return app + + +# def test_gevent(flask_app: Any): +# # init(**{**get_st_init_args([session.init(get_token_transfer_method=lambda *_: "cookie")]), "framework": "flask"}) # type: ignore +# start_st() +# client = flask_app.test_client() + +# client.get("/test").json +# client.get("/test").json +# client.get("/test").json +# client.get("/test").json +# client.get("/test").json diff --git a/tests/frontendIntegration/django2x/polls/views.py b/tests/frontendIntegration/django2x/polls/views.py index eda19409a..80e26d5e5 100644 --- a/tests/frontendIntegration/django2x/polls/views.py +++ b/tests/frontendIntegration/django2x/polls/views.py @@ -53,7 +53,7 @@ from supertokens_python.recipe.session.syncio import get_session_information from supertokens_python.normalised_url_path import NormalisedURLPath from supertokens_python.querier import Querier -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync protected_prop_name = { "sub", diff --git a/tests/frontendIntegration/drf_sync/polls/views.py b/tests/frontendIntegration/drf_sync/polls/views.py index cd776104b..92fbce0e7 100644 --- a/tests/frontendIntegration/drf_sync/polls/views.py +++ b/tests/frontendIntegration/drf_sync/polls/views.py @@ -51,7 +51,7 @@ ) from supertokens_python.recipe.session.framework.django.syncio import verify_session from supertokens_python.recipe.session.syncio import merge_into_access_token_payload -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from supertokens_python.constants import VERSION from supertokens_python.types import RecipeUserId diff --git a/tests/frontendIntegration/flask-server/app.py b/tests/frontendIntegration/flask-server/app.py index e829b61fc..5ff030e07 100644 --- a/tests/frontendIntegration/flask-server/app.py +++ b/tests/frontendIntegration/flask-server/app.py @@ -49,7 +49,7 @@ from supertokens_python.recipe.session.syncio import get_session_information from supertokens_python.normalised_url_path import NormalisedURLPath from supertokens_python.querier import Querier -from supertokens_python.async_to_sync_wrapper import sync +from supertokens_python.async_to_sync.base import sync from werkzeug.exceptions import NotFound diff --git a/tests/test-server/accountlinking.py b/tests/test-server/accountlinking.py index 30b73b694..65364b475 100644 --- a/tests/test-server/accountlinking.py +++ b/tests/test-server/accountlinking.py @@ -1,5 +1,6 @@ from flask import Flask, request, jsonify -from supertokens_python import async_to_sync_wrapper, convert_to_recipe_user_id +from supertokens_python import convert_to_recipe_user_id +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.accountlinking.syncio import can_create_primary_user from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe from supertokens_python.recipe.accountlinking.syncio import is_sign_in_allowed @@ -233,7 +234,7 @@ def verify_email_for_recipe_user_if_linked_accounts_are_verified_api(): # type: assert request.json is not None recipe_user_id = convert_to_recipe_user_id(request.json["recipeUserId"]) user = User.from_json(request.json["user"]) - async_to_sync_wrapper.sync( + sync( AccountLinkingRecipe.get_instance().verify_email_for_recipe_user_if_linked_accounts_are_verified( user=user, recipe_user_id=recipe_user_id, diff --git a/tests/test-server/emailverification.py b/tests/test-server/emailverification.py index e47660f18..918726739 100644 --- a/tests/test-server/emailverification.py +++ b/tests/test-server/emailverification.py @@ -1,6 +1,6 @@ from flask import Flask, request, jsonify -from supertokens_python import async_to_sync_wrapper +from supertokens_python.async_to_sync.base import sync from supertokens_python.framework.flask.flask_request import FlaskRequest from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, @@ -116,7 +116,7 @@ def update_session_if_required_post_email_verification(): # type: ignore ) session = convert_session_to_container(data) if "session" in data else None - session_resp = async_to_sync_wrapper.sync( + session_resp = sync( EmailVerificationRecipe.get_instance_or_throw().update_session_if_required_post_email_verification( recipe_user_id_whose_email_got_verified=recipe_user_id_whose_email_got_verified, session=session, diff --git a/tests/test-server/multifactorauth.py b/tests/test-server/multifactorauth.py index d82048917..99c3c68b4 100644 --- a/tests/test-server/multifactorauth.py +++ b/tests/test-server/multifactorauth.py @@ -1,7 +1,7 @@ from typing import List from flask import Flask, request, jsonify -from supertokens_python import async_to_sync_wrapper +from supertokens_python.async_to_sync.base import sync from supertokens_python.recipe.multifactorauth.types import MFAClaimValue from supertokens_python.types import User @@ -15,7 +15,7 @@ def fetch_value_api(): # type: ignore ) assert request.json is not None - response: MFAClaimValue = async_to_sync_wrapper.sync( # type: ignore + response: MFAClaimValue = sync( # type: ignore MultiFactorAuthClaim.fetch_value( # type: ignore request.json["_userId"], convert_to_recipe_user_id(request.json["recipeUserId"]), @@ -184,7 +184,7 @@ async def required_secondary_factors_for_tenant() -> List[str]: assert request.json is not None return request.json["requiredSecondaryFactorsForTenant"] - response = async_to_sync_wrapper.sync( + response = sync( MultiFactorAuthRecipe.get_instance_or_throw_error().recipe_implementation.get_mfa_requirements_for_auth( tenant_id=request.json["tenantId"], access_token_payload=request.json["accessTokenPayload"], From 0616c49623dc90dbe763510e2a5398c21dfcbca1 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Sat, 23 Nov 2024 17:49:01 +0530 Subject: [PATCH 3/6] update: remove unintentional changes --- supertokens_python/framework/flask/flask_middleware.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/supertokens_python/framework/flask/flask_middleware.py b/supertokens_python/framework/flask/flask_middleware.py index 7a1a2c7ee..7c7ee6dce 100644 --- a/supertokens_python/framework/flask/flask_middleware.py +++ b/supertokens_python/framework/flask/flask_middleware.py @@ -56,7 +56,6 @@ def _(): result: Union[BaseResponse, None] = sync( st.middleware(request_, response_, user_context), ) - # result = await st.middleware(request_, response_, user_context) if result is not None: if isinstance(result, FlaskResponse): @@ -112,12 +111,6 @@ def _(error: Exception): user_context, ), ) - # result: BaseResponse = await st.handle_supertokens_error( - # base_request, - # error, - # FlaskResponse(response), - # user_context, - # ) if isinstance(result, FlaskResponse): return result.response From 3b32d32c898b25370bb9bfb674ff8ded156f65e9 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Sun, 24 Nov 2024 12:36:35 +0530 Subject: [PATCH 4/6] update: adds option to specify loop type - Adds handling to prevent non-ideal configuration --- supertokens_python/async_to_sync/base.py | 4 + supertokens_python/async_to_sync/handler.py | 125 ++++++++++++++++---- 2 files changed, 108 insertions(+), 21 deletions(-) diff --git a/supertokens_python/async_to_sync/base.py b/supertokens_python/async_to_sync/base.py index f796aebf6..0e8c9d11d 100644 --- a/supertokens_python/async_to_sync/base.py +++ b/supertokens_python/async_to_sync/base.py @@ -18,6 +18,10 @@ def sync(co: Coroutine[Any, Any, _T]) -> _T: + """ + Convert async function calls to sync calls using the specified `_AsyncHandler` + """ + # Disabling cyclic import since the import is lazy, and will not cause issues from supertokens_python import supertokens # pylint: disable=cyclic-import st = supertokens.Supertokens.get_instance() diff --git a/supertokens_python/async_to_sync/handler.py b/supertokens_python/async_to_sync/handler.py index 63414103c..9bbed0124 100644 --- a/supertokens_python/async_to_sync/handler.py +++ b/supertokens_python/async_to_sync/handler.py @@ -29,24 +29,47 @@ class AsyncType(Enum): class _AsyncHandler(ABC): + """ + Abstract class to handle async-to-sync in various environments. + """ + async_type: AsyncType + """The type of async handling to use""" + create_loop_thread: bool + """Whether a thread needs to be created to run an event loop""" + loop: Optional[asyncio.AbstractEventLoop] + """The event loop to use for async-to-sync conversions""" + + is_loop_threaded: bool + """Whether the passed loop is running in a thread""" def __init__( self, create_loop_thread: bool, loop: Optional[asyncio.AbstractEventLoop], + is_loop_threaded: bool, ): # TODO: Add checks on the socket to see if it's patched by Gevent/Eventlet - # TODO: Consider setting the type of loop (thread/normal) and base sync implementation on that - if loop is not None: - if create_loop_thread: - raise ValueError("Pass either `loop` or `create_loop_thread`, not both") + # Either the user passes in a loop or tells us to create a thread, not both + # If neither is passed, we use the default event loop handling + if loop is not None and create_loop_thread: + raise ValueError("Pass either `loop` or `create_loop_thread`, not both") + + if is_loop_threaded: + if loop is None and not create_loop_thread: + raise ValueError( + "Loop cannot be marked as threaded without passing in `loop` or `create_loop_thread`" + ) + + if create_loop_thread: + is_loop_threaded = True self.loop = loop self.create_loop_thread = create_loop_thread + self.is_loop_threaded = is_loop_threaded self._create_loop_thread() self._register_loop() @@ -75,6 +98,17 @@ def _default_run_as_sync( coroutine: Coroutine[Any, Any, _T], loop: Optional[asyncio.AbstractEventLoop], ) -> _T: + # Event loop running in separate thread + if self.is_loop_threaded: + if self.loop is None: + raise ValueError( + "Expected `loop` to not be `None` when `is_loop_threaded` is True" + ) + + future = asyncio.run_coroutine_threadsafe(coroutine, self.loop) + return future.result() + + # Normal event loop in the current thread if loop is None: loop = create_or_get_event_loop() @@ -82,49 +116,84 @@ def _default_run_as_sync( class DefaultHandler(_AsyncHandler): + """ + Default async handler for Asyncio-based apps. + """ async_type = AsyncType.asyncio def __init__(self): - super().__init__(create_loop_thread=False, loop=None) + super().__init__(create_loop_thread=False, loop=None, is_loop_threaded=False) def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: return super()._default_run_as_sync(coroutine, self.loop) class AsyncioHandler(_AsyncHandler): + """ + Async handler specific to Asyncio-based apps. + + Only meant for cases where existing event loops need to be re-used, or new + threaded-loops need to be created. + For normal use-cases, prefer the `DefaultHandler`. + """ async_type = AsyncType.asyncio def __init__( self, create_loop_thread: bool = False, loop: Optional[asyncio.AbstractEventLoop] = None, + is_loop_threaded: bool = False, ): # NOTE: Creating a non-threaded loop and storing it causes asyncio context issues. # Handles missing loops similar to `DefaultHandler` - super().__init__(create_loop_thread=create_loop_thread, loop=loop) + if loop is not None and not is_loop_threaded: + raise ValueError( + "For existing, non-threaded loops in asyncio, prefer using DefaultHandler" + ) - def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: - if self.loop is None: - return super()._default_run_as_sync(coroutine, self.loop) + super().__init__( + create_loop_thread=create_loop_thread, + loop=loop, + is_loop_threaded=is_loop_threaded, + ) - future = asyncio.run_coroutine_threadsafe(coroutine, self.loop) - return future.result() + def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: + return super()._default_run_as_sync(coroutine, self.loop) class GeventHandler(_AsyncHandler): + """ + Async handler specific to Gevent-based apps. + + Does not work optimally with event loops on the same thread, will drop requests. + Requires a separate thread for the event loop to work well. + """ async_type = AsyncType.gevent def __init__( self, create_loop_thread: bool = True, loop: Optional[asyncio.AbstractEventLoop] = None, + is_loop_threaded: bool = True, ): - super().__init__(create_loop_thread=create_loop_thread, loop=loop) + if not create_loop_thread: + if not is_loop_threaded: + raise ValueError( + "Non-Threaded gevent loops result in stuck requests, use a threaded loop instead" + ) + + super().__init__( + create_loop_thread=create_loop_thread, + loop=loop, + is_loop_threaded=is_loop_threaded, + ) def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: - if self.loop is None: + # When a loop isn't declared or is not in a thread, handle as usual + if self.loop is None or not self.is_loop_threaded: return super()._default_run_as_sync(coroutine, self.loop) + # When loop is in a thread, we can optimize using Events from gevent.event import Event # type: ignore future = asyncio.run_coroutine_threadsafe(coroutine, self.loop) @@ -135,28 +204,42 @@ def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: class EventletHandler(_AsyncHandler): + """ + Async handler specific to Eventlet-based apps. + + Does not work with event loops on the same thread. + Requires a separate thread for the event loop. + """ async_type = AsyncType.eventlet def __init__( self, create_loop_thread: bool = True, loop: Optional[asyncio.AbstractEventLoop] = None, + is_loop_threaded: bool = True, ): if not create_loop_thread: - raise ValueError( - "Cannot use eventlet with Supertokens without a dedicated event loop thread. " - "Please set `create_loop_thread=True`." - ) - - super().__init__(create_loop_thread=create_loop_thread, loop=loop) + if loop is None or not is_loop_threaded: + raise ValueError( + "Cannot use eventlet with Supertokens without a dedicated event loop thread. " + "Please set `create_loop_thread=True` or pass in a threaded event loop." + ) + + super().__init__( + create_loop_thread=create_loop_thread, + loop=loop, + is_loop_threaded=is_loop_threaded, + ) def run_as_sync(self, coroutine: Coroutine[Any, Any, _T]) -> _T: - if self.loop is None: + # Eventlet only works well when the event loop is in a different thread + if self.loop is None or not self.is_loop_threaded: raise ValueError( "Cannot use eventlet with Supertokens without a dedicated event loop thread. " - "Please set `create_loop_thread=True`." + "Please set `create_loop_thread=True` or pass in a threaded event loop." ) + # Use Events to handle loop callbacks from eventlet.event import Event # type: ignore future = asyncio.run_coroutine_threadsafe(coroutine, loop=self.loop) From ecf4e63415d56341f049068306e807c697a15ce9 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Sun, 24 Nov 2024 12:40:19 +0530 Subject: [PATCH 5/6] update: remove unintentional changes --- supertokens_python/framework/flask/flask_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supertokens_python/framework/flask/flask_middleware.py b/supertokens_python/framework/flask/flask_middleware.py index 7c7ee6dce..957b47cf1 100644 --- a/supertokens_python/framework/flask/flask_middleware.py +++ b/supertokens_python/framework/flask/flask_middleware.py @@ -60,7 +60,7 @@ def _(): if result is not None: if isinstance(result, FlaskResponse): return result.response - raise Exception(f"Should never come here, {type(result)=}") + raise Exception("Should never come here") return None @app.after_request From 244efddfe80e3763687b04d4c0c42bbdb51a2831 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Sun, 24 Nov 2024 12:41:50 +0530 Subject: [PATCH 6/6] update: remove unintentional changes --- tests/Flask/test_gevent.py | 103 ------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 tests/Flask/test_gevent.py diff --git a/tests/Flask/test_gevent.py b/tests/Flask/test_gevent.py deleted file mode 100644 index 477238ff8..000000000 --- a/tests/Flask/test_gevent.py +++ /dev/null @@ -1,103 +0,0 @@ -# import gevent.monkey -# gevent.monkey.patch_all() - - -# from tests.utils import start_st -# import pytest -# from flask import Flask, g, jsonify, make_response, request -# from supertokens_python import InputAppInfo, SupertokensConfig, init -# from supertokens_python.framework.flask import Middleware -# from typing import Any -# import json -# from typing import Any, Dict, Union -# from base64 import b64encode - -# import pytest -# from _pytest.fixtures import fixture -# from flask import Flask, g, jsonify, make_response, request -# from supertokens_python import InputAppInfo, SupertokensConfig, init -# from supertokens_python.framework.flask import Middleware -# from supertokens_python.recipe import emailpassword, session, thirdparty -# from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions -# from supertokens_python.recipe.session import SessionContainer -# from supertokens_python.recipe.session.framework.flask import verify_session -# from supertokens_python.recipe.session.syncio import ( -# create_new_session, -# create_new_session_without_request_response, -# get_session, -# refresh_session, -# revoke_session, -# ) -# from supertokens_python.types import RecipeUserId -# from tests.Flask.utils import extract_all_cookies -# from tests.utils import ( -# TEST_ACCESS_TOKEN_MAX_AGE_CONFIG_KEY, -# TEST_ACCESS_TOKEN_MAX_AGE_VALUE, -# TEST_ACCESS_TOKEN_PATH_CONFIG_KEY, -# TEST_ACCESS_TOKEN_PATH_VALUE, -# TEST_COOKIE_DOMAIN_CONFIG_KEY, -# TEST_COOKIE_DOMAIN_VALUE, -# TEST_COOKIE_SAME_SITE_CONFIG_KEY, -# TEST_COOKIE_SECURE_CONFIG_KEY, -# TEST_DRIVER_CONFIG_ACCESS_TOKEN_PATH, -# TEST_DRIVER_CONFIG_COOKIE_DOMAIN, -# TEST_DRIVER_CONFIG_COOKIE_SAME_SITE, -# TEST_DRIVER_CONFIG_REFRESH_TOKEN_PATH, -# TEST_REFRESH_TOKEN_MAX_AGE_CONFIG_KEY, -# TEST_REFRESH_TOKEN_MAX_AGE_VALUE, -# TEST_REFRESH_TOKEN_PATH_CONFIG_KEY, -# TEST_REFRESH_TOKEN_PATH_KEY_VALUE, -# clean_st, -# reset, -# set_key_value_in_config, -# setup_st, -# start_st, -# create_users, -# ) -# from supertokens_python.recipe.dashboard import DashboardRecipe, InputOverrideConfig -# from supertokens_python.recipe.dashboard.interfaces import RecipeInterface -# from supertokens_python.framework import BaseRequest -# from supertokens_python.querier import Querier -# from supertokens_python.utils import is_version_gte -# from supertokens_python.recipe.passwordless import PasswordlessRecipe, ContactConfig -# from supertokens_python.recipe.dashboard.utils import DashboardConfig - - -# @pytest.fixture(scope="function") # type: ignore -# def flask_app(): -# app = Flask(__name__) -# app.app_context().push() -# Middleware(app) - -# app.testing = True -# init( -# supertokens_config=SupertokensConfig("http://localhost:3567"), -# app_info=InputAppInfo( -# app_name="SuperTokens Demo", -# api_domain="http://api.supertokens.io", -# website_domain="http://supertokens.io", -# api_base_path="/auth", -# ), -# framework="flask", -# recipe_list=[ -# session.init(), -# ], -# ) - -# @app.route("/test") # type: ignore -# def t(): # type: ignore -# return jsonify({}) - -# return app - - -# def test_gevent(flask_app: Any): -# # init(**{**get_st_init_args([session.init(get_token_transfer_method=lambda *_: "cookie")]), "framework": "flask"}) # type: ignore -# start_st() -# client = flask_app.test_client() - -# client.get("/test").json -# client.get("/test").json -# client.get("/test").json -# client.get("/test").json -# client.get("/test").json