From b38e14f63a856b7f978012ee8483755744eb4d0b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Nov 2022 14:21:08 +0000 Subject: [PATCH 1/3] Support Type[Screen] in App.SCREENS (lazy screens) --- src/textual/app.py | 14 +++++++++----- tests/test_screens.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/textual/app.py b/src/textual/app.py index dba40a9353..9593fea436 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -25,6 +25,7 @@ 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 @@ def __init__( 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 @@ def get_screen(self, screen: Screen | str) -> Screen: 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. @@ -1546,14 +1550,14 @@ async def _close_all(self) -> None: # Close all screens on the stack for screen in self._screen_stack: - if screen._running: + if isinstance(screen, Screen) and screen._running: await self._prune_node(screen) self._screen_stack.clear() # 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 diff --git a/tests/test_screens.py b/tests/test_screens.py index 0841faf510..707bad5dfa 100644 --- a/tests/test_screens.py +++ b/tests/test_screens.py @@ -11,8 +11,37 @@ ) +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() From ffa906563c66a9a62267e1f0a6b45f5ae9c25dad Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 15 Nov 2022 14:58:56 +0000 Subject: [PATCH 2/3] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd2fdb3318..46c7bf16b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 642cb9f12ad487e0ed0f20570d081e8fc3d02713 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Wed, 16 Nov 2022 15:36:32 +0000 Subject: [PATCH 3/3] Remove redundant isinstance --- src/textual/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/app.py b/src/textual/app.py index 81460e278d..e74a90f8a3 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1555,7 +1555,7 @@ async def _close_all(self) -> None: # Close all screens on the stack for screen in self._screen_stack: - if isinstance(screen, Screen) and screen._running: + if screen._running: await self._prune_node(screen) self._screen_stack.clear()