Skip to content

Commit 3ae137c

Browse files
authored
Merge pull request piccolo-orm#18 from piccolo-orm/improved_run_sync
handling edge cases in run_sync
2 parents 77afec4 + e3f57d2 commit 3ae137c

File tree

4 files changed

+64
-7
lines changed

4 files changed

+64
-7
lines changed

piccolo/apps/user/tables.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
import secrets
77
import typing as t
88

9-
from asgiref.sync import async_to_sync
10-
11-
from piccolo.table import Table
129
from piccolo.columns import Varchar, Boolean, Secret
1310
from piccolo.columns.readable import Readable
11+
from piccolo.table import Table
12+
from piccolo.utils.sync import run_sync
1413

1514

1615
class BaseUser(Table, tablename="piccolo_user"):
@@ -50,7 +49,7 @@ def get_readable(cls) -> Readable:
5049

5150
@classmethod
5251
def update_password_sync(cls, user: t.Union[str, int], password: str):
53-
return async_to_sync(cls.update_password)(user, password)
52+
return run_sync(cls.update_password(user, password))
5453

5554
@classmethod
5655
async def update_password(cls, user: t.Union[str, int], password: str):
@@ -112,7 +111,7 @@ def login_sync(cls, username: str, password: str) -> t.Optional[int]:
112111
"""
113112
Returns the user_id if a match is found.
114113
"""
115-
return async_to_sync(cls.login)(username, password)
114+
return run_sync(cls.login(username, password))
116115

117116
@classmethod
118117
async def login(cls, username: str, password: str) -> t.Optional[int]:

piccolo/utils/sync.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
import asyncio
2+
from concurrent.futures import ThreadPoolExecutor
23

34

45
def run_sync(coroutine):
5-
return asyncio.run(coroutine)
6+
"""
7+
Run the coroutine synchronously - trying to accomodate as many edge cases
8+
as possible.
9+
10+
1. When called within a coroutine.
11+
2. When called from `python -m asyncio`, or iPython with %autoawait
12+
enabled, which means an event loop may already be running in the
13+
current thread.
14+
15+
"""
16+
try:
17+
loop = asyncio.get_event_loop()
18+
except RuntimeError:
19+
return asyncio.run(coroutine)
20+
else:
21+
if loop.is_running():
22+
new_loop = asyncio.new_event_loop()
23+
24+
with ThreadPoolExecutor(max_workers=1) as executor:
25+
future = executor.submit(
26+
new_loop.run_until_complete, coroutine
27+
)
28+
return future.result()
29+
else:
30+
return loop.run_until_complete(coroutine)

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
aiosqlite==0.16.0
2-
asgiref==3.3.0
32
asyncpg==0.21.0
43
black==19.10b0
54
colorama==0.4.*

tests/utils/test_sync.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import asyncio
2+
from unittest import TestCase
3+
4+
from piccolo.utils.sync import run_sync
5+
6+
7+
class TestSync(TestCase):
8+
def test_sync_simple(self):
9+
"""
10+
Test calling a simple coroutine.
11+
"""
12+
run_sync(asyncio.sleep(0.1))
13+
14+
def test_sync_nested(self):
15+
"""
16+
Test calling a coroutine, which contains a call to `run_sync`.
17+
"""
18+
19+
async def test():
20+
run_sync(asyncio.sleep(0.1))
21+
22+
run_sync(test())
23+
24+
def test_sync_stopped_event_loop(self):
25+
"""
26+
Test calling a coroutine, when the current thread has an event loop,
27+
but it isn't running.
28+
"""
29+
loop = asyncio.new_event_loop()
30+
asyncio.set_event_loop(loop)
31+
32+
run_sync(asyncio.sleep(0.1))
33+
34+
asyncio.set_event_loop(None)

0 commit comments

Comments
 (0)