Skip to content

Commit

Permalink
Merge pull request #18 from piccolo-orm/improved_run_sync
Browse files Browse the repository at this point in the history
handling edge cases in run_sync
  • Loading branch information
dantownsend committed Nov 12, 2020
2 parents 77afec4 + e3f57d2 commit 3ae137c
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 7 deletions.
9 changes: 4 additions & 5 deletions piccolo/apps/user/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
import secrets
import typing as t

from asgiref.sync import async_to_sync

from piccolo.table import Table
from piccolo.columns import Varchar, Boolean, Secret
from piccolo.columns.readable import Readable
from piccolo.table import Table
from piccolo.utils.sync import run_sync


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

@classmethod
def update_password_sync(cls, user: t.Union[str, int], password: str):
return async_to_sync(cls.update_password)(user, password)
return run_sync(cls.update_password(user, password))

@classmethod
async def update_password(cls, user: t.Union[str, int], password: str):
Expand Down Expand Up @@ -112,7 +111,7 @@ def login_sync(cls, username: str, password: str) -> t.Optional[int]:
"""
Returns the user_id if a match is found.
"""
return async_to_sync(cls.login)(username, password)
return run_sync(cls.login(username, password))

@classmethod
async def login(cls, username: str, password: str) -> t.Optional[int]:
Expand Down
27 changes: 26 additions & 1 deletion piccolo/utils/sync.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
import asyncio
from concurrent.futures import ThreadPoolExecutor


def run_sync(coroutine):
return asyncio.run(coroutine)
"""
Run the coroutine synchronously - trying to accomodate as many edge cases
as possible.
1. When called within a coroutine.
2. When called from `python -m asyncio`, or iPython with %autoawait
enabled, which means an event loop may already be running in the
current thread.
"""
try:
loop = asyncio.get_event_loop()
except RuntimeError:
return asyncio.run(coroutine)
else:
if loop.is_running():
new_loop = asyncio.new_event_loop()

with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(
new_loop.run_until_complete, coroutine
)
return future.result()
else:
return loop.run_until_complete(coroutine)
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
aiosqlite==0.16.0
asgiref==3.3.0
asyncpg==0.21.0
black==19.10b0
colorama==0.4.*
Expand Down
34 changes: 34 additions & 0 deletions tests/utils/test_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import asyncio
from unittest import TestCase

from piccolo.utils.sync import run_sync


class TestSync(TestCase):
def test_sync_simple(self):
"""
Test calling a simple coroutine.
"""
run_sync(asyncio.sleep(0.1))

def test_sync_nested(self):
"""
Test calling a coroutine, which contains a call to `run_sync`.
"""

async def test():
run_sync(asyncio.sleep(0.1))

run_sync(test())

def test_sync_stopped_event_loop(self):
"""
Test calling a coroutine, when the current thread has an event loop,
but it isn't running.
"""
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

run_sync(asyncio.sleep(0.1))

asyncio.set_event_loop(None)

0 comments on commit 3ae137c

Please sign in to comment.