Skip to content

Commit

Permalink
Merge pull request #360 from lona-web-org/fscherf/fix-python311-async…
Browse files Browse the repository at this point in the history
…io-wait

fix python311 asyncio wait
  • Loading branch information
fscherf authored Mar 19, 2023
2 parents 8e0cba7 + 99fb591 commit 75b75e7
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lona/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def _assert_view_is_running(self) -> None:

# asyncio #################################################################
def _await_sync(self, awaitable: Awaitable[T]) -> T:
if asyncio.iscoroutine(awaitable):
awaitable = self.server.loop.create_task(awaitable)

async def await_awaitable() -> T:
finished, pending = await asyncio.wait(
[
Expand Down
4 changes: 4 additions & 0 deletions test_project/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@
'views/responses/non_interactive.py::CustomHeadersResponseView',
interactive=False),

# view API
Route('/view-api/sleep',
'views/view_api/sleep.py::SleepTestView'),

# permissions
Route('/permissions/access-denied-in-PermissionMiddleware/',
'views/permissions/denied_in_middleware.py::View'),
Expand Down
5 changes: 5 additions & 0 deletions test_project/views/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ def handle_request(self, request):
<li><a href="/responses/non-interactive/">Non-Interactive</a></li>
</ul>
<h2>View API</h2>
<ul>
<li><a href="/view-api/sleep">View.sleep()</a></li>
</ul>
<h2>Permissions</h2>
<ul>
<li><a href="/permissions/access-denied-in-PermissionMiddleware/">Access denied in Middleware.handle_request()</a></li>
Expand Down
89 changes: 89 additions & 0 deletions test_project/views/view_api/sleep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from datetime import datetime

from lona.html import NumberInput, Button, Table, HTML, Tr, Th, Td, H2
from lona import View


class SleepTestView(View):
def handle_request(self, request):

# setup global state
if 'id' in request.GET:
self.view_id = request.GET['id']

else:
self.view_id = 'sleep-test-view'

self.server.state[self.view_id] = {}

# setup html
seconds = NumberInput(value=1, id='seconds')
button = Button('Sleep', id='sleep')

self.html = HTML(
H2('View.sleep()'),

Table(
Tr(
Th('State:'),
Td(_id='state'),
),
Tr(
Th('Started:'),
Td(_id='started'),
),
Tr(
Th('Stopped:'),
Td(_id='stopped'),
),
),
seconds,
button,
)

self.set_state(sleeping=False, initial=True)

# main loop
while True:
self.show(self.html)
self.await_click(button)

self.set_state(sleeping=True)
self.show(self.html)

self.sleep(seconds.value)
self.set_state(sleeping=False)

def set_state(self, sleeping, initial=False):
with self.html.lock:
state = self.html.query_selector('#state')
started = self.html.query_selector('#started')
stopped = self.html.query_selector('#stopped')

# html
if sleeping:
state.set_text('Sleeping')
started.set_text(str(datetime.now()))
stopped.set_text('-')

else:
state.set_text('Waiting for start')

if initial:
started.set_text('-')
stopped.set_text('-')

else:
stopped.set_text(str(datetime.now()))

# server
if not self.view_id:
return

self.server.state[self.view_id]['state'] = state.get_text()
self.server.state[self.view_id]['started'] = started.get_text()
self.server.state[self.view_id]['stopped'] = stopped.get_text()

def on_cleanup(self) -> None:
if self.view_id:
self.server.state.pop(self.view_id)
70 changes: 70 additions & 0 deletions tests/test_view_sleep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
async def test_view_sleep(lona_project_context):
import os

from playwright.async_api import async_playwright

from lona.pytest import eventually

TEST_PROJECT_PATH = os.path.join(__file__, '../../test_project')

context = await lona_project_context(
project_root=TEST_PROJECT_PATH,
settings=['settings.py'],
)

# FIXME: this is only necessary because get parameters seem not to work
# with the current playwright setup
test_id = 'sleep-test-view'

async with async_playwright() as playwright:
browser = await playwright.chromium.launch()
browser_context = await browser.new_context()

page = await browser_context.new_page()

# short sleep #########################################################
await page.goto(context.make_url('/view-api/sleep'))
await page.wait_for_selector('#lona h2:has-text("View.sleep()")')

assert context.server.state[test_id]['state'] == 'Waiting for start'
assert context.server.state[test_id]['started'] == '-'
assert context.server.state[test_id]['stopped'] == '-'

await page.fill('#seconds', '1')
await page.click('#sleep')

# wait for start
for attempt in eventually():
async with attempt:
assert context.server.state[test_id]['started'] != '-'
assert context.server.state[test_id]['stopped'] == '-'

# wait for stop
for attempt in eventually():
async with attempt:
assert context.server.state[test_id]['started'] != '-'
assert context.server.state[test_id]['stopped'] != '-'

# long, interrupted sleep #############################################
await page.goto(context.make_url('/view-api/sleep'))
await page.wait_for_selector('#lona h2:has-text("View.sleep()")')

assert context.server.state[test_id]['state'] == 'Waiting for start'
assert context.server.state[test_id]['started'] == '-'
assert context.server.state[test_id]['stopped'] == '-'

await page.fill('#seconds', '300')
await page.click('#sleep')

# wait for start
for attempt in eventually():
async with attempt:
assert context.server.state[test_id]['started'] != '-'
assert context.server.state[test_id]['stopped'] == '-'

# close tab
await page.close()

for attempt in eventually():
async with attempt:
assert test_id not in context.server.state

0 comments on commit 75b75e7

Please sign in to comment.