Skip to content

Commit 5cd70ad

Browse files
committed
expose loop_factory
1 parent 5260e3f commit 5cd70ad

File tree

3 files changed

+48
-42
lines changed

3 files changed

+48
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010
### Fixed
1111

1212
- Fixed `OptionList` causing excessive redrawing https://github.com/Textualize/textual/pull/5766
13+
- Log messages could be written to stdout when there was no app, which could happen when using run_async or threads. Now they will be supressed, unless the env var `TEXTUAL_DEBUG` is set
1314

1415
### Added
1516

src/textual/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,12 @@ def __call__(self, *args: object, **kwargs) -> None:
8585
try:
8686
app = active_app.get()
8787
except LookupError:
88-
print_args = (*args, *[f"{key}={value!r}" for key, value in kwargs.items()])
89-
print(*print_args)
88+
if constants.DEBUG:
89+
print_args = (
90+
*args,
91+
*[f"{key}={value!r}" for key, value in kwargs.items()],
92+
)
93+
print(*print_args)
9094
return
9195
if app.devtools is None or not app.devtools.is_connected:
9296
return
@@ -108,8 +112,12 @@ def __call__(self, *args: object, **kwargs) -> None:
108112
)
109113
except LoggerError:
110114
# If there is not active app, try printing
111-
print_args = (*args, *[f"{key}={value!r}" for key, value in kwargs.items()])
112-
print(*print_args)
115+
if constants.DEBUG:
116+
print_args = (
117+
*args,
118+
*[f"{key}={value!r}" for key, value in kwargs.items()],
119+
)
120+
print(*print_args)
113121

114122
def verbosity(self, verbose: bool) -> Logger:
115123
"""Get a new logger with selective verbosity.

src/textual/app.py

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import threading
1919
import uuid
2020
import warnings
21-
from asyncio import Task, create_task
21+
from asyncio import AbstractEventLoop, Task, create_task
2222
from concurrent.futures import Future
2323
from contextlib import (
2424
asynccontextmanager,
@@ -2059,7 +2059,6 @@ async def run_async(
20592059
from textual.pilot import Pilot
20602060

20612061
app = self
2062-
20632062
auto_pilot_task: Task | None = None
20642063

20652064
if auto_pilot is None and constants.PRESS:
@@ -2092,27 +2091,29 @@ async def run_auto_pilot(
20922091
run_auto_pilot(auto_pilot, pilot), name=repr(pilot)
20932092
)
20942093

2095-
try:
2096-
app._loop = asyncio.get_running_loop()
2097-
app._thread_id = threading.get_ident()
2098-
2099-
await app._process_messages(
2100-
ready_callback=None if auto_pilot is None else app_ready,
2101-
headless=headless,
2102-
inline=inline,
2103-
inline_no_clear=inline_no_clear,
2104-
mouse=mouse,
2105-
terminal_size=size,
2106-
)
2107-
finally:
2094+
app._loop = asyncio.get_running_loop()
2095+
app._thread_id = threading.get_ident()
2096+
with app._context():
21082097
try:
2109-
if auto_pilot_task is not None:
2110-
await auto_pilot_task
2098+
await app._process_messages(
2099+
ready_callback=None if auto_pilot is None else app_ready,
2100+
headless=headless,
2101+
inline=inline,
2102+
inline_no_clear=inline_no_clear,
2103+
mouse=mouse,
2104+
terminal_size=size,
2105+
)
21112106
finally:
21122107
try:
2113-
await asyncio.shield(app._shutdown())
2114-
except asyncio.CancelledError:
2115-
pass
2108+
if auto_pilot_task is not None:
2109+
await auto_pilot_task
2110+
finally:
2111+
try:
2112+
await asyncio.shield(app._shutdown())
2113+
except asyncio.CancelledError:
2114+
pass
2115+
app._loop = None
2116+
app._thread_id = 0
21162117

21172118
return app.return_value
21182119

@@ -2125,6 +2126,7 @@ def run(
21252126
mouse: bool = True,
21262127
size: tuple[int, int] | None = None,
21272128
auto_pilot: AutopilotCallbackType | None = None,
2129+
loop_factory: Callable[[], AbstractEventLoop] | None = None,
21282130
) -> ReturnType | None:
21292131
"""Run the app.
21302132
@@ -2136,35 +2138,30 @@ def run(
21362138
size: Force terminal size to `(WIDTH, HEIGHT)`,
21372139
or None to auto-detect.
21382140
auto_pilot: An auto pilot coroutine.
2139-
2141+
loop_factory: Callable which returns a new asyncio Loop, or `None` to use default.
21402142
Returns:
21412143
App return value.
21422144
"""
21432145

21442146
async def run_app() -> None:
21452147
"""Run the app."""
2146-
self._loop = asyncio.get_running_loop()
2147-
self._thread_id = threading.get_ident()
2148-
with self._context():
2149-
try:
2150-
await self.run_async(
2151-
headless=headless,
2152-
inline=inline,
2153-
inline_no_clear=inline_no_clear,
2154-
mouse=mouse,
2155-
size=size,
2156-
auto_pilot=auto_pilot,
2157-
)
2158-
finally:
2159-
self._loop = None
2160-
self._thread_id = 0
2148+
await self.run_async(
2149+
headless=headless,
2150+
inline=inline,
2151+
inline_no_clear=inline_no_clear,
2152+
mouse=mouse,
2153+
size=size,
2154+
auto_pilot=auto_pilot,
2155+
)
21612156

21622157
if _ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED:
21632158
# N.B. This doesn't work with Python<3.10, as we end up with 2 event loops:
2164-
asyncio.run(run_app())
2159+
asyncio.run(run_app(), loop_factory=loop_factory)
21652160
else:
21662161
# However, this works with Python<3.10:
2167-
event_loop = asyncio.get_event_loop()
2162+
event_loop = (
2163+
asyncio.get_event_loop() if loop_factory is None else loop_factory()
2164+
)
21682165
event_loop.run_until_complete(run_app())
21692166
return self.return_value
21702167

0 commit comments

Comments
 (0)