7474from textual ._ansi_sequences import SYNC_END , SYNC_START
7575from textual ._ansi_theme import ALABASTER , MONOKAI
7676from textual ._callback import invoke
77+ from textual ._compat import cached_property
7778from textual ._compose import compose
7879from textual ._compositor import CompositorUpdate
7980from textual ._context import active_app , active_message_pump
150151if constants .DEBUG :
151152 warnings .simplefilter ("always" , ResourceWarning )
152153
154+ # `asyncio.get_event_loop()` is deprecated since Python 3.10:
155+ _ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED = sys .version_info >= (3 , 10 , 0 )
156+
153157ComposeResult = Iterable [Widget ]
154158RenderResult : TypeAlias = "RenderableType | Visual | SupportsVisual"
155159"""Result of Widget.render()"""
@@ -645,9 +649,6 @@ def __init__(
645649 """The unhandled exception which is leading to the app shutting down,
646650 or None if the app is still running with no unhandled exceptions."""
647651
648- self ._exception_event : asyncio .Event = asyncio .Event ()
649- """An event that will be set when the first exception is encountered."""
650-
651652 self .title = (
652653 self .TITLE if self .TITLE is not None else f"{ self .__class__ .__name__ } "
653654 )
@@ -841,6 +842,11 @@ def __init__(
841842 )
842843 )
843844
845+ @cached_property
846+ def _exception_event (self ) -> asyncio .Event :
847+ """An event that will be set when the first exception is encountered."""
848+ return asyncio .Event ()
849+
844850 def __init_subclass__ (cls , * args , ** kwargs ) -> None :
845851 for variable_name , screen_collection in (
846852 ("SCREENS" , cls .SCREENS ),
@@ -2140,9 +2146,9 @@ def run(
21402146 App return value.
21412147 """
21422148
2143- async def run_app () -> None :
2149+ async def run_app () -> ReturnType | None :
21442150 """Run the app."""
2145- await self .run_async (
2151+ return await self .run_async (
21462152 headless = headless ,
21472153 inline = inline ,
21482154 inline_no_clear = inline_no_clear ,
@@ -2151,9 +2157,24 @@ async def run_app() -> None:
21512157 auto_pilot = auto_pilot ,
21522158 )
21532159
2154- event_loop = asyncio .get_event_loop () if loop is None else loop
2155- event_loop .run_until_complete (run_app ())
2156- return self .return_value
2160+ if loop is None :
2161+ if _ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED :
2162+ # N.B. This does work with Python<3.10, but global Locks, Events, etc
2163+ # eagerly bind the event loop, and result in Future bound to wrong
2164+ # loop errors.
2165+ return asyncio .run (run_app ())
2166+ try :
2167+ global_loop = asyncio .get_event_loop ()
2168+ except RuntimeError :
2169+ # the global event loop may have been destroyed by someone running
2170+ # asyncio.run(), or asyncio.set_event_loop(None), in which case
2171+ # we need to use asyncio.run() also. (We run this outside the
2172+ # context of an exception handler)
2173+ pass
2174+ else :
2175+ return global_loop .run_until_complete (run_app ())
2176+ return asyncio .run (run_app ())
2177+ return loop .run_until_complete (run_app ())
21572178
21582179 async def _on_css_change (self ) -> None :
21592180 """Callback for the file monitor, called when CSS files change."""
0 commit comments