mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Support callables in App.SCREENS (#1185)
* Support Type[Screen] in App.SCREENS (lazy screens) * Update CHANGELOG * Remove redundant isinstance
This commit is contained in:
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
https://github.com/Textualize/textual/issues/1094
|
||||
- Added Pilot.wait_for_animation
|
||||
- Added `Widget.move_child` https://github.com/Textualize/textual/issues/1121
|
||||
- Support lazy-instantiated Screens (callables in App.SCREENS) https://github.com/Textualize/textual/pull/1185
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from typing import (
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
Callable,
|
||||
)
|
||||
from weakref import WeakSet, WeakValueDictionary
|
||||
|
||||
@@ -228,7 +229,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
}
|
||||
"""
|
||||
|
||||
SCREENS: dict[str, Screen] = {}
|
||||
SCREENS: dict[str, Screen | Callable[[], Screen]] = {}
|
||||
_BASE_PATH: str | None = None
|
||||
CSS_PATH: CSSPathType = None
|
||||
TITLE: str | None = None
|
||||
@@ -330,7 +331,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self._registry: WeakSet[DOMNode] = WeakSet()
|
||||
|
||||
self._installed_screens: WeakValueDictionary[
|
||||
str, Screen
|
||||
str, Screen | Callable[[], Screen]
|
||||
] = WeakValueDictionary()
|
||||
self._installed_screens.update(**self.SCREENS)
|
||||
|
||||
@@ -998,12 +999,15 @@ class App(Generic[ReturnType], DOMNode):
|
||||
next_screen = self._installed_screens[screen]
|
||||
except KeyError:
|
||||
raise KeyError(f"No screen called {screen!r} installed") from None
|
||||
if callable(next_screen):
|
||||
next_screen = next_screen()
|
||||
self._installed_screens[screen] = next_screen
|
||||
else:
|
||||
next_screen = screen
|
||||
return next_screen
|
||||
|
||||
def _get_screen(self, screen: Screen | str) -> tuple[Screen, AwaitMount]:
|
||||
"""Get an installed screen and a await mount object.
|
||||
"""Get an installed screen and an AwaitMount object.
|
||||
|
||||
If the screen isn't running, it will be registered before it is run.
|
||||
|
||||
@@ -1558,7 +1562,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
# Close pre-defined screens
|
||||
for screen in self.SCREENS.values():
|
||||
if screen._running:
|
||||
if isinstance(screen, Screen) and screen._running:
|
||||
await self._prune_node(screen)
|
||||
|
||||
# Close any remaining nodes
|
||||
|
||||
@@ -11,8 +11,37 @@ skip_py310 = pytest.mark.skipif(
|
||||
)
|
||||
|
||||
|
||||
async def test_installed_screens():
|
||||
class ScreensApp(App):
|
||||
SCREENS = {
|
||||
"home": Screen, # Screen type
|
||||
"one": Screen(), # Screen instance
|
||||
"two": lambda: Screen() # Callable[[], Screen]
|
||||
}
|
||||
|
||||
app = ScreensApp()
|
||||
async with app.run_test() as pilot:
|
||||
pilot.app.push_screen("home") # Instantiates and pushes the "home" screen
|
||||
pilot.app.push_screen("one") # Pushes the pre-instantiated "one" screen
|
||||
pilot.app.push_screen("home") # Pushes the single instance of "home" screen
|
||||
pilot.app.push_screen("two") # Calls the callable, pushes returned Screen instance
|
||||
|
||||
assert len(app.screen_stack) == 5
|
||||
assert app.screen_stack[1] is app.screen_stack[3]
|
||||
assert app.screen is app.screen_stack[4]
|
||||
assert isinstance(app.screen, Screen)
|
||||
assert app.is_screen_installed(app.screen)
|
||||
|
||||
assert pilot.app.pop_screen()
|
||||
assert pilot.app.pop_screen()
|
||||
assert pilot.app.pop_screen()
|
||||
assert pilot.app.pop_screen()
|
||||
with pytest.raises(ScreenStackError):
|
||||
pilot.app.pop_screen()
|
||||
|
||||
|
||||
|
||||
@skip_py310
|
||||
@pytest.mark.asyncio
|
||||
async def test_screens():
|
||||
|
||||
app = App()
|
||||
|
||||
Reference in New Issue
Block a user