Skip to content

Commit

Permalink
Enable async JupyterApp (#381)
Browse files Browse the repository at this point in the history
* Enable async app

* improve the test

* fix async handling

* fix typing
  • Loading branch information
blink1073 authored Dec 26, 2023
1 parent 29a9aaa commit e33fb74
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 16 deletions.
32 changes: 28 additions & 4 deletions jupyter_core/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
jupyter_path,
jupyter_runtime_dir,
)
from .utils import ensure_dir_exists
from .utils import ensure_dir_exists, get_event_loop

# mypy: disable-error-code="no-untyped-call"

Expand Down Expand Up @@ -259,6 +259,11 @@ def initialize(self, argv: t.Any = None) -> None:
self.update_config(cl_config)
if allow_insecure_writes:
issue_insecure_write_warning()
return

async def initialize_async(self) -> None:
"""Perform async initialization of the application, will be called
after synchronous initialize."""

def start(self) -> None:
"""Start the whole thing"""
Expand All @@ -274,14 +279,33 @@ def start(self) -> None:
self.write_default_config()
raise NoStart()

return

async def start_async(self) -> None:
"""Perform async start of the app, will be called after sync start."""

@classmethod
def launch_instance(cls, argv: t.Any = None, **kwargs: t.Any) -> None:
"""Launch an instance of a Jupyter Application"""
async def _async_launch_instance(cls, argv: t.Any = None, **kwargs: t.Any) -> None:
"""Launch the instance from inside an event loop."""
try:
super().launch_instance(argv=argv, **kwargs)
app = cls.instance(**kwargs)
app.initialize(argv)
await app.initialize_async()
app.start()
await app.start_async()
except NoStart:
return

@classmethod
def launch_instance(cls, argv: t.Any = None, **kwargs: t.Any) -> None:
"""Launch a global instance of this Application
If a global instance already exists, this reinitializes and starts it
"""
loop = get_event_loop()
coro = cls._async_launch_instance(argv, **kwargs)
loop.run_until_complete(coro)


if __name__ == "__main__":
JupyterApp.launch_instance()
32 changes: 20 additions & 12 deletions jupyter_core/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,8 @@ def wrapped(*args: Any, **kwargs: Any) -> Any:
except RuntimeError:
pass

# Run the loop for this thread.
# In Python 3.12, a deprecation warning is raised, which
# may later turn into a RuntimeError. We handle both
# cases.
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(inner)
loop = get_event_loop()
return loop.run_until_complete(inner)

wrapped.__doc__ = coro.__doc__
return wrapped
Expand All @@ -194,3 +184,21 @@ async def ensure_async(obj: Awaitable[T] | T) -> T:
return result
# obj doesn't need to be awaited
return cast(T, obj)


def get_event_loop() -> asyncio.AbstractEventLoop:
# Get the loop for this thread.
# In Python 3.12, a deprecation warning is raised, which
# may later turn into a RuntimeError. We handle both
# cases.
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
try:
loop = asyncio.get_event_loop()
except RuntimeError:
if sys.platform == "win32":
loop = asyncio.WindowsSelectorEventLoopPolicy().new_event_loop()
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
14 changes: 14 additions & 0 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,17 @@ def test_runtime_dir_changed():
app.runtime_dir = td
assert os.path.isdir(td)
shutil.rmtree(td)


class AsyncApp(JupyterApp):
async def initialize_async(self):
self.value = 10

async def start_async(self):
assert self.value == 10


def test_async_app():
AsyncApp.launch_instance([])
app = AsyncApp.instance()
assert app.value == 10

0 comments on commit e33fb74

Please sign in to comment.