Skip to content

feat: add litestar framework #311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed

feat: add litestar framework #311

wants to merge 3 commits into from

Conversation

Goldziher
Copy link

@Goldziher Goldziher commented Apr 15, 2023

Summary of change

(A few sentences about this PR)

Related issues

Test Plan

I added unit tests based on the existing ones

Documentation changes

I'm leaving this outside.

Checklist for important updates

  • Changelog has been updated
  • coreDriverInterfaceSupported.json file has been updated (if needed)
    • Along with the associated array in supertokens_python/constants.py
  • frontendDriverInterfaceSupported.json file has been updated (if needed)
  • Changes to the version if needed
    • In setup.py
    • In supertokens_python/constants.py
  • Had installed and ran the pre-commit hook
  • Issue this PR against the latest non released version branch.
    • To know which one it is, run find the latest released tag (git tag) in the format vX.Y.Z, and then find the latest branch (git branch --all) whose X.Y is greater than the latest released tag.
    • If no such branch exists, then create one from the latest released branch.
  • If have added a new web framework, update the supertokens_python/utils.py file to include that in the FRAMEWORKS variable
  • If added a new recipe that has a User type with extra info, then be sure to change the User type in supertokens_python/types.py

Remaining TODOs for this PR

  • Tests are not passing - due to an SQLError What caused the crash: java.sql.SQLException: Error opening connection, I do not know how to fix this since its coming from the root repository (i am running it correctly in a second terminal)

@rishabhpoddar
Copy link
Contributor

@KShivendu can help with the review for this. Thanks

@rishabhpoddar rishabhpoddar requested a review from KShivendu April 16, 2023 15:55
@Goldziher Goldziher marked this pull request as ready for review April 16, 2023 16:56
@Spectryx
Copy link

Spectryx commented Apr 18, 2023

@Goldziher @rishabhpoddar
It seems i cant get this working yet, i get 404 Not Found when trying to access dashboard or SuperTokens routes.
I've got a self hosted SuperTokens Core instance setup and running.

Heres the log from console

