tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.66k stars 389 forks source link

Unable to perform any orm related stuff from a websocket connection #902

Open Koshkaj opened 3 years ago

Koshkaj commented 3 years ago

Describe the bug Whenever I try to connect to a websocket connection from pytest, and I try to run some queries in the database, it crashes and returns the following error

Traceback:

apps/users/selectors.py:7: in get_user
    return await User.get_or_none(**kwargs)
/usr/local/lib/python3.8/site-packages/tortoise/queryset.py:885: in _execute
    instance_list = await self._db.executor_class(
/usr/local/lib/python3.8/site-packages/tortoise/backends/base/executor.py:124: in execute_select
    _, raw_results = await self.db.execute_query(query.get_sql())
/usr/local/lib/python3.8/site-packages/tortoise/backends/asyncpg/client.py:36: in translate_exceptions_
    return await func(self, *args)
/usr/local/lib/python3.8/site-packages/tortoise/backends/asyncpg/client.py:186: in execute_query
    return len(rows), rows
/usr/local/lib/python3.8/site-packages/tortoise/backends/base/client.py:308: in __aexit__
    await self.pool.release(self.connection)
/usr/local/lib/python3.8/site-packages/asyncpg/pool.py:666: in release
    return await asyncio.shield(ch.release(timeout))
/usr/local/lib/python3.8/site-packages/asyncpg/pool.py:218: in release
    raise ex
/usr/local/lib/python3.8/site-packages/asyncpg/pool.py:208: in release
    await self._con.reset(timeout=budget)
/usr/local/lib/python3.8/site-packages/asyncpg/connection.py:1311: in reset
    await self.execute(reset_query, timeout=timeout)
/usr/local/lib/python3.8/site-packages/asyncpg/connection.py:297: in execute
    return await self._protocol.query(query, timeout)
asyncpg/protocol/protocol.pyx:323: in query
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   ???
E   asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress

See my code below:


async def is_authenticated(token: str = Security(oauth2_scheme)) -> Union[User, Exception]:
    """ Verifies access token, returns user if it is valid (authenticated) else throws an error"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
        if payload['token_type'] != "access":
            raise CustomHTTPException(status_code=status.HTTP_403_FORBIDDEN,
                                      detail="Could not validate credentials - Wrong token type)")
        token_data = TokenData(**payload)
    except jwt.JWTError:
        raise CustomHTTPException(status_code=status.HTTP_403_FORBIDDEN,
                                  detail="Could not validate credentials - Invalid signature of access token")
    user = await get_user(id=token_data.user_id)  **# Error is thrown right here** 
    if not user:
        raise CustomHTTPException(status_code=status.HTTP_404_NOT_FOUND,
                                  detail="User not found")
    return user

async def is_authenticated_ws(websocket: WebSocket,
                              token: str = Query("")):
    # Websocket specific authentication
    try:
        return await is_authenticated(token)
    except:
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)

@websocket_app.websocket("/")
async def websocket_endpoint(websocket: WebSocket,
                             current_user: User = Depends(is_authenticated_ws)):
    if not current_user:
        return

    service = await accept_user_connection(user_id=current_user.id, websocket=websocket)
    await run_until_first_complete(
        (producer_handler, {"service": service}),
        (consumer_handler, {"service": service, "current_user": current_user})
    )

class TestWebSockets:
    @pytest.mark.asyncio
    async def test_websocket_events(self, ws_client, actor_user):
        token = create_token(actor_user.id, generate_verification_code())
        with ws_client.websocket_connect(f"/ws/?token={token['access_token']}") as websocket:
            ...

Obviously it has to do something with the event loop. It is a huge but since in a production environment I should be able to test websocket events without any issues but right now I cant even query something from the database before I connect to a websocket.

Koshkaj commented 3 years ago

@long2ice Could you please have an input on this? is it a bug or could this be resolved ?

long2ice commented 3 years ago

I'm not sure, but tortoise-orm is not related to websocket

Koshkaj commented 3 years ago

I know that it is not related to the websocket in general, all I am saying is that the orm does not work with the websocket tests. Inside websockets I have a normal ORM which works fine on connections but obviously you have to write some unit tests for it right? Therefore when I tried to write unit tests it did not work and threw an error. Therefore the issue is in the implementation of a library.

long2ice commented 3 years ago

Did you run Tortoise.init on start of test?

Koshkaj commented 3 years ago

Did you run Tortoise.init on start of test?

Yeah Obviously, I have it as an event when the app is initialized, thing is that HTTP API Endpoints and it's unit tests are working fine with the library, However the websocket unit tests are not working because it throws an exception which is mentioned above

Koshkaj commented 3 years ago

I am regretting that I chose this library for the production, I literally cant write any tests because of this issue.

I would not recommend this library to anyone, It is not a production ready library at all.

viniciusao commented 2 years ago

I am regretting that I chose this library for the production, I literally cant write any tests because of this issue.

I would not recommend this library to anyone, It is not a production ready library at all.

  • Ton of bugs
  • Literally no one cares about the issues that are opened here daily
  • Very poor and trivial documentation examples

I had the same exception ("different loop") using Tortoise and FastAPI. Tortoise and your _websocketapp must have the same event loop, Tortoise has a module for such integration (FastAPI), so I just had to create a fixture to instantiate the FastAPI app object and pass it to the _register_tortoise_ function.

Koshkaj commented 2 years ago

@viniciusao Thanks for the reply, I have a different FastAPI instance for tests, and have a websocket as a different instance as well which is mounted to the main app and test app. therefore it should have a two different event loops. I can write ordinary API tests without any issues but when I try to connect to the websocket endpoint from the tests, this error is thrown that I described before. There is something wrong with the dynamic asynchronous db creation (asyncpg) and Tortoise's event loop, they come in conflict and thats why error is thrown

I have been wasting too much time to resolve that issue but I could not, I ended up writing tests in a base database in a CI/CD integration level and I manually remove garbage collect those data as soon as tests are finished so no worries.

Regardless, This library sucks lol