Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## [Unreleased]

### Fixed

- Match pages and `Summary()` load inside Jupyter and other code that already
runs an asyncio event loop; they raised `asyncio.run() cannot be called from
a running event loop` (#55).

### Added

- `Series.season(key)` finds a season by year or by ESPNcricinfo's two-year
Expand Down
17 changes: 14 additions & 3 deletions espncricinfo/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import json
from concurrent.futures import ThreadPoolExecutor
from datetime import date as _date
from datetime import datetime as _datetime
from typing import Any, TypedDict
Expand Down Expand Up @@ -50,9 +51,19 @@ async def _async_playwright_fetch(url):
return json.loads(m.group(1))


def _playwright_fetch(url):
"""Synchronous wrapper around _async_playwright_fetch."""
return asyncio.run(_async_playwright_fetch(url))
def _playwright_fetch(url: str) -> dict[str, Any]:
"""Synchronous wrapper around _async_playwright_fetch.

Jupyter and other async callers already run an event loop on this thread,
and asyncio.run() refuses to nest inside one, so the fetch then runs on a
worker thread with its own loop.
"""
try:
asyncio.get_running_loop()
except RuntimeError:
return asyncio.run(_async_playwright_fetch(url))
with ThreadPoolExecutor(max_workers=1) as pool:
return pool.submit(lambda: asyncio.run(_async_playwright_fetch(url))).result()


def _as_float(record: dict[str, Any] | None, key: str) -> float | None:
Expand Down
16 changes: 16 additions & 0 deletions tests/test_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,19 @@ def test_no_toss_leaves_toss_fields_none():

def test_series_id_is_int():
assert isinstance(_make_match().series_id, int)


def test_fetch_works_inside_a_running_event_loop():
"""Jupyter runs an event loop; asyncio.run() refuses to nest inside it (#55)."""
import asyncio

import espncricinfo.match as match_module

async def fake_fetch(url):
return {"url": url}

async def notebook_cell():
return match_module._playwright_fetch("https://example.invalid/page")

with patch.object(match_module, "_async_playwright_fetch", fake_fetch):
assert asyncio.run(notebook_cell()) == {"url": "https://example.invalid/page"}
Loading