pytorch-lightning
861 строка · 32.3 Кб
1# Copyright The Lightning AI team.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import inspect16import warnings17from copy import deepcopy18from datetime import datetime19from types import FrameType20from typing import TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast21
22from deepdiff import DeepHash23
24from lightning.app.core.work import LightningWork25from lightning.app.frontend import Frontend26from lightning.app.storage.drive import Drive, _maybe_create_drive27from lightning.app.storage.path import Path28from lightning.app.utilities.app_helpers import _is_json_serializable, _LightningAppRef, _set_child_name, is_overridden29from lightning.app.utilities.component import _sanitize_state30from lightning.app.utilities.exceptions import ExitAppException, LightningFlowException31from lightning.app.utilities.introspection import _is_init_context, _is_run_context32from lightning.app.utilities.packaging.cloud_compute import CloudCompute, _maybe_create_cloud_compute33
34if TYPE_CHECKING:35from lightning.app.runners.backends.backend import Backend36
37
38class LightningFlow:39_INTERNAL_STATE_VARS = {40# Internal protected variables that are still part of the state (even though they are prefixed with "_")41"_paths",42"_layout",43}44
45def __init__(self) -> None:46"""The LightningFlow is used by the :class:`~lightning.app.core.app.LightningApp` to coordinate and manage47long- running jobs contained, the :class:`~lightning.app.core.work.LightningWork`.
48
49A LightningFlow is characterized by:
50
51* A set of state variables.
52* Long-running jobs (:class:`~lightning.app.core.work.LightningWork`).
53* Its children ``LightningFlow`` or ``LightningWork`` with their state variables.
54
55**State variables**
56
57The LightningFlow are special classes whose attributes require to be
58json-serializable (e.g., int, float, bool, list, dict, ...).
59
60They also may not reach into global variables unless they are constant.
61
62The attributes need to be all defined in `__init__` method,
63and eventually assigned to different values throughout the lifetime of the object.
64However, defining new attributes outside of `__init__` is not allowed.
65
66Attributes taken together represent the state of the component.
67Components are capable of retrieving their state and that of their
68children recursively at any time. They are also capable of setting
69an externally provided state recursively to its children.
70
71**Execution model and work**
72
73The entry point for execution is the ``run`` method at the root component.
74The ``run`` method of the root component may call the ``run`` method of its children, and the children
75may call the ``run`` methods of their children and so on.
76
77The ``run`` method of the root component is called repeatedly in a while loop forever until the app gets
78terminated. In this programming model (reminiscent of React, Vue or Streamlit from the JavaScript world),
79the values of the state variables, or their changes, are translated into actions throughout the component
80hierarchy. This means the flow of execution will only be affected by state changes in a component or one of
81its children, and otherwise remain idempotent.
82
83The actions themselves are self-contained within :class:`~lightning.app.core.work.LightningWork`.
84The :class:`~lightning.app.core.work.LightningWork` are typically used for long-running jobs,
85like downloading a dataset, performing a query, starting a computationally heavy script.
86While one may access any state variable in a LightningWork from a LightningFlow, one may not
87directly call methods of other components from within a LightningWork as LightningWork can't have any children.
88This limitation allows applications to be distributed at scale.
89
90**Component hierarchy and App**
91
92Given the above characteristics, a root LightningFlow, potentially containing
93children components, can be passed to an App object and its execution
94can be distributed (each LightningWork will be run within its own process
95or different arrangements).
96
97Example:
98
99>>> from lightning.app import LightningFlow
100>>> class RootFlow(LightningFlow):
101... def __init__(self):
102... super().__init__()
103... self.counter = 0
104... def run(self):
105... self.counter += 1
106...
107>>> flow = RootFlow()
108>>> flow.run()
109>>> assert flow.counter == 1
110>>> assert flow.state["vars"]["counter"] == 1
111
112"""
113self._state: set = set()114self._name: str = ""115self._flows: set = set()116self._works: set = set()117self._structures: set = set()118self._calls: dict = {}119self._changes: dict = {}120self._layout: Union[List[Dict], Dict] = {}121self._paths: dict = {}122self._backend: Optional["Backend"] = None123# tuple instead of a list so that it cannot be modified without using the setter124self._lightningignore: Tuple[str, ...] = ()125
126@property127def name(self) -> str:128"""Return the current LightningFlow name."""129return self._name or "root"130
131def __setattr__(self, name: str, value: Any) -> None:132attr = getattr(self.__class__, name, None)133if isinstance(attr, property) and attr.fset is not None:134return attr.fset(self, value)135
136from lightning.app.structures import Dict as ComponentDict137from lightning.app.structures import List as ComponentList138
139if (140not _is_init_context(self)141and name not in self._state142and name not in self._paths143and (144not isinstance(value, (LightningWork, LightningFlow))145or (isinstance(value, (LightningWork, LightningFlow)) and not _is_run_context(self))146)147and name not in self._works.union(self._flows)148and self._is_state_attribute(name)149):150raise AttributeError(f"Cannot set attributes that were not defined in __init__: {name}")151
152if isinstance(value, str) and value.startswith("lit://"):153value = Path(value)154
155if self._is_state_attribute(name):156if hasattr(self, name):157if name in self._flows and value != getattr(self, name):158raise AttributeError(f"Cannot set attributes as the flow can't be changed once defined: {name}")159
160if name in self._works and value != getattr(self, name):161raise AttributeError(f"Cannot set attributes as the work can't be changed once defined: {name}")162
163if isinstance(value, (list, dict)) and value:164_type = (LightningFlow, LightningWork, ComponentList, ComponentDict)165if isinstance(value, list) and all(isinstance(va, _type) for va in value):166value = ComponentList(*value)167
168if isinstance(value, dict) and all(isinstance(va, _type) for va in value.values()):169value = ComponentDict(**value)170
171if isinstance(value, LightningFlow):172self._flows.add(name)173_set_child_name(self, value, name)174if name in self._state:175self._state.remove(name)176# Attach the backend to the flow and its children work.177if self._backend:178LightningFlow._attach_backend(value, self._backend)179for work in value.works():180work._register_cloud_compute()181
182elif isinstance(value, LightningWork):183self._works.add(name)184_set_child_name(self, value, name)185if name in self._state:186self._state.remove(name)187if self._backend:188self._backend._wrap_run_method(_LightningAppRef().get_current(), value) # type: ignore[arg-type]189value._register_cloud_compute()190
191elif isinstance(value, (ComponentDict, ComponentList)):192self._structures.add(name)193_set_child_name(self, value, name)194
195_backend = getattr(self, "backend", None)196if _backend is not None:197value._backend = _backend198
199for flow in value.flows:200if _backend is not None:201LightningFlow._attach_backend(flow, _backend)202
203for work in value.works:204work._register_cloud_compute()205if _backend is not None:206_backend._wrap_run_method(_LightningAppRef().get_current(), work)207
208elif isinstance(value, Path):209# In the init context, the full name of the Flow and Work is not known, i.e., we can't serialize210# the path without losing the information of origin and consumer. Hence, we delay the serialization211# of the path object until the app is instantiated.212if not _is_init_context(self):213self._paths[name] = value.to_dict()214self._state.add(name)215
216elif isinstance(value, Drive):217value = deepcopy(value)218value.component_name = self.name219self._state.add(name)220
221elif isinstance(value, CloudCompute):222self._state.add(name)223
224elif _is_json_serializable(value):225self._state.add(name)226
227if not isinstance(value, Path) and hasattr(self, "_paths") and name in self._paths:228# The attribute changed type from Path to another229self._paths.pop(name)230
231else:232raise AttributeError(233f"Only JSON-serializable attributes are currently supported"234f" (str, int, float, bool, tuple, list, dict etc.) to be part of {self} state. "235f"Found the attribute {name} with {value} instead. \n"236"HINT: Private attributes defined as follows `self._x = y` won't be shared between components "237"and therefore don't need to be JSON-serializable."238)239
240super().__setattr__(name, value)241return None242
243@staticmethod244def _attach_backend(flow: "LightningFlow", backend: "Backend") -> None:245"""Attach the backend to all flows and its children."""246flow._backend = backend247
248for name in flow._structures:249getattr(flow, name)._backend = backend250
251for child_flow in flow.flows.values():252child_flow._backend = backend253for name in child_flow._structures:254getattr(child_flow, name)._backend = backend255
256app = _LightningAppRef().get_current()257
258for child_work in flow.works():259child_work._backend = backend260backend._wrap_run_method(app, child_work) # type: ignore[arg-type]261
262def __getattr__(self, item: str) -> Any:263if item in self.__dict__.get("_paths", {}):264return Path.from_dict(self._paths[item])265return self.__getattribute__(item)266
267@property268def ready(self) -> bool:269"""Override to customize when your App should be ready."""270flows = self.flows271return all(flow.ready for flow in flows.values()) if flows else True272
273@property274def changes(self) -> dict:275return self._changes.copy()276
277@property278def state(self) -> dict:279"""Returns the current flow state along its children."""280children_state = {child: getattr(self, child).state for child in self._flows}281works_state = {work: getattr(self, work).state for work in self._works}282return {283"vars": _sanitize_state({el: getattr(self, el) for el in self._state}),284# this may have the challenge that ret cannot be pickled, we'll need to handle this285"calls": self._calls.copy(),286"flows": children_state,287"works": works_state,288"structures": {child: getattr(self, child).state for child in self._structures},289"changes": {},290}291
292@property293def state_vars(self) -> dict:294children_state = {child: getattr(self, child).state_vars for child in self._flows}295works_state = {work: getattr(self, work).state_vars for work in self._works}296return {297"vars": _sanitize_state({el: getattr(self, el) for el in self._state}),298"flows": children_state,299"works": works_state,300"structures": {child: getattr(self, child).state_vars for child in self._structures},301}302
303@property304def state_with_changes(self) -> dict:305children_state = {child: getattr(self, child).state_with_changes for child in self._flows}306works_state = {work: getattr(self, work).state_with_changes for work in self._works}307return {308"vars": _sanitize_state({el: getattr(self, el) for el in self._state}),309# this may have the challenge that ret cannot be pickled, we'll need to handle this310"calls": self._calls.copy(),311"flows": children_state,312"works": works_state,313"structures": {child: getattr(self, child).state_with_changes for child in self._structures},314"changes": self.changes,315}316
317@property318def flows(self) -> Dict[str, "LightningFlow"]:319"""Return its children LightningFlow."""320flows = {}321for el in sorted(self._flows):322flow = getattr(self, el)323flows[flow.name] = flow324flows.update(flow.flows)325for struct_name in sorted(self._structures):326flows.update(getattr(self, struct_name).flows)327return flows328
329@property330def lightningignore(self) -> Tuple[str, ...]:331"""Programmatic equivalent of the ``.lightningignore`` file."""332return self._lightningignore333
334@lightningignore.setter335def lightningignore(self, lightningignore: Tuple[str, ...]) -> None:336if self._backend is not None:337raise RuntimeError(338f"Your app has been already dispatched, so modifying the `{self.name}.lightningignore` does not have an"339" effect"340)341self._lightningignore = lightningignore342
343def works(self, recurse: bool = True) -> List[LightningWork]:344"""Return its :class:`~lightning.app.core.work.LightningWork`."""345works = [getattr(self, el) for el in sorted(self._works)]346if not recurse:347return works348for child_name in sorted(self._flows):349for w in getattr(self, child_name).works(recurse=recurse):350works.append(w)351for struct_name in sorted(self._structures):352for w in getattr(self, struct_name).works:353works.append(w)354return works355
356def named_works(self, recurse: bool = True) -> List[Tuple[str, LightningWork]]:357"""Return its :class:`~lightning.app.core.work.LightningWork` with their names."""358return [(w.name, w) for w in self.works(recurse=recurse)]359
360def set_state(self, provided_state: Dict, recurse: bool = True) -> None:361"""Method to set the state to this LightningFlow, its children and362:class:`~lightning.app.core.work.LightningWork`.
363
364Arguments:
365provided_state: The state to be reloaded
366recurse: Whether to apply the state down children.
367
368"""
369for k, v in provided_state["vars"].items():370if isinstance(v, Dict):371v = _maybe_create_drive(self.name, v)372if isinstance(v, Dict):373v = _maybe_create_cloud_compute(v)374setattr(self, k, v)375self._changes = provided_state["changes"]376self._calls.update(provided_state["calls"])377
378if not recurse:379return380
381for child, state in provided_state["flows"].items():382getattr(self, child).set_state(state)383for work, state in provided_state["works"].items():384getattr(self, work).set_state(state)385for structure, state in provided_state["structures"].items():386getattr(self, structure).set_state(state)387
388def stop(self, end_msg: str = "") -> None:389"""Method used to exit the application."""390if end_msg:391print(end_msg)392raise ExitAppException393
394def fail(self, end_msg: str = "") -> None:395"""Method used to exit and fail the application."""396if end_msg:397print(end_msg)398raise LightningFlowException399
400def _exit(self, end_msg: str = "") -> None:401"""Used to exit the application.402
403Private method.
404
405.. deprecated:: 1.9.0
406This function is deprecated and will be removed in 2.0.0. Use :meth:`stop` instead.
407
408"""
409warnings.warn(410DeprecationWarning(411"This function is deprecated and will be removed in 2.0.0. Use `LightningFlow.stop` instead."412)413)414
415return self.stop(end_msg=end_msg)416
417@staticmethod418def _is_state_attribute(name: str) -> bool:419"""Every public attribute is part of the state by default and all protected (prefixed by '_') or private420(prefixed by '__') attributes are not.
421
422Exceptions are listed in the `_INTERNAL_STATE_VARS` class variable.
423
424"""
425return name in LightningFlow._INTERNAL_STATE_VARS or not name.startswith("_")426
427def run(self, *args: Any, **kwargs: Any) -> None:428"""Override with your own logic."""429pass430
431def schedule(432self, cron_pattern: str, start_time: Optional[datetime] = None, user_key: Optional[str] = None433) -> bool:434"""The schedule method is used to run a part of the flow logic on timely manner.435
436.. code-block:: python
437
438from lightning.app import LightningFlow
439
440
441class Flow(LightningFlow):
442def run(self):
443if self.schedule("hourly"):
444print("run some code every hour")
445
446Arguments:
447cron_pattern: The cron pattern to provide. Learn more at https://crontab.guru/.
448start_time: The start time of the cron job.
449user_key: Optional key used to improve the caching mechanism.
450
451A best practice is to avoid running a dynamic flow or work under the self.schedule method.
452Instead, instantiate them within the condition, but run them outside.
453
454.. code-block:: python
455
456from lightning.app import LightningFlow
457from lightning.app.structures import List
458
459
460class SchedulerDAG(LightningFlow):
461def __init__(self):
462super().__init__()
463self.dags = List()
464
465def run(self):
466if self.schedule("hourly"):
467self.dags.append(DAG(...))
468
469for dag in self.dags:
470payload = dag.run()
471
472**Learn more about Scheduling**
473
474.. raw:: html
475
476<div class="display-card-container">
477<div class="row">
478
479.. displayitem::
480:header: Schedule your components
481:description: Learn more scheduling.
482:col_css: col-md-4
483:button_link: ../../../glossary/scheduling.html
484:height: 180
485:tag: Basic
486
487.. displayitem::
488:header: Build your own DAG
489:description: Learn more DAG scheduling with examples.
490:col_css: col-md-4
491:button_link: ../../../examples/app/dag/dag.html
492:height: 180
493:tag: Basic
494
495.. raw:: html
496
497</div>
498</div>
499<br />
500
501"""
502if not user_key:503frame = cast(FrameType, inspect.currentframe()).f_back504assert frame is not None505cache_key = f"{cron_pattern}.{frame.f_code.co_filename}.{frame.f_lineno}"506else:507cache_key = user_key508
509call_hash = f"{self.schedule.__name__}:{DeepHash(cache_key)[cache_key]}"510
511if "scheduling" not in self._calls:512self._calls["scheduling"] = {}513
514entered = call_hash in self._calls["scheduling"]515
516expr_aliases = {517"midnight": "@midnight",518"hourly": "@hourly",519"daily": "@daily",520"weekly": "@weekly",521"monthly": "@monthly",522"yearly": "@yearly",523"annually": "@annually",524}525
526if cron_pattern in expr_aliases:527cron_pattern = expr_aliases[cron_pattern]528
529if not entered:530if not start_time:531start_time = datetime.now()532
533schedule_metadata = {534"running": False,535"cron_pattern": cron_pattern,536"start_time": str(start_time.isoformat()),537"name": self.name,538}539
540self._calls["scheduling"][call_hash] = schedule_metadata541app = _LightningAppRef().get_current()542if app:543app._register_schedule(call_hash, schedule_metadata)544return True545
546return self._calls["scheduling"][call_hash]["running"]547
548def _enable_schedule(self, call_hash: str) -> None:549self._calls["scheduling"][call_hash]["running"] = True550
551def _disable_running_schedules(self) -> None:552if "scheduling" not in self._calls:553return554for call_hash in self._calls["scheduling"]:555self._calls["scheduling"][call_hash]["running"] = False556
557def configure_layout(self) -> Union[Dict[str, Any], List[Dict[str, Any]], Frontend]:558"""Configure the UI layout of this LightningFlow.559
560You can either
561
5621. Return a single :class:`~lightning.app.frontend.frontend.Frontend` object to serve a user interface
563for this Flow.
5642. Return a single dictionary to expose the UI of a child flow.
5653. Return a list of dictionaries to arrange the children of this flow in one or multiple tabs.
566
567**Example:** Serve a static directory (with at least a file index.html inside).
568
569.. code-block:: python
570
571from lightning.app.frontend import StaticWebFrontend
572
573
574class Flow(LightningFlow):
575...
576
577def configure_layout(self):
578return StaticWebFrontend("path/to/folder/to/serve")
579
580**Example:** Serve a streamlit UI (needs the streamlit package to be installed).
581
582.. code-block:: python
583
584from lightning.app.frontend import StaticWebFrontend
585
586
587class Flow(LightningFlow):
588...
589
590def configure_layout(self):
591return StreamlitFrontend(render_fn=my_streamlit_ui)
592
593
594def my_streamlit_ui(state):
595# add your streamlit code here!
596import streamlit as st
597
598
599**Example:** Arrange the UI of my children in tabs (default UI by Lightning).
600
601.. code-block:: python
602
603class Flow(LightningFlow):
604def configure_layout(self):
605return [
606dict(name="First Tab", content=self.child0),
607dict(name="Second Tab", content=self.child1),
608dict(name="Lightning", content="https://lightning.ai"),
609]
610
611If you don't implement ``configure_layout``, Lightning will collect all children and display their UI in a tab
612(if they have their own ``configure_layout`` implemented).
613
614Note:
615This hook gets called at the time of app creation and then again as part of the loop. If desired, the
616returned layout configuration can depend on the state. The only exception are the flows that return a
617:class:`~lightning.app.frontend.frontend.Frontend`. These need to be provided at the time of app creation
618in order for the runtime to start the server.
619
620**Learn more about adding UI**
621
622.. raw:: html
623
624<div class="display-card-container">
625<div class="row">
626
627.. displayitem::
628:header: Add a web user interface (UI)
629:description: Learn more how to integrate several UIs.
630:col_css: col-md-4
631:button_link: ../../../workflows/add_web_ui/index.html
632:height: 180
633:tag: Basic
634
635.. raw:: html
636
637</div>
638</div>
639<br />
640
641"""
642return [{"name": name, "content": component} for (name, component) in self.flows.items()]643
644def experimental_iterate(self, iterable: Iterable, run_once: bool = True, user_key: str = "") -> Generator:645"""This method should always be used with any kind of iterable to ensure its fault tolerant.646
647If you want your iterable to always be consumed from scratch, you shouldn't use this method.
648
649Arguments:
650iterable: Iterable to iterate over. The iterable shouldn't have side effects or be random.
651run_once: Whether to run the entire iteration only once.
652Otherwise, it would restart from the beginning.
653user_key: Key to be used to track the caching mechanism.
654
655"""
656if not isinstance(iterable, Iterable):657raise TypeError(f"An iterable should be provided to `self.iterate` method. Found {iterable}")658
659# TODO: Find a better way. Investigated using __reduce__, but state change invalidate the cache.660if not user_key:661frame = cast(FrameType, inspect.currentframe()).f_back662assert frame is not None663cache_key = f"{frame.f_code.co_filename}.{frame.f_code.co_firstlineno}"664else:665cache_key = user_key666
667call_hash = f"{self.experimental_iterate.__name__}:{DeepHash(cache_key)[cache_key]}"668entered = call_hash in self._calls669has_started = entered and self._calls[call_hash]["counter"] > 0670has_finished = entered and self._calls[call_hash]["has_finished"]671
672if has_finished:673if not run_once:674self._calls[call_hash].update({"counter": 0, "has_finished": False})675else:676return range(0)677
678if not has_started:679self._calls[call_hash] = {680"name": self.experimental_iterate.__name__,681"call_hash": call_hash,682"counter": 0,683"has_finished": False,684}685
686skip_counter = max(self._calls[call_hash]["counter"], 0)687
688for counter, value in enumerate(iterable):689if skip_counter:690skip_counter -= 1691continue692self._calls[call_hash].update({"counter": counter})693yield value694
695self._calls[call_hash].update({"has_finished": True})696
697def configure_commands(self) -> None:698"""Configure the commands of this LightningFlow.699
700Returns a list of dictionaries mapping a command name to a flow method.
701
702.. code-block:: python
703
704class Flow(LightningFlow):
705def __init__(self):
706super().__init__()
707self.names = []
708
709def configure_commands(self):
710return {"my_command_name": self.my_remote_method}
711
712def my_remote_method(self, name):
713self.names.append(name)
714
715Once the app is running with the following command:
716
717.. code-block:: bash
718
719lightning_app run app app.py
720
721.. code-block:: bash
722
723lightning_app my_command_name --args name=my_own_name
724
725"""
726raise NotImplementedError727
728def configure_api(self) -> None:729"""Configure the API routes of the LightningFlow.730
731Returns a list of HttpMethod such as Post or Get.
732
733.. code-block:: python
734
735from lightning.app import LightningFlow
736from lightning.app.api import Post
737
738from pydantic import BaseModel
739
740
741class HandlerModel(BaseModel):
742name: str
743
744
745class Flow(LightningFlow):
746def __init__(self):
747super().__init__()
748self.names = []
749
750def handler(self, config: HandlerModel) -> None:
751self.names.append(config.name)
752
753def configure_api(self):
754return [Post("/v1/api/request", self.handler)]
755
756Once the app is running, you can access the Swagger UI of the app
757under the ``/docs`` route.
758
759"""
760raise NotImplementedError761
762def state_dict(self) -> dict:763"""Returns the current flow state but not its children."""764return {765"vars": _sanitize_state({el: getattr(self, el) for el in self._state}),766"calls": self._calls.copy(),767"changes": {},768"flows": {},769"works": {},770"structures": {},771}772
773def load_state_dict(774self,775flow_state: Dict[str, Any],776children_states: Dict[str, Any],777strict: bool = True,778) -> None:779"""Reloads the state of this flow and its children.780
781.. code-block:: python
782
783
784class Work(LightningWork):
785def __init__(self):
786super().__init__()
787self.counter = 0
788
789def run(self):
790self.counter += 1
791
792
793class Flow(LightningFlow):
794def run(self):
795# dynamically create a work.
796if not getattr(self, "w", None):
797self.w = WorkReload()
798
799self.w.run()
800
801def load_state_dict(self, flow_state, children_states, strict) -> None:
802# 1: Re-instantiate the dynamic work
803self.w = Work()
804
805# 2: Make any states modification / migration.
806...
807
808# 3: Call the parent ``load_state_dict`` to
809# recursively reload the states.
810super().load_state_dict(
811flow_state,
812children_states,
813strict,
814)
815
816Arguments:
817flow_state: The state of the current flow.
818children_states: The state of the dynamic children of this flow.
819strict: Whether to raise an exception if a dynamic
820children hasn't been re-created.
821
822"""
823self.set_state(flow_state, recurse=False)824direct_children_states = {k: v for k, v in children_states.items() if "." not in k}825for child_name, state in direct_children_states.items():826child = getattr(self, child_name, None)827if isinstance(child, LightningFlow):828lower_children_states = {829k.replace(child_name + ".", ""): v830for k, v in children_states.items()831if k.startswith(child_name) and k != child_name832}833child.load_state_dict(state, lower_children_states, strict=strict)834elif isinstance(child, LightningWork):835child.set_state(state)836elif strict:837raise ValueError(f"The component {child_name} wasn't instantiated for the component {self.name}")838
839
840class _RootFlow(LightningFlow):841def __init__(self, work: LightningWork) -> None:842super().__init__()843self.work = work844
845@property846def ready(self) -> bool:847ready = getattr(self.work, "ready", None)848if ready is not None:849return ready850return self.work.url != ""851
852def run(self) -> None:853if self.work.has_succeeded:854self.work.stop()855self.stop()856self.work.run()857
858def configure_layout(self) -> list:859if is_overridden("configure_layout", self.work):860return [{"name": "Main", "content": self.work}]861return []862