emmett-framework / emmett

The web framework for inventors
BSD 3-Clause "New" or "Revised" License
1.08k stars 72 forks source link

Async function db insert returns ValueError #467

Open SvenKeimpema opened 1 year ago

SvenKeimpema commented 1 year ago

Whenever i do a insert with db.(table).insert in an async function that is managed by asyncio i seem to get an ValueError. I am not 100% sure why this occurs but i do know this only happend withing the async function run_taak(), see run_taak code. I tried this without the asyncio and it seemed to be working fine. Is this due to the framework not supporting async db insert's(which would be strange bc even if the insert is async the db should still be able to do it sync)?? Or am i not able to use asyncio for db insert's(if not is there any other lib I can use that supports the same functionality?)

import asyncio
async def run(user_id):
   loop = asyncio.get_event_loop()

    for task in tasks:
        tasks = db(db.taak.id > 0).select(db.taak.id, db.taak.requirement_id)
        # if task.requirement_id is None:
            loop.create_task(run_taak(db, task.id, user_id))

I seem to get an value error

emmett_1        | [ERROR] Task exception was never retrieved
emmett_1        | future: <Task finished name='Task-5' coro=<run_taak() done, defined at /app/testem/tools/taak_tools.py:16> exception=ValueError('INSERT INTO "taak_voortgang_response"("request_uuid","ran_ok","body","status_code","nieuweling_id","taak_id") VALUES (\'a\',\'T\',\'test\',200,\'1\',\'1\');')>
emmett_1        | Traceback (most recent call last):
emmett_1        |   File "/app/testem/tools/taak_tools.py", line 40, in run_taak
emmett_1        |     db.taak_voortgang_response.insert(taak_id=1, body="test", ran_ok=True, request_uuid="a", nieuweling_id=1,
emmett_1        |   File "/usr/local/lib/python3.11/site-packages/emmett/orm/objects.py", line 143, in insert
emmett_1        |     ret = self._db._adapter.insert(self, row.op_values())
emmett_1        |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emmett_1        |   File "/usr/local/lib/python3.11/site-packages/emmett/orm/adapters.py", line 62, in wrapped
emmett_1        |     return f(adapter, *args, **kwargs)
emmett_1        |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
emmett_1        |   File "/usr/local/lib/python3.11/site-packages/emmett/orm/adapters.py", line 139, in insert
emmett_1        |     raise e
emmett_1        |   File "/usr/local/lib/python3.11/site-packages/emmett/orm/adapters.py", line 134, in insert
emmett_1        |     adapter.execute(query)
emmett_1        |   File "/usr/local/lib/python3.11/site-packages/pydal/adapters/__init__.py", line 65, in wrap
emmett_1        |     raise ValueError(args[1])
emmett_1        | ValueError: INSERT INTO "taak_voortgang_response"("request_uuid","ran_ok","body","status_code","nieuweling_id","taak_id") VALUES ('a','T','test',200,'1','1');

run_taak code:

async def run_taak(db, taak_id, user_id):
    db.taak_voortgang_response.insert(taak_id=1, body="test", ran_ok=True, request_uuid="a", nieuweling_id=1,
                                            status_code=200)
gi0baro commented 1 year ago

Emmett's ORM currently doesn't provide async operations support.

You get such error because the connection context is not preserved between tasks. Considering that the inner adapters are not async, please consider your code is actually blocking, and thus you're blocking the loop.

Is there any specific reason you want to run such code in an async context? There's no benefit at all vs sync.

SvenKeimpema commented 1 year ago

ye i am currently also running async http request which i really don't want to wait for in the main-worker, there are maybe like 20 requests that can take 3-10 seconds each to complete and making the user wait until that is done is kinda not worth it. and whenever a http quests(post quest) is done it will need to do an insert with information about how the request went like: "is_ok", "status_code", "body", "url", ect...

SvenKeimpema commented 1 year ago

@gi0baro is there maybe a way i can give the context to the next task as an temporary fix. I have thought about fixing it right now through http request wich is pretty janky(they are signed with jwt btw so i know that i was the one who send it). It would be nice to beable to preserve the context or give the context to a different task. is this an upcomming feature or is it not going to be added? also do you have an release date currently for emmett #3.0 or is that a while away from now?

gi0baro commented 1 year ago

@SvenKeimpema if your code runs in a route, the database pipe should work. And you don't even need to call create_task, just await your coroutine.

In case your code runs outside of your request flow, you can manually handle the connection in your coroutine, eg:

async def my_coro():
    await http.get(whatever)
    async with db.connection():
        db.table.insert(...)

Regarding Emmett 3.0: there's not ETA at the moment. To be completely honest, I don't think is gonna happen this year.