textual
textual copied to clipboard
Is it possible to run Textual apps inside of a Jupyter Notebook?
This seems like it should work, but I'm running into AsyncIO issues when I try.
RuntimeError Traceback (most recent call last)
Input In [12], in <cell line: 9>()
11 logger.add("debug.log", level="ERROR")
12 if load_config(CONFIG_PATH):
---> 13 UI.run(log="textual.log")
File ~\.conda\envs\basic\lib\site-packages\textual\app.py:206, in App.run(cls, console, screen, driver, **kwargs)
203 app = cls(screen=screen, driver_class=driver, **kwargs)
204 await app.process_messages()
--> 206 asyncio.run(run_app())
File ~\.conda\envs\basic\lib\asyncio\runners.py:33, in run(main, debug)
9 """Execute the coroutine and return the result.
10
11 This function runs the passed coroutine, taking care of
(...)
30 asyncio.run(main())
31 """
32 if events._get_running_loop() is not None:
---> 33 raise RuntimeError(
34 "asyncio.run() cannot be called from a running event loop")
36 if not coroutines.iscoroutine(main):
37 raise ValueError("a coroutine was expected, got {!r}".format(main))
RuntimeError: asyncio.run() cannot be called from a running event loop
I guess there's a workaround for this. In the newer versions of the Jupyter notebook, you can use top level await
keyword. This is done by having a running event loop when you start the jupyter.
The conflict with Textualize here is because of [this line]:(https://github.com/Textualize/textual/blob/main/src/textual/app.py)
async def run_app() -> None:
app = cls(screen=screen, driver_class=driver, **kwargs)
await app.process_messages()
asyncio.run(run_app())
So either Texualize can reuse the active event loop here, or you can disable this in the Jupyter:
%autoawait False
@willmcgugan Do you have any preference on this? I would be happy to help with a patch or write documentation to clarify this.
This is basically what I thought was happening. I am not a Python expert, but I am trying to improve.
I tried to patch the run
method to allow it to re-use the existing event loop:
@classmethod
async def run_notebook(
cls,
console: Console = None,
screen: bool = True,
driver: Type[Driver] = None,
**kwargs,
):
"""Run the app.
Args:
console (Console, optional): Console object. Defaults to None.
screen (bool, optional): Enable application mode. Defaults to True.
driver (Type[Driver], optional): Driver class or None for default. Defaults to None.
"""
async def run_app() -> None:
app = cls(screen=screen, driver_class=driver, **kwargs)
await app.process_messages()
await run_app()
Now, this runs, but it results in deeper errors that I have no clue how to approach:
UnsupportedOperation Traceback (most recent call last)
Input In [16], in <cell line: 10>()
8 logger.add("debug.log", level="ERROR")
10 if load_config(CONFIG_PATH):
---> 11 await UI.run_notebook(log="textual.log")
Input In [14], in UI.run_notebook(cls, console, screen, driver, **kwargs)
359 app = cls(screen=screen, driver_class=driver, **kwargs)
360 await app.process_messages()
--> 362 await run_app()
Input In [14], in UI.run_notebook.<locals>.run_app()
358 async def run_app() -> None:
359 app = cls(screen=screen, driver_class=driver, **kwargs)
--> 360 await app.process_messages()
File ~\.conda\envs\basic\lib\site-packages\textual\app.py:298, in App.process_messages(self)
295 # Wait for the load event to be processed, so we don't go in to application mode beforehand
296 await load_event.wait()
--> 298 driver = self._driver = self.driver_class(self.console, self)
299 try:
300 driver.start_application_mode()
File ~\.conda\envs\basic\lib\site-packages\textual\drivers\windows_driver.py:23, in WindowsDriver.__init__(self, console, target)
21 super().__init__(console, target)
22 self.in_fileno = sys.stdin.fileno()
---> 23 self.out_fileno = sys.stdout.fileno()
25 self.exit_event = Event()
26 self._event_thread: Thread | None = None
File ~\.conda\envs\basic\lib\site-packages\ipykernel\iostream.py:310, in OutStream.fileno(self)
308 return self._original_stdstream_copy
309 else:
--> 310 raise io.UnsupportedOperation("fileno")
UnsupportedOperation: fileno
Now, to my uneducated eyes, this looks like Textual, since it is running on a Windows installation of Python, expects the Jupyter Notebook output to support some kind of functionality that it actually does not support.
As to where I go from here, I have no clue. Would I have to manually select a different driver?
TBH I would be very surprised if you get this working, even after fixing this hurdle. Pretty sure Jupyter Notebook doesn't implement full terminal capabilities.
We do plan on a neat Jupyter integration, but you might have to wait for that.
Did we solve your problem?
Glad we could help!