Skip to content

Commit 852089e

Browse files
authored
[App] Add ready property to the flow (#15921)
1 parent 77006a2 commit 852089e

File tree

6 files changed

+70
-17
lines changed

6 files changed

+70
-17
lines changed

examples/app_boring/app_dynamic.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ def __init__(self):
3737
super().__init__()
3838
self.dict = Dict()
3939

40+
@property
41+
def ready(self) -> bool:
42+
if "dst_w" in self.dict:
43+
return self.dict["dst_w"].url != ""
44+
return False
45+
4046
def run(self):
4147
# create dynamically the source_work at runtime
4248
if "src_w" not in self.dict:

src/lightning_app/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
1212
- Added the CLI command `lightning delete app` to delete a lightning app on the cloud ([#15783](https://github.com/Lightning-AI/lightning/pull/15783))
1313

1414
- Show a message when `BuildConfig(requirements=[...])` is passed but a `requirements.txt` file is already present in the Work ([#15799](https://github.com/Lightning-AI/lightning/pull/15799))
15+
1516
- Show a message when `BuildConfig(dockerfile="...")` is passed but a `Dockerfile` file is already present in the Work ([#15799](https://github.com/Lightning-AI/lightning/pull/15799))
1617

1718
- Added a CloudMultiProcessBackend which enables running a child App from within the Flow in the cloud ([#15800](https://github.com/Lightning-AI/lightning/pull/15800))
1819

20+
- Added the property `ready` of the LightningFlow to inform when the `Open App` should be visible ([#15921](https://github.com/Lightning-AI/lightning/pull/15921))
21+
1922

2023
### Changed
2124

src/lightning_app/core/app.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ def __init__(
142142
self.exception = None
143143
self.collect_changes: bool = True
144144

145+
# TODO: Enable ready locally for opening the UI.
146+
self.ready = False
147+
145148
# NOTE: Checkpointing is disabled by default for the time being. We
146149
# will enable it when resuming from full checkpoint is supported. Also,
147150
# we will need to revisit the logic at _should_snapshot, since right now
@@ -446,6 +449,9 @@ def run_once(self):
446449
done = True
447450
self.stage = AppStage.STOPPING
448451

452+
if not self.ready:
453+
self.ready = self.root.ready
454+
449455
self._last_run_time = time() - t0
450456

451457
self.on_run_once_end()
@@ -480,13 +486,11 @@ def _run(self) -> bool:
480486
"""
481487
self._original_state = deepcopy(self.state)
482488
done = False
489+
self.ready = self.root.ready
483490

484491
self._start_with_flow_works()
485492

486-
if self.should_publish_changes_to_api and self.api_publish_state_queue:
487-
logger.debug("Publishing the state with changes")
488-
# Push two states to optimize start in the cloud.
489-
self.api_publish_state_queue.put(self.state_vars)
493+
if self.ready and self.should_publish_changes_to_api and self.api_publish_state_queue:
490494
self.api_publish_state_queue.put(self.state_vars)
491495

492496
self._reset_run_time_monitor()
@@ -496,7 +500,7 @@ def _run(self) -> bool:
496500

497501
self._update_run_time_monitor()
498502

499-
if self._has_updated and self.should_publish_changes_to_api and self.api_publish_state_queue:
503+
if self.ready and self._has_updated and self.should_publish_changes_to_api and self.api_publish_state_queue:
500504
self.api_publish_state_queue.put(self.state_vars)
501505

502506
self._has_updated = False

src/lightning_app/core/flow.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ def __getattr__(self, item):
230230
return Path.from_dict(self._paths[item])
231231
return self.__getattribute__(item)
232232

233+
@property
234+
def ready(self) -> bool:
235+
"""Override to customize when your App should be ready."""
236+
flows = self.flows
237+
return all(flow.ready for flow in flows.values()) if flows else True
238+
233239
@property
234240
def changes(self):
235241
return self._changes.copy()

src/lightning_app/testing/testing.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -362,17 +362,7 @@ def run_app_in_cloud(
362362
except playwright._impl._api_types.TimeoutError:
363363
print("'Create Project' dialog not visible, skipping.")
364364

365-
admin_page.locator(f"role=link[name='{name}']").click()
366-
sleep(5)
367-
# Scroll to the bottom of the page. Used to capture all logs.
368-
admin_page.evaluate(
369-
"""
370-
var intervalID = setInterval(function () {
371-
var scrollingElement = (document.scrollingElement || document.body);
372-
scrollingElement.scrollTop = scrollingElement.scrollHeight;
373-
}, 200);
374-
"""
375-
)
365+
admin_page.locator(f'[data-cy="{name}"]').click()
376366

377367
client = LightningClient()
378368
project = _get_project(client)

tests/tests_app/core/test_lightning_flow.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from collections import Counter
44
from copy import deepcopy
55
from dataclasses import dataclass
6+
from functools import partial
67
from time import time
7-
from unittest.mock import ANY
8+
from unittest.mock import ANY, MagicMock
89

910
import pytest
1011
from deepdiff import DeepDiff, Delta
@@ -859,3 +860,46 @@ def test_lightning_flow_flows_and_works():
859860
"root.flows_dict.a.w",
860861
"root.flows_list.0.w",
861862
]
863+
864+
865+
class WorkReady(LightningWork):
866+
def __init__(self):
867+
super().__init__(parallel=True)
868+
self.counter = 0
869+
870+
def run(self):
871+
self.counter += 1
872+
873+
874+
class FlowReady(LightningFlow):
875+
def __init__(self):
876+
super().__init__()
877+
self.w = WorkReady()
878+
879+
@property
880+
def ready(self) -> bool:
881+
return self.w.has_succeeded
882+
883+
def run(self):
884+
self.w.run()
885+
886+
if self.ready:
887+
self._exit()
888+
889+
890+
def test_flow_ready():
891+
"""This test validates the api publish state queue is populated only once ready is True."""
892+
893+
def run_patch(method):
894+
app.api_publish_state_queue = MagicMock()
895+
app.should_publish_changes_to_api = False
896+
method()
897+
898+
app = LightningApp(FlowReady())
899+
app._run = partial(run_patch, method=app._run)
900+
MultiProcessRuntime(app, start_server=False).dispatch()
901+
902+
# Validates the state has been added only when ready was true.
903+
state = app.api_publish_state_queue.put._mock_call_args[0][0]
904+
call_hash = state["works"]["w"]["calls"]["latest_call_hash"]
905+
assert state["works"]["w"]["calls"][call_hash]["statuses"][0]["stage"] == "succeeded"

0 commit comments

Comments
 (0)