INFO:     127.0.0.1:64909 - "GET //dashboard HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:64910 - "GET /dashboard HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:64910 - "GET /dashboard HTTP/1.1" 404 Not Found
com.supertokens {"t": "2023-04-18T20:26:51.968Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}

com.supertokens {"t": "2023-04-18T20:26:51.968Z", "sdkVer": "0.12.6", "message": "middleware: Not handling because request path did not start with api base path. Request path: /test/unsecure", "file": "supertokens.py:465"}

INFO:     127.0.0.1:64910 - "GET /test/unsecure HTTP/1.1" 200 OK
DEBUG - 2023-04-18 22:26:51,968 - com.supertokens - supertokens - {"t": "2023-04-18T20:26:51.968Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}
DEBUG - 2023-04-18 22:26:51,968 - com.supertokens - supertokens - {"t": "2023-04-18T20:26:51.968Z", "sdkVer": "0.12.6", "message": "middleware: Not handling because request path did not start with api base path. Request path: /test/unsecure", "file": "supertokens.py:465"}
com.supertokens {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}

com.supertokens {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "middleware: Not handling because request path did not start with api base path. Request path: /test/secure", "file": "supertokens.py:465"}

DEBUG - 2023-04-18 22:26:55,103 - com.supertokens - supertokens - {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}
com.supertokens {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "getSession: Started", "file": "recipe\\session\\recipe_implementation.py:355"}

DEBUG - 2023-04-18 22:26:55,103 - com.supertokens - supertokens - {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "middleware: Not handling because request path did not start with api base path. Request path: /test/secure", "file": "supertokens.py:465"}
DEBUG - 2023-04-18 22:26:55,103 - com.supertokens - recipe_implementation - {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "getSession: Started", "file": "recipe\\session\\recipe_implementation.py:355"}
com.supertokens {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "getSession: optional validation: False", "file": "recipe\\session\\recipe_implementation.py:365"}

com.supertokens {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "getSession: UNAUTHORISED because access_token in request is None", "file": "recipe\\session\\recipe_implementation.py:414"}

INFO:     127.0.0.1:64910 - "GET /test/secure HTTP/1.1" 500 Internal Server Error
DEBUG - 2023-04-18 22:26:55,103 - com.supertokens - recipe_implementation - {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "getSession: optional validation: False", "file": "recipe\\session\\recipe_implementation.py:365"}
DEBUG - 2023-04-18 22:26:55,103 - com.supertokens - recipe_implementation - {"t": "2023-04-18T20:26:55.103Z", "sdkVer": "0.12.6", "message": "getSession: UNAUTHORISED because access_token in request is None", "file": "recipe\\session\\recipe_implementation.py:414"}
com.supertokens {"t": "2023-04-18T20:26:55.208Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}

com.supertokens {"t": "2023-04-18T20:26:55.209Z", "sdkVer": "0.12.6", "message": "middleware: Not handling because request path did not start with api base path. Request path: /test/secure", "file": "supertokens.py:465"}

DEBUG - 2023-04-18 22:26:55,208 - com.supertokens - supertokens - {"t": "2023-04-18T20:26:55.208Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}
com.supertokens {"t": "2023-04-18T20:26:55.209Z", "sdkVer": "0.12.6", "message": "getSession: Started", "file": "recipe\\session\\recipe_implementation.py:355"}

DEBUG - 2023-04-18 22:26:55,209 - com.supertokens - supertokens - {"t": "2023-04-18T20:26:55.209Z", "sdkVer": "0.12.6", "message": "middleware: Not handling because request path did not start with api base path. Request path: /test/secure", "file": "supertokens.py:465"}
DEBUG - 2023-04-18 22:26:55,209 - com.supertokens - recipe_implementation - {"t": "2023-04-18T20:26:55.209Z", "sdkVer": "0.12.6", "message": "getSession: Started", "file": "recipe\\session\\recipe_implementation.py:355"}
com.supertokens {"t": "2023-04-18T20:26:55.210Z", "sdkVer": "0.12.6", "message": "getSession: optional validation: False", "file": "recipe\\session\\recipe_implementation.py:365"}

com.supertokens {"t": "2023-04-18T20:26:55.210Z", "sdkVer": "0.12.6", "message": "getSession: UNAUTHORISED because access_token in request is None", "file": "recipe\\session\\recipe_implementation.py:414"}

INFO:     127.0.0.1:64910 - "GET /test/secure HTTP/1.1" 500 Internal Server Error
DEBUG - 2023-04-18 22:26:55,210 - com.supertokens - recipe_implementation - {"t": "2023-04-18T20:26:55.210Z", "sdkVer": "0.12.6", "message": "getSession: optional validation: False", "file": "recipe\\session\\recipe_implementation.py:365"}
DEBUG - 2023-04-18 22:26:55,210 - com.supertokens - recipe_implementation - {"t": "2023-04-18T20:26:55.210Z", "sdkVer": "0.12.6", "message": "getSession: UNAUTHORISED because access_token in request is None", "file": "recipe\\session\\recipe_implementation.py:414"}

And heres the code /im running with Uvicorn)

from supertokens_python import init, get_all_cors_headers, InputAppInfo, SupertokensConfig
from supertokens_python.framework.litestar import get_middleware
from supertokens_python.recipe import emailpassword, session, dashboard
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.litestar import verify_session
from litestar import Litestar, Controller, get
from litestar.config.cors import CORSConfig
from litestar.di import Provide


init(
    app_info=InputAppInfo(
        app_name="TestApp",
        api_domain="http://localhost:7303",
        website_domain="http://localhost:9000",
        api_base_path="/",
        website_base_path="/auth"
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="https://auth.xxxx.com",
        api_key="superSecret"
    ),
    framework='litestar',
    recipe_list=[
        session.init(),
        emailpassword.init(),
        dashboard.init()
    ],
    mode='asgi'
)


cors = CORSConfig(
    allow_origins=[
        "http://localhost:9000"
    ],
    allow_credentials=True,
    allow_methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
    allow_headers=["Content-Type"] + get_all_cors_headers(),
)


class TestController(Controller):
    path = "/test"

    @get("/secure", dependencies={"sess": Provide(verify_session())})
    async def secure(self, sess: SessionContainer) -> dict:
        return {"message": "Success"}

    @get("/unsecure")
    async def unsecure(self) -> str:
        return "Working"


app = Litestar(
    middleware=[get_middleware()],
    route_handlers=[TestController],
    cors_config=cors
)

@Spectryx Spectryx mentioned this pull request Apr 18, 2023
@Spectryx
Copy link

Ahh, i see now - think i might be calling the get_all_cors_headers() to early.
ill test again tomorrow.

@rishabhpoddar
Copy link
Contributor

Not only that, you also seem to be calling the /test/secure API which is not an API we expose. Instead, try calling the /signin POST API (here is the full API spec: https://app.swaggerhub.com/apis/supertokens/FDI)

@Spectryx
Copy link

Spectryx commented Apr 19, 2023

Not only that, you also seem to be calling the /test/secure API which is not an API we expose. Instead, try calling the /signin POST API (here is the full API spec: https://app.swaggerhub.com/apis/supertokens/FDI)

Yeah, i tried that do and Get 404. See the /dashboard request also responds with 404 in the log

@rishabhpoddar
Copy link
Contributor

It seems like when the /dashboard is called, the middleware is not being hit at all. When a middleware is hit, you get the following log from it:

com.supertokens {"t": "2023-04-18T20:26:51.968Z", "sdkVer": "0.12.6", "message": "middleware: Started", "file": "supertokens.py:458"}

And this log is not present before the INFO: 127.0.0.1:64910 - "GET /dashboard HTTP/1.1" 404 Not Found line. Would you know why?

@Goldziher
Copy link
Author

Hmm, i have no idea either.

I can't actually run the tests for this, so i can't properly debug it.

@rishabhpoddar
Copy link
Contributor

@Goldziher what error(s) are you facing when setting up the test env? We have a contributing guide. Just a note, you will need to setup the supertokens-core on your machine to run the tests (instrs on that are here: https://github.com/supertokens/supertokens-core/blob/master/CONTRIBUTING.md#development-setup)

@Goldziher
Copy link
Author

@Goldziher what error(s) are you facing when setting up the test env? We have a contributing guide. Just a note, you will need to setup the supertokens-core on your machine to run the tests (instrs on that are here: https://github.com/supertokens/supertokens-core/blob/master/CONTRIBUTING.md#development-setup)

I have done that. I wrote in the description of the PR what im getting.

@rishabhpoddar
Copy link
Contributor

Ah ok. I had missed that. Are you connecting it to a real database? I would suggest just setting up the core with the in memory db. The modules.txt in the supertokens-root should look like this:

// put module name like <module name>,<branch name>,<github username>(if contributing with a forked repository) and then call ./loadModules script
core,master
plugin-interface,master
sqlite-plugin,master

@Goldziher
Copy link
Author

Ah ok. I had missed that. Are you connecting it to a real database? I would suggest just setting up the core with the in memory db. The modules.txt in the supertokens-root should look like this:

// put module name like <module name>,<branch name>,<github username>(if contributing with a forked repository) and then call ./loadModules script
core,master
plugin-interface,master
sqlite-plugin,master

Aight, i currently got the flu or something. When i feel up to it I'll give it a go and update with the results. Feel free to leave review comments regarding the code - I'll address.

@Spectryx
Copy link

Spectryx commented Apr 20, 2023

@Goldziher @rishabhpoddar
I was able to get the test env up and running and ran some tests. Here is part of the output, all the tests failed for the same reason. It seems like we are forgetting to await something? If you did not already notice, im pretty new at programming so i might be wrong ^^,

6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/spectryx/supertokens/supertokens-root/supertokens-python/venv/bin/python
cachedir: .pytest_cache
rootdir: /home/spectryx/supertokens/supertokens-root/supertokens-python, configfile: pytest.ini
plugins: respx-0.19.2, requests-mock-1.9.3, mock-3.8.2, asyncio-0.18.0, Faker-18.4.0, anyio-3.5.0, rerunfailures-10.3
asyncio: mode=legacy
collected 18 items

tests/litestar/test_litestar.py::test_login_refresh FAILED                                                       [  5%]
tests/litestar/test_litestar.py::test_login_logout FAILED                                                        [ 11%]
tests/litestar/test_litestar.py::test_login_info FAILED                                                          [ 16%]
tests/litestar/test_litestar.py::test_login_handle FAILED                                                        [ 22%]
tests/litestar/test_litestar.py::test_login_refresh_error_handler FAILED                                         [ 27%]
tests/litestar/test_litestar.py::test_custom_response FAILED                                                     [ 33%]
tests/litestar/test_litestar.py::test_optional_session FAILED                                                    [ 38%]
tests/litestar/test_litestar.py::test_should_clear_all_response_during_refresh_if_unauthorized[cookie] FAILED    [ 44%]
tests/litestar/test_litestar.py::test_should_clear_all_response_during_refresh_if_unauthorized[header] FAILED    [ 50%]
tests/litestar/test_litestar.py::test_revoking_session_after_create_new_session_with_throwing_unauthorized_error[cookie] FAILED [ 55%]
tests/litestar/test_litestar.py::test_revoking_session_after_create_new_session_with_throwing_unauthorized_error[header] FAILED [ 61%]
tests/litestar/test_litestar.py::test_search_with_email_t FAILED                                                 [ 66%]
tests/litestar/test_litestar.py::test_search_with_email_multiple_email_entry FAILED                              [ 72%]
tests/litestar/test_litestar.py::test_search_with_email_iresh FAILED                                             [ 77%]
tests/litestar/test_litestar.py::test_search_with_phone_plus_one FAILED                                          [ 83%]
tests/litestar/test_litestar.py::test_search_with_phone_one_bracket FAILED                                       [ 88%]
tests/litestar/test_litestar.py::test_search_with_provider_google FAILED                                         [ 94%]
tests/litestar/test_litestar.py::test_search_with_provider_google_and_phone_1 FAILED                             [100%]

======================================================= FAILURES =======================================================__________________________________________________ test_login_refresh __________________________________________________
litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f8b5508f8b0>

    @mark.asyncio
    async def test_login_refresh(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                    override=session.InputOverrideConfig(apis=apis_override_session),
                )
            ],
            mode="asgi",
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:183:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f8b58843490>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
----------------------------------------------- Captured stderr teardown -----------------------------------------------/usr/lib/python3.10/asyncio/base_events.py:671: RuntimeWarning: coroutine 'RecipeImplementation.__init__.<locals>.call_get_handshake_info' was never awaited
  self._ready.clear()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
ERROR - 2023-04-20 19:44:17,324 - asyncio - base_events - Task was destroyed but it is pending!
task: <Task pending name='Task-3' coro=<RecipeImplementation.__init__.<locals>.call_get_handshake_info() running at /home/spectryx/supertokens/supertokens-root/supertokens-python/supertokens_python/recipe/session/recipe_implementation.py:113>>
------------------------------------------------ Captured log teardown -------------------------------------------------ERROR    asyncio:base_events.py:1744 Task was destroyed but it is pending!
task: <Task pending name='Task-3' coro=<RecipeImplementation.__init__.<locals>.call_get_handshake_info() running at /home/spectryx/supertokens/supertokens-root/supertokens-python/supertokens_python/recipe/session/recipe_implementation.py:113>>
__________________________________________________ test_login_logout ___________________________________________________
litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f8b5468d460>

    @mark.asyncio
    async def test_login_logout(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                )
            ],
            mode="asgi",
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:253:

@Goldziher
Copy link
Author

Ha yes, we should be using the starlite async test client

@Spectryx
Copy link

Spectryx commented Apr 21, 2023

Ive been trying to get the tests to work, but i cant! Seems like my knowledge is at its limits. Ive tried replacing all TestClient with AsyncTestClient[Litestar] without any luck.
Example:

@mark.asyncio
async def test_login_refresh(litestar_test_client: AsyncTestClient[Litestar]):
    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="litestar",
        recipe_list=[
            session.init(
                anti_csrf="VIA_TOKEN",
                cookie_domain="supertokens.io",
                get_token_transfer_method=get_token_transfer_method,
                override=session.InputOverrideConfig(apis=apis_override_session),
            )
        ],
        mode="asgi",
    )
    start_st()


    response_1 = await litestar_test_client.get("/login")
    cookies_1 = extract_all_cookies(response_1)

    assert response_1.headers.get("anti-csrf") is not None
    assert cookies_1["sAccessToken"]["domain"] == TEST_DRIVER_CONFIG_COOKIE_DOMAIN
    assert cookies_1["sRefreshToken"]["domain"] == TEST_DRIVER_CONFIG_COOKIE_DOMAIN
    assert cookies_1["sAccessToken"]["path"] == TEST_DRIVER_CONFIG_ACCESS_TOKEN_PATH
    assert cookies_1["sRefreshToken"]["path"] == TEST_DRIVER_CONFIG_REFRESH_TOKEN_PATH
    assert cookies_1["sAccessToken"]["httponly"]
    assert cookies_1["sRefreshToken"]["httponly"]
    assert (
        cookies_1["sAccessToken"]["samesite"].lower()
        == TEST_DRIVER_CONFIG_COOKIE_SAME_SITE
    )
    assert (
        cookies_1["sRefreshToken"]["samesite"].lower()
        == TEST_DRIVER_CONFIG_COOKIE_SAME_SITE
    )

    response_3 = await litestar_test_client.post(
        url="/refresh",
        headers={"anti-csrf": response_1.headers.get("anti-csrf")},
        cookies={
            "sRefreshToken": cookies_1["sRefreshToken"]["value"],
        },
    )
    cookies_3 = extract_all_cookies(response_3)

    assert cookies_3["sAccessToken"]["value"] != cookies_1["sAccessToken"]["value"]
    assert cookies_3["sRefreshToken"]["value"] != cookies_1["sRefreshToken"]["value"]
    assert response_3.headers.get("anti-csrf") is not None
    assert cookies_3["sAccessToken"]["domain"] == TEST_DRIVER_CONFIG_COOKIE_DOMAIN
    assert cookies_3["sRefreshToken"]["domain"] == TEST_DRIVER_CONFIG_COOKIE_DOMAIN
    assert cookies_3["sRefreshToken"]["path"] == TEST_DRIVER_CONFIG_REFRESH_TOKEN_PATH
    assert cookies_3["sAccessToken"]["httponly"]
    assert cookies_3["sRefreshToken"]["httponly"]
    assert (
        cookies_3["sAccessToken"]["samesite"].lower()
        == TEST_DRIVER_CONFIG_COOKIE_SAME_SITE
    )
    assert (
        cookies_3["sRefreshToken"]["samesite"].lower()
        == TEST_DRIVER_CONFIG_COOKIE_SAME_SITE
    )


@Goldziher
Copy link
Author

Try

async with litestar_test_client as client:
   response = await client.get(...)

The test client is an async context manager

@Spectryx
Copy link

Spectryx commented Apr 21, 2023

Try

async with litestar_test_client as client:
   response = await client.get(...)

The test client is an async context manager

Ok, thanks - but cant get it working. Sorry if im beeing a noob ^^
Ive tried: (just cut out the relevant pieces of code)
Ive also tried the same with both TestClient and AsyncTestClient

@fixture(scope="function")
async def litestar_test_client() -> AsyncTestClient[Litestar]:

return AsyncTestClient(app)

@mark.asyncio
async def test_login_refresh(litestar_test_client: AsyncTestClient[Litestar]):

    async with litestar_test_client as client:
        response_1 = await client.get("/login")
    cookies_1 = extract_all_cookies(response_1)
sr/lib/python3.10/asyncio/base_events.py:671: RuntimeWarning: coroutine 'RecipeImplementation.__init__.<locals>.call_get_handshake_info' was never awaited
  self._ready.clear()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
-------------------------------------------------------------------------------------------------------- Captured log teardown ---------------------------------------------------------------------------------------------------------
ERROR    asyncio:base_events.py:1744 Task was destroyed but it is pending!
task: <Task pending name='Task-6' coro=<RecipeImplementation.__init__.<locals>.call_get_handshake_info() running at /home/spectryx/supertokens/supertokens-python/supertokens_python/recipe/session/recipe_implementation.py:113>>      
======================================================================================================= short test summary info ========================================================================================================
FAILED tests/litestar/test_litestar.py::test_login_refresh - RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.
FAILED tests/litestar/test_litestar.py::test_login_refresh_error_handler - RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-2_0'.
================================================================================================== 2 failed, 16 deselected in 10.62s ===================================================================================================
ERROR - 2023-04-21 12:09:15,138 - asyncio - base_events - Task was destroyed but it is pending!
task: <Task pending name='Task-6' coro=<RecipeImplementation.__init__.<locals>.call_get_handshake_info() running at /home/spectryx/supertokens/supertokens-python/supertokens_python/recipe/session/recipe_implementation.py:113>>      
sys:1: RuntimeWarning: coroutine 'start_blocking_portal.<locals>.run_portal' was never awaited

@Goldziher
Copy link
Author

Try this

@fixture(scope="function")
def litestar_test_client() -> TestClient[Litestar]:

return TestClient(app)

def test_login_refresh(litestar_test_client: TestClient[Litestar]):

    with litestar_test_client as client:
        response_1 = client.get("/login")
    cookies_1 = extract_all_cookies(response_1

@Spectryx
Copy link

Spectryx commented Apr 21, 2023

Failed, see error output.
With mode=asgi
(see log below for mode=wsgi)

tests/litestar/test_litestar.py::test_login_refresh FAILED                                                                                                                                                                       [ 50%]
tests/litestar/test_litestar.py::test_login_refresh_error_handler FAILED                                                                                                                                                         [100%]

=============================================================================================================== FAILURES ===============================================================================================================
__________________________________________________________________________________________________________ test_login_refresh __________________________________________________________________________________________________________

litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f246e9867a0>

    @mark.asyncio
    def test_login_refresh(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                    override=session.InputOverrideConfig(apis=apis_override_session),
                )
            ],
            mode="asgi",
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:183:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f247223b5b0>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
--------------------------------------------------------------------------------------------------------- Captured stderr call ---------------------------------------------------------------------------------------------------------
/home/spectryx/supertokens/supertokens-python/supertokens_python/utils.py:200: RuntimeWarning: Inconsistent mode detected, check if you are using the right asgi / wsgi mode
  warnings.warn(
___________________________________________________________________________________________________ test_login_refresh_error_handler ___________________________________________________________________________________________________

litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f246caf3ca0>

    @mark.asyncio
    def test_login_refresh_error_handler(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                )
            ],
            mode="asgi",
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:438:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f247223b5b0>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-2_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
--------------------------------------------------------------------------------------------------------- Captured stderr call ---------------------------------------------------------------------------------------------------------
/home/spectryx/supertokens/supertokens-python/supertokens_python/utils.py:200: RuntimeWarning: Inconsistent mode detected, check if you are using the right asgi / wsgi mode
  warnings.warn(
======================================================================================================= short test summary info ========================================================================================================
FAILED tests/litestar/test_litestar.py::test_login_refresh - RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.
FAILED tests/litestar/test_litestar.py::test_login_refresh_error_handler - RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-2_0'.
================================================================================================== 2 failed, 16 deselected in 10.84s ===================================================================================================

With mode=wsgi

tests/litestar/test_litestar.py::test_login_refresh FAILED                                                                                                                                                                       [ 50%]
tests/litestar/test_litestar.py::test_login_refresh_error_handler FAILED                                                                                                                                                         [100%]

=============================================================================================================== FAILURES ===============================================================================================================
__________________________________________________________________________________________________________ test_login_refresh __________________________________________________________________________________________________________

litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f8a47596960>

    @mark.asyncio
    def test_login_refresh(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                    override=session.InputOverrideConfig(apis=apis_override_session),
                )
            ],
            mode="wsgi"
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:183:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f8a4ae37640>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
___________________________________________________________________________________________________ test_login_refresh_error_handler ___________________________________________________________________________________________________

litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f8a459df140>

    @mark.asyncio
    def test_login_refresh_error_handler(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                )
            ],
            mode="wsgi"
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:438:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f8a4ae37640>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-2_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
======================================================================================================= short test summary info ========================================================================================================
FAILED tests/litestar/test_litestar.py::test_login_refresh - RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.
FAILED tests/litestar/test_litestar.py::test_login_refresh_error_handler - RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-2_0'.
================================================================================================== 2 failed, 16 deselected in 10.74s ===================================================================================================

@Goldziher
Copy link
Author

I updated the tests, its still failing:

FAILED                              [  5%]/Users/naamanhirschfeld/workspace/supertokens-python/supertokens_python/utils.py:200: RuntimeWarning: Inconsistent mode detected, check if you are using the right asgi / wsgi mode
  warnings.warn(

tests/litestar/test_litestar.py:159 (test_login_refresh)
litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x104881550>

    def test_login_refresh(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                    override=session.InputOverrideConfig(apis=apis_override_session),
                )
            ],
            mode="asgi",
        )
>       start_st()

test_litestar.py:180: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

host = 'localhost', port = '3567'

    def start_st(host: str = "localhost", port: str = "3567"):
        pid_after = pid_before = __get_list_of_process_ids()
        run(
            "cd "
            + INSTALLATION_PATH
            + " && java -Djava.security.egd=file:/dev/urandom -classpath "
            '"./core/*:./plugin-interface/*" io.supertokens.Main ./ DEV host='
            + host
            + " port="
            + str(port)
            + " test_mode &",
            shell=True,
            stdout=DEVNULL,
        )
        for _ in range(35):
            pid_after = __get_list_of_process_ids()
            if len(pid_after) != len(pid_before):
                break
            sleep(0.5)
        if len(pid_after) == len(pid_before):
>           raise Exception("could not start ST process")
E           Exception: could not start ST process

../utils.py:151: Exception

Im afraid I dont have more time to dedicate to this. You guys are welcome to either take over this PR or close it.

@Spectryx
Copy link

Ive also tried testing with the updated PR, without any luck - but im not getting the same error as @Goldziher . Im still getting event loop errors and i want to try and help with this but i cant figure out where to start looking for this error. It has something to do with the litestar_test_client but not sure what im looking for.

platform linux -- Python 3.10.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/spectryx/supertokens/supertokens-python/venv/bin/python
cachedir: .pytest_cache
rootdir: /home/spectryx/supertokens/supertokens-python, configfile: pytest.ini
plugins: respx-0.19.2, requests-mock-1.9.3, mock-3.8.2, asyncio-0.18.0, Faker-18.4.0, anyio-3.5.0, rerunfailures-10.3
asyncio: mode=legacy
collected 18 items / 16 deselected / 2 selected

tests/litestar/test_litestar.py::test_login_refresh FAILED                                                       [ 50%]
tests/litestar/test_litestar.py::test_login_refresh_error_handler FAILED                                         [100%]

======================================================= FAILURES =======================================================
__________________________________________________ test_login_refresh __________________________________________________

litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f96fcfd4580>

    def test_login_refresh(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                    override=session.InputOverrideConfig(apis=apis_override_session),
                )
            ],
            mode="asgi",
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:182:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f9700723430>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
------------------------------------------------- Captured stderr call -------------------------------------------------
/home/spectryx/supertokens/supertokens-python/supertokens_python/utils.py:200: RuntimeWarning: Inconsistent mode detected, check if you are using the right asgi / wsgi mode
  warnings.warn(
___________________________________________ test_login_refresh_error_handler ___________________________________________

litestar_test_client = <litestar.testing.client.sync_client.TestClient object at 0x7f96fc5e1a10>

    def test_login_refresh_error_handler(litestar_test_client: TestClient[Litestar]):
        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="litestar",
            recipe_list=[
                session.init(
                    anti_csrf="VIA_TOKEN",
                    cookie_domain="supertokens.io",
                    get_token_transfer_method=get_token_transfer_method,
                )
            ],
            mode="asgi",
        )
        start_st()

>       with litestar_test_client as client:

tests/litestar/test_litestar.py:433:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.10/site-packages/litestar/testing/client/sync_client.py:92: in __enter__
    self.blocking_portal = portal = stack.enter_context(self.portal())
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/litestar/testing/client/base.py:114: in portal
    with start_blocking_portal(
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
venv/lib/python3.10/site-packages/anyio/from_thread.py:416: in start_blocking_portal
    run_future.result()
/usr/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
/usr/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
/usr/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
venv/lib/python3.10/site-packages/anyio/_core/_eventloop.py:56: in run
    return asynclib.run(func, *args, **backend_options)
venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:233: in run
    return native_run(wrapper(), debug=debug)
venv/lib/python3.10/site-packages/nest_asyncio.py:30: in run
    loop = asyncio.get_event_loop()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x7f9700723430>

    def get_event_loop(self):
        """Get the event loop for the current context.

        Returns an instance of EventLoop or raises an exception.
        """
        if (self._local._loop is None and
                not self._local._set_called and
                threading.current_thread() is threading.main_thread()):
            self.set_event_loop(self.new_event_loop())

        if self._local._loop is None:
>           raise RuntimeError('There is no current event loop in thread %r.'
                               % threading.current_thread().name)
E           RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-2_0'.

/usr/lib/python3.10/asyncio/events.py:656: RuntimeError
------------------------------------------------- Captured stderr call -------------------------------------------------
/home/spectryx/supertokens/supertokens-python/supertokens_python/utils.py:200: RuntimeWarning: Inconsistent mode detected, check if you are using the right asgi / wsgi mode
  warnings.warn(
=============================================== short test summary info ================================================
FAILED tests/litestar/test_litestar.py::test_login_refresh - RuntimeError: There is no current event loop in thread '...
FAILED tests/litestar/test_litestar.py::test_login_refresh_error_handler - RuntimeError: There is no current event lo...
========================================== 2 failed, 16 deselected in 10.02s ===========================================

@Spectryx
Copy link

I dont know if @Goldziher or @rishabhpoddar are able to point me in the right direction? Im trying google and some of the answers say;

You are trying to run asyncio.get_event_loop() in some thread other than the main thread - however, asyncio only generates an event loop for the main thread.

Instead use new_event_loop:

asyncio.new_event_loop().run_until_complete(main())

But im not sure if the answer applies here and how to fix it?

Copy link
Contributor

@KShivendu KShivendu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your contribution. We really appreciate it!

I tried setting up Litestar with Supertokens Python SDK and discovered some points for improvement.

Let's resolve the error handling comment first and then we can proceed with further reviews :)

@@ -26,7 +27,7 @@

def init(
app_info: InputAppInfo,
framework: Literal["fastapi", "flask", "django"],
framework: SupportedFrameworks,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Thanks!

Comment on lines +28 to +49
try:
result = await st.middleware(
LitestarRequest(request),
LitestarResponse(Response[Any](content=None)),
)
except SuperTokensError as e:
result = await st.handle_supertokens_error(
LitestarRequest(request),
e,
LitestarResponse(Response[Any](content=None)),
)

if isinstance(result, LitestarResponse):
if (
session_container := request.state.get("supertokens")
) and isinstance(session_container, SessionContainer):
manage_session_post_response(session_container, result)

await result.response(scope, receive, send)
return

await self.app(scope, receive, send)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except SuperTokensError as e isn't working as expected.

I visited http://localhost:8000/test/secure without any session (based on config given by @Spectryx) returned:

{
  "status_code": 500,
  "detail": "UnauthorisedError('Session does not exist. Are you sending the session tokens in the request with the appropriate token transfer method?')"
}

I used the debugger and found that await self.app(...) raises UnauthorizedError from verify_session and that isn't handled here. So I changed it to:

    class Middleware(AbstractMiddleware):
        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
            st = Supertokens.get_instance()
            request = Request[Any, Any, Any](scope, receive, send)

            try:
                result = await st.middleware(
                    LitestarRequest(request),
                    LitestarResponse(Response[Any](content=None)),
                )
                if isinstance(result, LitestarResponse):
                    if (
                        session_container := request.state.get("supertokens")
                    ) and isinstance(session_container, SessionContainer):
                        manage_session_post_response(session_container, result)

                    await result.response(scope, receive, send)
                    return

                async def send_wrapper(message: "Message") -> None:
                    await send(message)

                await self.app(scope, receive, send_wrapper)
                print("Sent response") # reaches this line
            except SuperTokensError as e:
                result = await st.handle_supertokens_error(
                    LitestarRequest(request),
                    e,
                    LitestarResponse(Response[Any](content=None)),
                )
                if isinstance(result, LitestarResponse):
                    await result.response(scope, receive, send)
                    return
            except Exception as e:
                print(e)
                raise e
                
            print("Middleware ran") # reaches this line 

    return Middleware

The debugger reaches print("Sent response") line despite the error response. This seems different from other frameworks. How does Litestar handle errors? Is https://docs.litestar.dev/2/usage/exceptions.html the only way?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wrap the chain of middlewares inside an exception handling Middleware. You can customize its behavior by defining exception handler functions mapped to either status codes or exception types.

It's similar to Starlette in this regard.

@Goldziher Goldziher closed this Aug 25, 2023
@bpereto bpereto mentioned this pull request Apr 8, 2024
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants