diff --git a/.actions/assistant.py b/.actions/assistant.py
index 664f3e8a89e75..c1e2f08c340cb 100644
--- a/.actions/assistant.py
+++ b/.actions/assistant.py
@@ -14,10 +14,8 @@
import glob
import logging
import os
-import pathlib
import re
import shutil
-import tarfile
import tempfile
import urllib.request
from distutils.version import LooseVersion
@@ -35,11 +33,6 @@
"requirements/pytorch/strategies.txt",
"requirements/pytorch/examples.txt",
),
- "app": (
- "requirements/app/app.txt",
- "requirements/app/cloud.txt",
- "requirements/app/ui.txt",
- ),
"fabric": (
"requirements/fabric/base.txt",
"requirements/fabric/strategies.txt",
@@ -216,30 +209,6 @@ def distribute_version(src_folder: str, ver_file: str = "version.info") -> None:
shutil.copy2(ver_template, fpath)
-def _download_frontend(pkg_path: str, version: str = "v0.0.0"):
- """Downloads an archive file for a specific release of the Lightning frontend and extracts it to the correct
- directory."""
-
- try:
- frontend_dir = pathlib.Path(pkg_path, "ui")
- download_dir = tempfile.mkdtemp()
-
- shutil.rmtree(frontend_dir, ignore_errors=True)
- # TODO: remove this once lightning-ui package is ready as a dependency
- frontend_release_url = f"https://lightning-packages.s3.amazonaws.com/ui/{version}.tar.gz"
- response = urllib.request.urlopen(frontend_release_url)
-
- file = tarfile.open(fileobj=response, mode="r|gz")
- file.extractall(path=download_dir) # noqa: S202
-
- shutil.move(download_dir, frontend_dir)
- print("The Lightning UI has successfully been downloaded!")
-
- # If installing from source without internet connection, we don't want to break the installation
- except Exception:
- print("The Lightning UI downloading has failed!")
-
-
def _load_aggregate_requirements(req_dir: str = "requirements", freeze_requirements: bool = False) -> None:
"""Load all base requirements from all particular packages and prune duplicates.
@@ -466,7 +435,7 @@ def pull_docs_files(
raise RuntimeError(f"Requesting file '{zip_url}' does not exist or it is just unavailable.")
with zipfile.ZipFile(zip_file, "r") as zip_ref:
- zip_ref.extractall(tmp) # noqa: S202
+ zip_ref.extractall(tmp)
zip_dirs = [d for d in glob.glob(os.path.join(tmp, "*")) if os.path.isdir(d)]
# check that the extracted archive has only repo folder
diff --git a/.azure/app-cloud-e2e.yml b/.azure/app-cloud-e2e.yml
deleted file mode 100644
index 53174c42aba02..0000000000000
--- a/.azure/app-cloud-e2e.yml
+++ /dev/null
@@ -1,206 +0,0 @@
-# Python package
-# Create and test a Python package on multiple Python versions.
-# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
-# https://docs.microsoft.com/azure/devops/pipelines/languages/python
-
-trigger:
- tags:
- include:
- - "*"
- branches:
- include:
- - "master"
- - "release/*"
- - "refs/tags/*"
-
-schedules:
- - cron: "0 0 * * *" # At the end of every day
- displayName: Daily midnight testing
- branches:
- include:
- - "master"
-
-pr:
- branches:
- include:
- - "master"
- - "release/*"
- paths:
- include:
- - ".actions/*"
- - ".azure/app-cloud-e2e.yml"
- - "src/lightning/__about__.py"
- - "src/lightning/__init__.py"
- - "src/lightning/__main__.py"
- - "src/lightning/__setup__.py"
- - "src/lightning/__version__.py"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "examples/app/**"
- - "requirements/app/**"
- - "tests/integrations_app/**"
- - "setup.py"
- exclude:
- - "!tests/integrations_app/flagship/**"
- - "requirements/*/docs.txt"
- - "*.md"
- - "**/*.md"
-
-# variables are automatically exported as environment variables so this will override pip's default cache dir
-variables:
- - name: pip_cache_dir
- value: $(Pipeline.Workspace)/.pip
- - name: local_id
- value: $(Build.BuildId)
- - name: video_artifact_dir
- value: ./videos
-
-jobs:
- - job: test_e2e
- pool: "azure-cpus"
- container:
- # see all available tags: https://mcr.microsoft.com/en-us/product/playwright/python/tags
- image: mcr.microsoft.com/playwright/python:v1.38.0-focal
- options: "--shm-size=4gb"
- strategy:
- matrix:
- "App: v0_app":
- name: "v0_app"
- dir: "public"
- "App: boring_app":
- name: "boring_app"
- dir: "public"
- "App: template_streamlit_ui":
- name: "template_streamlit_ui"
- dir: "public"
- "App: template_react_ui":
- name: "template_react_ui"
- dir: "public"
- # 'App: template_jupyterlab': # TODO: clarify where these files lives
- # name: "template_jupyterlab"
- "App: installation_commands_app":
- name: "installation_commands_app"
- dir: "public"
- "App: drive":
- name: "drive"
- dir: "public"
- "App: payload":
- name: "payload"
- dir: "public"
- "App: commands_and_api":
- name: "commands_and_api"
- dir: "public"
- "App: quick_start":
- name: "quick_start"
- dir: "public"
- "App: idle_timeout":
- name: "idle_timeout"
- dir: "local"
- "App: collect_failures":
- name: "collect_failures"
- dir: "local"
- "App: custom_work_dependencies":
- name: "custom_work_dependencies"
- dir: "local"
- timeoutInMinutes: "15"
- cancelTimeoutInMinutes: "1"
- # values: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases?view=azure-devops&tabs=yaml#workspace
- workspace:
- clean: all
- variables:
- FREEZE_REQUIREMENTS: "1"
- HEADLESS: "1"
- PACKAGE_LIGHTNING: "1"
- CLOUD: "1"
- VIDEO_LOCATION: $(video_artifact_dir)
- PR_NUMBER: $(local_id)
- TEST_APP_NAME: $(name)
- TEST_APP_FOLDER: $(dir)
- HAR_LOCATION: "./artifacts/hars"
- SLOW_MO: "50"
- LIGHTNING_DEBUG: "1"
- steps:
- - script: echo '##vso[task.setvariable variable=local_id]$(System.PullRequest.PullRequestNumber)'
- displayName: "Set id for this PR"
- condition: eq(variables['Build.Reason'], 'PullRequest')
-
- - bash: |
- whoami
- mkdir -p "$(video_artifact_dir)/$(name)"
- printf "local id: $(local_id)\n"
- python --version
- pip --version
- echo "allow fail: ${{ in(variables['name'], 'quick_start', 'template_react_ui') }}"
- displayName: "Info"
-
- # TODO: we are testing it as `lightning`, so add also version for `lightning_app`
- - bash: |
- pip install -e .[app-dev] \
- -f https://download.pytorch.org/whl/cpu/torch_stable.html
- displayName: "Install Lightning & dependencies"
-
- - bash: python -m playwright install # --with-deps
- displayName: "Install Playwright system dependencies"
-
- # The magic happens here it doesn't need to install the quick start dependencies.
- # This test is very important to test the main user story of lightning app.
- # It also e2e tests running on cloud without installing dependencies.
- - bash: |
- git clone https://github.com/Lightning-AI/lightning-quick-start examples/app/quick-start
- # without succeeded this could run even if the job has already failed
- condition: and(succeeded(), eq(variables['name'], 'quick_start'))
- displayName: "Clone Quick start Repo"
- - bash: |
- git clone https://github.com/Lightning-AI/lightning-template-react examples/app/template_react_ui
- # without succeeded this could run even if the job has already failed
- condition: and(succeeded(), eq(variables['name'], 'template_react_ui'))
- displayName: "Clone Template React UI Repo"
-
- # Replace imports to use `lightning` instead of `lightning_app` since we install lightning only ATM
- - bash: |
- pip install -q -r .actions/requirements.txt
- python .actions/assistant.py copy_replace_imports \
- --source_dir="./examples" --source_import="lightning_app" --target_import="lightning.app"
- displayName: "Adjust examples"
-
- - bash: pip --version && pip list
- displayName: "List pip dependency"
-
- - bash: |
- ls -l examples/app/$(TEST_APP_NAME)
- echo ${TEST_FILE}
- python -m pytest ${TEST_FILE}::test_$(TEST_APP_NAME)_example_cloud \
- --timeout=360 --capture=no -v --color=yes
- env:
- TEST_FILE: tests/integrations_app/$(TEST_APP_FOLDER)/test_$(TEST_APP_NAME).py
- #LAI_USER: $(LAI_USER) # for STAGING
- #LAI_PASS: $(LAI_PASS) # for STAGING
- LIGHTNING_USER_ID: $(LIGHTNING_USER_ID_PROD)
- LIGHTNING_API_KEY: $(LIGHTNING_API_KEY_PROD)
- LIGHTNING_USERNAME: $(LIGHTNING_USERNAME_PROD)
- LIGHTNING_CLOUD_URL: $(LIGHTNING_CLOUD_URL_PROD)
- # Todo: investigate why these apps are failing
- continueOnError: ${{ in(variables['name'], 'quick_start', 'template_react_ui') }}
- displayName: "Run the tests"
-
- - task: PublishPipelineArtifact@1
- condition: failed()
- inputs:
- path: "$(video_artifact_dir)/$(name)"
- artifactName: $(name)
- publishLocation: "pipeline"
- displayName: "Publish videos"
-
- - bash: |
- time python -c "from lightning.app import testing; testing.delete_cloud_lightning_apps()"
- condition: always()
- continueOnError: "true"
- timeoutInMinutes: "3"
- env:
- #LAI_USER: $(LAI_USER) # for STAGING
- #LAI_PASS: $(LAI_PASS) # for STAGING
- LIGHTNING_USER_ID: $(LIGHTNING_USER_ID_PROD)
- LIGHTNING_API_KEY: $(LIGHTNING_API_KEY_PROD)
- LIGHTNING_USERNAME: $(LIGHTNING_USERNAME_PROD)
- LIGHTNING_CLOUD_URL: $(LIGHTNING_CLOUD_URL_PROD)
- displayName: "Clean Previous Apps"
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 821543cea4438..cdc2b63b2379d 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -11,7 +11,6 @@
/.actions/ @borda @ethanwharris @justusschock
/.github/ @borda @ethanwharris @justusschock
/.azure/ @borda @ethanwharris @justusschock
-/.azure/app-cloud-e2e.yml @awaelchli @ethanwharris @lantiga
/dockers/ @borda @ethanwharris @justusschock
*.yml @borda @ethanwharris @justusschock
@@ -25,7 +24,6 @@
/docs/source-pytorch/conf.py @borda @awaelchli
/docs/source-pytorch/index.rst @williamfalcon @lantiga
/docs/source-pytorch/levels @williamfalcon @lantiga
-/docs/source-app/ @williamfalcon @lantiga @tchaton
# PyTorch Lightning
/src/lightning/pytorch @lantiga @borda @tchaton @awaelchli @justusschock
@@ -36,18 +34,10 @@
# Lightning Fabric
/src/lightning/fabric @lantiga @borda @tchaton @awaelchli @justusschock
-# Lightning App
-/src/lightning/app @tchaton @lantiga @awaelchli @ethanwharris
-/src/lightning_app @tchaton @lantiga @awaelchli @ethanwharris
-/tests/tests_app @tchaton @lantiga @awaelchli @ethanwharris
-/tests/integrations_app @tchaton @lantiga @awaelchli @ethanwharris
-/examples/app_* @tchaton @lantiga @awaelchli @ethanwharris
-
/.github/CODEOWNERS @williamfalcon
/SECURITY.md @williamfalcon @lantiga
/README.md @williamfalcon @lantiga
/setup.py @williamfalcon @borda
/src/pytorch_lightning/__about__.py @williamfalcon @borda
-/src/lightning_app/__about__.py @williamfalcon @lantiga @borda
/src/lightning_fabric/__about__.py @williamfalcon @borda @awaelchli
/src/*/__setup__.py @borda @justusschock
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index db6543b8cb40e..775dc5dee77dc 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -310,20 +310,6 @@ and the last true master commit is `ccc111` and your first commit is `mmm222`.
git push -f
```
-#### How to run an app on the cloud with a local version of lightning
-
-The lightning cloud uses the latest release by default. However, you might want to run your app with some local changes you've made to the lightning framework. To use your local version of lightning on the cloud, set the following environment variable:
-
-```bash
-git clone https://github.com/Lightning-AI/lightning.git
-cd lightning
-pip install -e .
-export PACKAGE_LIGHTNING=1 # <- this is the magic to use your version (not mainstream PyPI)!
-lightning run app app.py --cloud
-```
-
-By setting `PACKAGE_LIGHTNING=1`, lightning packages the lightning source code in your local directory in addition to your app source code and uploads them to the cloud.
-
### Bonus Workflow Tip
If you don't want to remember all the commands above every time you want to push some code/setup a Lightning Dev environment on a new VM, you can set up bash aliases for some common commands. You can add these to one of your `~/.bashrc`, `~/.zshrc`, or `~/.bash_aliases` files.
diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.yaml b/.github/ISSUE_TEMPLATE/1_bug_report.yaml
index e6037c6d3bbb0..a3e2cfabe58f9 100644
--- a/.github/ISSUE_TEMPLATE/1_bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/1_bug_report.yaml
@@ -91,9 +91,7 @@ body:
Current environment
```
- #- Lightning Component (e.g. Trainer, LightningModule, LightningApp, LightningWork, LightningFlow):
#- PyTorch Lightning Version (e.g., 1.5.0):
- #- Lightning App Version (e.g., 0.5.2):
#- PyTorch Version (e.g., 2.0):
#- Python version (e.g., 3.9):
#- OS (e.g., Linux):
diff --git a/.github/actions/pkg-install/action.yml b/.github/actions/pkg-install/action.yml
index 96aab24a66a4c..e379f37aef68a 100644
--- a/.github/actions/pkg-install/action.yml
+++ b/.github/actions/pkg-install/action.yml
@@ -25,7 +25,7 @@ runs:
run: |
import os, glob
- lut = {'app': 'lightning_app', 'fabric': 'lightning_fabric', 'pytorch': 'pytorch_lightning'}
+ lut = {'fabric': 'lightning_fabric', 'pytorch': 'pytorch_lightning'}
act_pkg = lut.get('${{inputs.pkg-name}}', 'lightning')
pkg_sdist = glob.glob('*.tar.gz')[0]
pkg_wheel = glob.glob('*.whl')[0]
diff --git a/.github/actions/prep-apps/action.yml b/.github/actions/prep-apps/action.yml
deleted file mode 100644
index 2cd1655e2a36d..0000000000000
--- a/.github/actions/prep-apps/action.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-name: Adjust App environment
-description: make adjustment specific for selected App
-
-inputs:
- name:
- description: application name
- required: true
-
-runs:
- using: "composite"
- steps:
- - name: adjust env -> Flashy
- if: inputs.name == 'flashy'
- working-directory: tests/_flagship-app
- run: |
- ls -l .
- pip install -r requirements-dev.txt -f $TORCH_URL
- pip install -e . -f $TORCH_URL
- shell: bash
-
- - name: adjust env -> Muse
- if: inputs.name == 'muse'
- working-directory: tests/
- run: |
- pip install -e _flagship-app -f $TORCH_URL
- cp _flagship-app/tests/test_app.py \
- integrations_app/flagship/test_${{ inputs.name }}.py
- shell: bash
-
- - name: adjust env -> Jupyter
- if: inputs.name == 'jupyter'
- working-directory: tests/
- run: |
- pip install -e _flagship-app -f $TORCH_URL
- # pip install -r _flagship-app/tests/requirements-dev.txt
- cp _flagship-app/tests/test_jupyter_app.py \
- integrations_app/flagship/test_${{ inputs.name }}.py
- shell: bash
diff --git a/.github/checkgroup.yml b/.github/checkgroup.yml
index 79b65664d2eb8..3774a56e2f480 100644
--- a/.github/checkgroup.yml
+++ b/.github/checkgroup.yml
@@ -213,121 +213,6 @@ subprojects:
checks:
- "test-on-tpus (pytorch, pjrt, v4-8)"
- # SECTION: lightning_app
-
- - id: "lightning_app: Tests workflow"
- paths:
- - ".actions/*"
- - ".github/workflows/ci-tests-app.yml"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "tests/tests_app/**"
- - "requirements/app/**"
- - "setup.py"
- - "!requirements/*/docs.txt"
- - "!*.md"
- - "!**/*.md"
- checks:
- - "app-pytest (macOS-12, lightning, 3.8, latest)"
- - "app-pytest (macOS-12, lightning, 3.8, oldest)"
- - "app-pytest (macOS-12, app, 3.9, latest)"
- - "app-pytest (macOS-12, app, 3.11, latest)"
- - "app-pytest (ubuntu-20.04, lightning, 3.8, latest)"
- - "app-pytest (ubuntu-20.04, lightning, 3.8, oldest)"
- - "app-pytest (ubuntu-20.04, app, 3.9, latest)"
- - "app-pytest (ubuntu-22.04, app, 3.11, latest)"
- - "app-pytest (windows-2022, lightning, 3.8, latest)"
- - "app-pytest (windows-2022, lightning, 3.8, oldest)"
- - "app-pytest (windows-2022, app, 3.8, latest)"
- - "app-pytest (windows-2022, app, 3.11, latest)"
-
- - id: "lightning_app: Examples"
- paths:
- - ".actions/*"
- - ".github/workflows/ci-examples-app.yml"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "tests/integrations_app/**"
- - "!tests/integrations_app/flagship/**"
- - "examples/app/**"
- - "requirements/app/**"
- - "setup.py"
- - "!requirements/*/docs.txt"
- - "!*.md"
- - "!**/*.md"
- checks:
- - "app-examples (macOS-12, lightning, 3.9, latest)"
- - "app-examples (macOS-12, lightning, 3.9, oldest)"
- - "app-examples (macOS-12, app, 3.9, latest)"
- - "app-examples (ubuntu-20.04, lightning, 3.9, latest)"
- - "app-examples (ubuntu-20.04, lightning, 3.9, oldest)"
- - "app-examples (ubuntu-20.04, app, 3.9, latest)"
- - "app-examples (windows-2022, lightning, 3.9, latest)"
- - "app-examples (windows-2022, lightning, 3.9, oldest)"
- - "app-examples (windows-2022, app, 3.9, latest)"
-
- #- id: "lightning: Flagships"
- # paths:
- # - ".github/workflows/_flagship-apps.yml"
- # - ".github/workflows/ci-flagship-apps.yml"
- # - "github/actions/prep-apps/action.yml"
- # - "tests/integrations_app/flagship/**"
- # checks:
- # - "test-flagships / run-flagships (flashy, Lightning-Universe/Flashy-app)"
-
- - id: "lightning: Store"
- paths:
- - ".github/workflows/ci-tests-store.yml"
- - "src/lightning/__init__.py"
- - "src/lightning/__setup__.py"
- - "src/lightning/__version__.py"
- - "src/lightning/store/**"
- - "tests/tests_store/**"
- checks:
- - "store-cpu (macOS-14, lightning, 3.10, 2.0)"
- - "store-cpu (ubuntu-20.04, lightning, 3.10, 2.0)"
- - "store-cpu (windows-2022, lightning, 3.10, 2.0)"
-
- # FixMe: re-enable when BE stabilize
- # - id: "lightning_app: Azure"
- # paths:
- # - ".actions/*"
- # - ".azure/app-cloud-e2e.yml"
- # - "src/lightning/__about__.py"
- # - "src/lightning/__init__.py"
- # - "src/lightning/__main__.py"
- # - "src/lightning/__setup__.py"
- # - "src/lightning/__version__.py"
- # - "src/lightning/app/**"
- # - "src/lightning_app/*"
- # - "examples/app/**"
- # - "requirements/app/**"
- # - "tests/integrations_app/**"
- # - "!tests/integrations_app/flagship/**"
- # - "setup.py"
- # - "!requirements/*/docs.txt"
- # - "!*.md"
- # - "!**/*.md"
- # checks:
- # - "App.cloud-e2e"
-
- - id: "lightning_app: Docs"
- paths:
- - ".actions/*"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "docs/source-app/**"
- - ".github/workflows/docs-build.yml"
- - "requirements/docs.txt"
- - "requirements/app/**"
- - "setup.py"
- - "pyproject.toml" # includes metadata used in the package creation
- - "!*.md"
- - "!**/*.md"
- checks:
- - "docs-make (app, doctest)"
- - "docs-make (app, html)"
-
# SECTION: common
- id: "mypy"
@@ -359,8 +244,6 @@ subprojects:
- "!*.md"
- "!**/*.md"
checks:
- - "install-pkg (ubuntu-22.04, app, 3.8)"
- - "install-pkg (ubuntu-22.04, app, 3.11)"
- "install-pkg (ubuntu-22.04, fabric, 3.8)"
- "install-pkg (ubuntu-22.04, fabric, 3.11)"
- "install-pkg (ubuntu-22.04, pytorch, 3.8)"
@@ -369,8 +252,6 @@ subprojects:
- "install-pkg (ubuntu-22.04, lightning, 3.11)"
- "install-pkg (ubuntu-22.04, notset, 3.8)"
- "install-pkg (ubuntu-22.04, notset, 3.11)"
- - "install-pkg (macOS-12, app, 3.8)"
- - "install-pkg (macOS-12, app, 3.11)"
- "install-pkg (macOS-12, fabric, 3.8)"
- "install-pkg (macOS-12, fabric, 3.11)"
- "install-pkg (macOS-12, pytorch, 3.8)"
@@ -379,8 +260,6 @@ subprojects:
- "install-pkg (macOS-12, lightning, 3.11)"
- "install-pkg (macOS-12, notset, 3.8)"
- "install-pkg (macOS-12, notset, 3.11)"
- - "install-pkg (windows-2022, app, 3.8)"
- - "install-pkg (windows-2022, app, 3.11)"
- "install-pkg (windows-2022, fabric, 3.8)"
- "install-pkg (windows-2022, fabric, 3.11)"
- "install-pkg (windows-2022, pytorch, 3.8)"
diff --git a/.github/label-change.yml b/.github/label-change.yml
index 1f5b809a5fa60..8312c612b1207 100644
--- a/.github/label-change.yml
+++ b/.github/label-change.yml
@@ -1,28 +1,9 @@
-app:
- - changed-files:
- - any-glob-to-any-file:
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "tests/tests_app/**"
- - "tests/integrations_app/**"
- - "tests/integrations_app_examples/**"
- - "examples/app/**"
- - "docs/source-app/**"
- - "requirements/app/**"
-
data:
- changed-files:
- any-glob-to-any-file:
- "src/lightning/data/**"
- "requirements/data/**"
-store:
- - changed-files:
- - any-glob-to-any-file:
- - "src/lightning/store/**"
- - "tests/tests_store/**"
- - "requirements/store/**"
-
pl:
- changed-files:
- any-glob-to-any-file:
@@ -70,7 +51,6 @@ package:
- "src/version.info"
- "src/lightning/*/__setup__.py"
- "src/lightning/*/__version__.py"
- - "src/lightning_app/*"
- "src/lightning_fabric/*"
- "src/pytorch_lightning/*"
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 0c2c5a69c4b4c..58f4afe529509 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -8,15 +8,12 @@ Brief description of all our automation tools used for boosting development perf
| workflow file | action | accelerator |
| -------------------------------------- | ----------------------------------------------------------------------------------------- | ----------- |
-| .github/workflows/ci-tests-app.yml | Run all tests (may need internet connectivity). | CPU |
| .github/workflows/ci-tests-fabric.yml | Run all tests except for accelerator-specific and standalone. | CPU |
| .github/workflows/ci-tests-pytorch.yml | Run all tests except for accelerator-specific and standalone. | CPU |
| .github/workflows/ci-tests-data.yml | Run unit and integration tests with data pipelining. | CPU |
-| .github/workflows/ci-tests-store.yml | Run integration tests on uploading models to cloud. | CPU |
| .azure-pipelines/gpu-tests-fabric.yml | Run only GPU-specific tests, standalone\*, and examples. | GPU |
| .azure-pipelines/gpu-tests-pytorch.yml | Run only GPU-specific tests, standalone\*, and examples. | GPU |
| .azure-pipelines/gpu-benchmarks.yml | Run speed/memory benchmarks for parity with vanila PyTorch. | GPU |
-| .github/workflows/ci-examples-app.yml | Run integration tests with App examples. | CPU |
| .github/workflows/ci-flagship-apps.yml | Run end-2-end tests with full applications, including deployment to the production cloud. | CPU |
| .github/workflows/ci-tests-pytorch.yml | Run all tests except for accelerator-specific, standalone and slow tests. | CPU |
| .github/workflows/tpu-tests.yml | Run only TPU-specific tests. Requires that the PR title contains '\[TPU\]' | TPU |
diff --git a/.github/workflows/_build-packages.yml b/.github/workflows/_build-packages.yml
index cb58031805803..e0262ac63b685 100644
--- a/.github/workflows/_build-packages.yml
+++ b/.github/workflows/_build-packages.yml
@@ -12,7 +12,7 @@ on:
required: false
type: string
default: |
- ["lightning", "app", "fabric", "pytorch"]
+ ["lightning", "fabric", "pytorch"]
defaults:
run:
diff --git a/.github/workflows/_flagship-apps.yml b/.github/workflows/_flagship-apps.yml
deleted file mode 100644
index 3282c58ababb5..0000000000000
--- a/.github/workflows/_flagship-apps.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-name: Call integration of flagship Apps
-
-# see: https://help.github.com/en/actions/reference/events-that-trigger-workflows
-on:
- workflow_call:
- inputs:
- environment:
- description: "Lightning environment"
- required: false
- default: "PROD"
- type: string
- workflow_dispatch:
- inputs:
- environment:
- description: "Lightning environment"
- required: true
- default: "PROD"
- type: choice
- options:
- - PROD
- - STAGING
-
-defaults:
- run:
- shell: bash
-
-jobs:
- run-flagships:
- if: github.event.pull_request.draft == false
- runs-on: ubuntu-latest
- container:
- image: mcr.microsoft.com/playwright/python:v1.38.0-focal
- strategy:
- fail-fast: false
- matrix:
- include:
- - { app: "flashy", repo: "Lightning-Universe/Flashy-app" }
- - { app: "muse", repo: "Lightning-Universe/stable-diffusion-deploy" }
- - { app: "jupyter", repo: "Lightning-Universe/Jupyter-component" }
-
- # TODO:
- # - Training Studio
- # - Echo
- # - StreamLit / Gradio
- # - All homepage & docs apps
-
- env:
- HEADLESS: "1"
- PACKAGE_LIGHTNING: "1"
- CLOUD: "1"
- VIDEO_LOCATION: "./videos"
- HAR_LOCATION: "./artifacts/hars"
- SLOW_MO: "50"
- LIGHTNING_DEBUG: "1"
- TORCH_URL: "https://download.pytorch.org/whl/cpu/torch_stable.html"
- # Timeout: https://stackoverflow.com/a/59076067/4521646
- timeout-minutes: 20
-
- steps:
- - uses: actions/checkout@v4
-
- - name: basic setup
- timeout-minutes: 20
- run: |
- mkdir -p tests/_flagships
- mkdir -p $VIDEO_LOCATION
- pip --version
- pip list
- # for some reason the python package playwright is missing
- pip install -r requirements/app/test.txt
- python -m playwright install # --with-deps
-
- - name: Clone the Repo/App
- uses: actions/checkout@v4
- with:
- repository: ${{ matrix.repo }}
- path: tests/_flagship-app
-
- - name: Adjust env. for this App
- uses: ./.github/actions/prep-apps
- with:
- name: ${{ matrix.app }}
-
- - name: Install Lightning package
- timeout-minutes: 20
- run: pip install -e .[cloud,test] -f $TORCH_URL
- - name: List pip dependency
- run: pip --version && pip list
-
- - name: Run the tests
- working-directory: tests/
- env:
- LIGHTNING_USER_ID: ${{ secrets[format('LIGHTNING_USER_ID_{0}', inputs.environment)] }}
- LIGHTNING_API_KEY: ${{ secrets[format('LIGHTNING_API_KEY_{0}', inputs.environment)] }}
- LIGHTNING_USERNAME: ${{ secrets[format('LIGHTNING_USERNAME_{0}', inputs.environment)] }}
- LIGHTNING_CLOUD_URL: ${{ secrets[format('LIGHTNING_CLOUD_URL_{0}', inputs.environment)] }}
- LAI_USER: ${{ secrets.LAI_SSH_USER }}
- LAI_PASS: ${{ secrets.LAI_SSH_PASS }}
- run: |
- ls -l _flagship-app
- python -m pytest integrations_app/flagship/test_${{ matrix.app }}.py \
- --capture=no -v --color=yes
-
- - name: Upload recordings
- uses: actions/upload-artifact@v3
- if: failure()
- with:
- name: flahship-app-${{ matrix.app }}
- path: ${{ env.VIDEO_LOCATION }}
-
- - name: Clean Previous Apps
- if: always()
- timeout-minutes: 3
- env:
- LIGHTNING_USER_ID: ${{ secrets[format('LIGHTNING_USER_ID_{0}', inputs.environment)] }}
- LIGHTNING_API_KEY: ${{ secrets[format('LIGHTNING_API_KEY_{0}', inputs.environment)] }}
- LIGHTNING_USERNAME: ${{ secrets[format('LIGHTNING_USERNAME_{0}', inputs.environment)] }}
- LIGHTNING_CLOUD_URL: ${{ secrets[format('LIGHTNING_CLOUD_URL_{0}', inputs.environment)] }}
- LAI_USER: ${{ secrets.LAI_SSH_USER }}
- LAI_PASS: ${{ secrets.LAI_SSH_PASS }}
- run: |
- time python -c "from lightning.app import testing; testing.delete_cloud_lightning_apps()"
diff --git a/.github/workflows/ci-examples-app.yml b/.github/workflows/ci-examples-app.yml
deleted file mode 100644
index b6db69e67aead..0000000000000
--- a/.github/workflows/ci-examples-app.yml
+++ /dev/null
@@ -1,136 +0,0 @@
-name: Test App - examples
-
-# see: https://help.github.com/en/actions/reference/events-that-trigger-workflows
-on:
- push:
- branches: [master, "release/*"]
- pull_request:
- branches: [master, "release/*"]
- types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped
- paths:
- - ".actions/*"
- - ".github/workflows/ci-examples-app.yml"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "tests/integrations_app/**"
- - "!tests/integrations_app/flagship/**"
- - "examples/app/**"
- - "requirements/app/**"
- - "setup.py"
- - "!requirements/*/docs.txt"
- - "!*.md"
- - "!**/*.md"
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-defaults:
- run:
- shell: bash
-
-jobs:
- app-examples:
- if: github.event.pull_request.draft == false
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-20.04, macOS-12, windows-2022]
- pkg-name: ["lightning"]
- python-version: ["3.9"]
- requires: ["oldest", "latest"]
- include:
- # "app" installs the standalone package
- - { os: "macOS-12", pkg-name: "app", python-version: "3.9", requires: "latest" }
- - { os: "ubuntu-20.04", pkg-name: "app", python-version: "3.9", requires: "latest" }
- - { os: "windows-2022", pkg-name: "app", python-version: "3.9", requires: "latest" }
- # Timeout: https://stackoverflow.com/a/59076067/4521646
- timeout-minutes: 15
- env:
- PACKAGE_NAME: ${{ matrix.pkg-name }}
- FREEZE_REQUIREMENTS: 1
- TORCH_URL: "https://download.pytorch.org/whl/cpu/torch_stable.html"
- PYPI_CACHE_DIR: "_pip-wheels"
- steps:
- - uses: actions/checkout@v4
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: basic setup
- run: pip install -q -r .actions/requirements.txt
-
- - name: Set min. dependencies
- if: ${{ matrix.requires == 'oldest' }}
- run: python .actions/assistant.py replace_oldest_ver
-
- - name: pip wheels cache
- uses: actions/cache/restore@v4
- with:
- path: ${{ env.PYPI_CACHE_DIR }}
- key: pypi_wheels
-
- - name: Install Lightning package & dependencies
- timeout-minutes: 20
- run: |
- extra=$(python -c "print({'lightning': 'app-'}.get('${{ matrix.pkg-name }}', ''))")
- # do not use `-e` because it will make both packages available since it adds `src` to `sys.path` automatically
- pip install ".[${extra}dev]" -U -f ${TORCH_URL} -f ${PYPI_CACHE_DIR} --prefer-binary
- pip list
- - name: Dump handy wheels
- if: github.event_name == 'push' && github.ref == 'refs/heads/master'
- continue-on-error: true
- uses: ./.github/actions/pip-wheels
- with:
- wheel-dir: ${{ env.PYPI_CACHE_DIR }}
- torch-url: ${{ env.TORCH_URL }}
- cache-key: "pypi_wheels"
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "16"
-
- - name: Install Yarn
- timeout-minutes: 20
- run: npm install -g yarn
-
- - name: Adjust imports -> App
- if: ${{ matrix.pkg-name != 'lightning' }}
- run: |
- python .actions/assistant.py copy_replace_imports --source_dir="./tests" \
- --source_import="lightning.app,lightning.fabric,lightning.pytorch" \
- --target_import="lightning_app,lightning_fabric,pytorch_lightning" \
- --lightning_by="lightning_app"
- python .actions/assistant.py copy_replace_imports --source_dir="./examples" \
- --source_import="lightning.app,lightning.fabric,lightning.pytorch,lightning" \
- --target_import="lightning_app,lightning_fabric,pytorch_lightning,lightning_app"
-
- - name: Switch coverage scope
- run: python -c "print('COVERAGE_SCOPE=' + str('lightning' if '${{matrix.pkg-name}}' == 'lightning' else 'lightning_app'))" >> $GITHUB_ENV
-
- - name: Tests
- working-directory: ./tests
- run: |
- python -m coverage run --source ${{ env.COVERAGE_SCOPE }} \
- -m pytest -m "not cloud" integrations_app \
- --timeout=120 --durations=0 -vvvv
-
- - name: Statistics
- if: success()
- working-directory: ./tests
- run: |
- coverage xml -i
- coverage report -i
-
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- file: tests/coverage.xml
- flags: cpu,pytest,app,examples
- env_vars: OS,PYTHON
- name: codecov-umbrella
- fail_ci_if_error: false
diff --git a/.github/workflows/ci-flagship-apps.yml b/.github/workflows/ci-flagship-apps.yml
deleted file mode 100644
index 6332299fc03d1..0000000000000
--- a/.github/workflows/ci-flagship-apps.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Test App - flagships
-
-# see: https://help.github.com/en/actions/reference/events-that-trigger-workflows
-on:
- push:
- branches: ["release/*"]
- pull_request:
- branches: [master, "release/*"]
- types: [opened, reopened, ready_for_review, synchronize]
- paths:
- - ".github/workflows/_flagship-apps.yml"
- - ".github/workflows/ci-flagship-apps.yml"
- - "github/actions/prep-apps/action.yml"
- - "tests/integrations_app/flagship/**"
- schedule:
- # on Sundays
- - cron: "0 0 * * 0"
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-jobs:
- test-flagships:
- if: github.event.pull_request.draft == false
- uses: ./.github/workflows/_flagship-apps.yml
- secrets: inherit
diff --git a/.github/workflows/ci-pkg-install.yml b/.github/workflows/ci-pkg-install.yml
index 67a9b9f21b515..6e82167410ec3 100644
--- a/.github/workflows/ci-pkg-install.yml
+++ b/.github/workflows/ci-pkg-install.yml
@@ -43,14 +43,8 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-22.04", "macOS-12", "windows-2022"]
- pkg-name: ["app", "fabric", "pytorch", "lightning", "notset"]
+ pkg-name: ["fabric", "pytorch", "lightning", "notset"]
python-version: ["3.8", "3.11"]
- # TODO: add also install from source
- include:
- - { os: "macOS-12", pkg-name: "lightning", python-version: "3.9", pkg-extra: "app" }
- - { os: "macOS-12", pkg-name: "notset", python-version: "3.9", pkg-extra: "app" }
- - { os: "ubuntu-22.04", pkg-name: "lightning", python-version: "3.9", pkg-extra: "app" }
- - { os: "ubuntu-22.04", pkg-name: "notset", python-version: "3.9", pkg-extra: "app" }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@@ -70,16 +64,6 @@ jobs:
with:
pkg-folder: dist/${{ env.PKG_DIR }}
pkg-name: ${{ matrix.pkg-name }}
- pkg-extra: ${{ matrix.pkg-extra }}
-
- - name: Run CLI (via python)
- if: ${{ (matrix.pkg-name == 'lightning' || matrix.pkg-name == 'notset') && matrix.pkg-extra == 'app' }}
- run: python -m lightning --version
- - name: Run CLI (direct bash)
- if: |
- ((matrix.pkg-name == 'lightning' || matrix.pkg-name == 'notset') && matrix.pkg-extra == 'app') ||
- matrix.pkg-name == 'app'
- run: lightning_app --version
- name: DocTests actions
working-directory: .actions/
@@ -88,15 +72,15 @@ jobs:
python -m pytest assistant.py
- name: Adjust code for standalone
- if: contains(fromJSON('["app", "fabric", "pytorch"]'), matrix.pkg-name)
+ if: contains(fromJSON('["fabric", "pytorch"]'), matrix.pkg-name)
run: |
python .actions/assistant.py copy_replace_imports --source_dir="./src" \
- --source_import="lightning.pytorch,lightning.fabric,lightning.app" \
- --target_import="pytorch_lightning,lightning_fabric,lightning_app"
+ --source_import="lightning.pytorch,lightning.fabric" \
+ --target_import="pytorch_lightning,lightning_fabric"
- name: Rename src folders
working-directory: src/
run: |
- python -c "n = '${{matrix.pkg-name}}' ; n = n if n in ('app', 'fabric', 'pytorch') else '' ; print('PKG_NAME=' + n)" >> $GITHUB_ENV
+ python -c "n = '${{matrix.pkg-name}}' ; n = n if n in ('fabric', 'pytorch') else '' ; print('PKG_NAME=' + n)" >> $GITHUB_ENV
rm -f ./*/__*.py
rm -f ./**/__*.py
mv lightning lit # rename lightning folder to prevent accidental local imports
@@ -104,12 +88,8 @@ jobs:
if: ${{ matrix.pkg-name == 'lightning' || matrix.pkg-name == 'notset' }}
working-directory: src/lit
run: |
- items=("data" "store" "app")
+ items=("data")
for item in "${items[@]}"; do
- if [[ "$item" == "${{ matrix.pkg-extra }}" ]]; then
- echo "Skipping $item"
- continue # Skip this iteration
- fi
echo "Removing $item"
rm -rf $item
done
diff --git a/.github/workflows/ci-tests-app.yml b/.github/workflows/ci-tests-app.yml
deleted file mode 100644
index ee643fa397f43..0000000000000
--- a/.github/workflows/ci-tests-app.yml
+++ /dev/null
@@ -1,175 +0,0 @@
-name: Test App
-
-# see: https://help.github.com/en/actions/reference/events-that-trigger-workflows
-on:
- push:
- branches: [master, "release/*"]
- pull_request:
- branches: [master, "release/*"]
- types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped
- paths:
- - ".actions/*"
- - ".github/workflows/ci-tests-app.yml"
- - "requirements/ci.txt"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- - "tests/tests_app/**"
- - "requirements/app/**"
- - "setup.py"
- - "!requirements/*/docs.txt"
- - "!*.md"
- - "!**/*.md"
- schedule:
- # At the end of every day
- - cron: "0 0 * * *"
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-defaults:
- run:
- shell: bash
-
-jobs:
- app-pytest:
- if: github.event.pull_request.draft == false
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: ["ubuntu-20.04", "macOS-12", "windows-2022"]
- pkg-name: ["lightning"]
- python-version: ["3.8"]
- requires: ["oldest", "latest"]
- include:
- # only run Python latest, use App scope to limit dependency issues
- - { os: "macOS-12", pkg-name: "app", python-version: "3.11", requires: "latest" }
- - { os: "ubuntu-22.04", pkg-name: "app", python-version: "3.11", requires: "latest" }
- - { os: "windows-2022", pkg-name: "app", python-version: "3.11", requires: "latest" }
- # "app" installs the standalone package
- - { os: "macOS-12", pkg-name: "app", python-version: "3.9", requires: "latest" }
- - { os: "ubuntu-20.04", pkg-name: "app", python-version: "3.9", requires: "latest" }
- - { os: "windows-2022", pkg-name: "app", python-version: "3.8", requires: "latest" }
- # Timeout: https://stackoverflow.com/a/59076067/4521646
- timeout-minutes: 55
- env:
- PACKAGE_NAME: ${{ matrix.pkg-name }}
- FREEZE_REQUIREMENTS: ${{ ! (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release/')) }}
- PYPI_CACHE_DIR: "_pip-wheels"
- TORCH_URL: "https://download.pytorch.org/whl/cpu/torch_stable.html"
- steps:
- - uses: actions/checkout@v4
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: basic setup
- run: pip install -q -r .actions/requirements.txt
-
- - name: Set min. dependencies
- if: ${{ matrix.requires == 'oldest' }}
- run: python .actions/assistant.py replace_oldest_ver
-
- - name: pip wheels cache
- uses: actions/cache/restore@v4
- with:
- path: ${{ env.PYPI_CACHE_DIR }}
- key: pypi_wheels
- - name: List restored pkgs
- run: |
- mkdir -p $PYPI_CACHE_DIR
- ls -lh $PYPI_CACHE_DIR
-
- - name: Env. variables
- run: |
- # Switch coverage scope
- python -c "print('COVERAGE_SCOPE=' + str('lightning' if '${{matrix.pkg-name}}' == 'lightning' else 'pytorch_lightning'))" >> $GITHUB_ENV
- # if you install mono-package set dependency only for this subpackage
- python -c "print('EXTRA_PREFIX=' + str('' if '${{matrix.pkg-name}}' != 'lightning' else 'app-'))" >> $GITHUB_ENV
-
- - name: Install package & dependencies
- timeout-minutes: 20
- run: |
- pip install -e ".[${EXTRA_PREFIX}dev]" -U --prefer-binary \
- --find-links="${TORCH_URL}" --find-links="${PYPI_CACHE_DIR}"
- pip list
- - name: Dump handy wheels
- if: github.event_name == 'push' && github.ref == 'refs/heads/master'
- continue-on-error: true
- uses: ./.github/actions/pip-wheels
- with:
- wheel-dir: ${{ env.PYPI_CACHE_DIR }}
- torch-url: ${{ env.TORCH_URL }}
- cache-key: "pypi_wheels"
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "16"
- - name: Install Yarn
- timeout-minutes: 20
- run: npm install -g yarn
-
- - name: Adjust imports -> App
- if: ${{ matrix.pkg-name != 'lightning' }}
- run: |
- python .actions/assistant.py copy_replace_imports --source_dir="./tests" \
- --source_import="lightning.app,lightning.fabric,lightning.pytorch" \
- --target_import="lightning_app,lightning_fabric,pytorch_lightning" \
- --lightning_by="lightning_app"
- python .actions/assistant.py copy_replace_imports --source_dir="./examples" \
- --source_import="lightning.app,lightning.fabric,lightning.pytorch" \
- --target_import="lightning_app,lightning_fabric,pytorch_lightning" \
- --lightning_by="lightning_app"
-
- - name: Switch coverage scope
- run: python -c "print('COVERAGE_SCOPE=' + str('lightning' if '${{matrix.pkg-name}}' == 'lightning' else 'lightning_app'))" >> $GITHUB_ENV
-
- - name: Set parallel for Unix
- if: ${{ runner.os != 'windows' }}
- # on Win, tests takes even loner then with normal single thread
- run: echo "PYTEST_XDIST_ARGS=-n auto --dist=loadfile" >> $GITHUB_ENV
- - name: Tests
- working-directory: ./tests
- run: |
- set -e
- python -m coverage run --source ${{ env.COVERAGE_SCOPE }} \
- -m pytest -m "not cloud" -vvvv tests_app \
- --ignore="tests_app/components/python/test_python.py" \
- --timeout=120 --durations=50 ${PYTEST_XDIST_ARGS}
- pytest -m "not cloud" -v \
- tests_app/components/python/test_python.py \
- --timeout=120 --durations=50
-
- - name: Statistics
- if: success()
- working-directory: ./tests
- run: |
- coverage xml -i
- coverage report -i
-
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- file: tests/coverage.xml
- flags: ${{ env.COVERAGE_SCOPE }},cpu,pytest
- env_vars: OS,PYTHON
- name: codecov-umbrella
- fail_ci_if_error: false
-# TODO: figure out why we clone and install quick-start
-# - name: Clone Quick Start Example Repo
-# uses: actions/checkout@v4
-# # TODO: this needs to be git submodule
-# if: matrix.os == 'windows-2022' # because the install doesn't work on windows
-# with:
-# repository: Lightning-AI/lightning-quick-start
-# ref: 'main'
-# path: lightning-quick-start
-#
-# - name: Lightning Install quick-start
-# if: matrix.os != 'windows-2022' # because the install doesn't work on windows
-# run: |
-# python -m lightning install app lightning/quick-start -y
diff --git a/.github/workflows/ci-tests-store.yml b/.github/workflows/ci-tests-store.yml
deleted file mode 100644
index 60614005c614d..0000000000000
--- a/.github/workflows/ci-tests-store.yml
+++ /dev/null
@@ -1,96 +0,0 @@
-name: Test Store
-
-# see: https://help.github.com/en/actions/reference/events-that-trigger-workflows
-on:
- push:
- branches: [master, "release/*"]
- pull_request:
- branches: [master, "release/*"]
- types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped
- paths:
- - ".actions/*"
- - "requirements/ci.txt"
- - "requirements/store/**"
- - "src/lightning/__init__.py"
- - "src/lightning/__setup__.py"
- - "src/lightning/__version__.py"
- - "src/lightning/store/**"
- - "tests/tests_store/**"
- - "pyproject.toml" # includes pytest config
- - ".github/workflows/ci-tests-store.yml"
- - "!requirements/*/docs.txt"
- - "!*.md"
- - "!**/*.md"
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-defaults:
- run:
- shell: bash
-
-jobs:
- store-cpu:
- runs-on: ${{ matrix.os }}
- if: github.event.pull_request.draft == false
- strategy:
- fail-fast: false
- matrix:
- os: ["macOS-11", "ubuntu-20.04", "windows-2022"]
- pkg-name: ["lightning"]
- python-version: ["3.10"]
- pytorch-version: ["2.0"]
- timeout-minutes: 25 # because of building grpcio on Mac
- env:
- PACKAGE_NAME: ${{ matrix.pkg-name }}
- FREEZE_REQUIREMENTS: ${{ ! (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release/')) }}
- # PYPI_CACHE_DIR: "_pip-wheels"
- TORCH_URL_STABLE: "https://download.pytorch.org/whl/cpu/torch_stable.html"
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Adjust PyTorch versions in requirements files
- if: ${{ matrix.requires != 'oldest' && matrix.release != 'pre' }}
- run: |
- pip install -q -r requirements/ci.txt
- python -m wget https://raw.githubusercontent.com/Lightning-AI/utilities/main/scripts/adjust-torch-versions.py
- for fpath in `ls requirements/store/*.txt`; do \
- python ./adjust-torch-versions.py $fpath ${{ matrix.pytorch-version }}; \
- done
-
- - name: Install package & dependencies
- timeout-minutes: 20
- run: |
- pip install -e ".[store,store-test]" -U -f ${TORCH_URL} --prefer-binary
- pip list
-
- - name: Testing Store
- working-directory: tests/tests_store
- # NOTE: do not include coverage report here, see: https://github.com/nedbat/coveragepy/issues/1003
- run: |
- python -m coverage run --source lightning \
- -m pytest -v --timeout=60 --durations=60
-
- - name: Statistics
- if: success()
- working-directory: tests/tests_store
- run: |
- coverage report
- coverage xml
-
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v4
- # see: https://github.com/actions/toolkit/issues/399
- continue-on-error: true
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- file: tests/tests_store/coverage.xml
- flags: lightning,cpu,pytest,python${{ matrix.python-version }}
- name: CPU-coverage
- fail_ci_if_error: false
diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml
index 535274e403a58..55e2a8ec4a01a 100644
--- a/.github/workflows/code-checks.yml
+++ b/.github/workflows/code-checks.yml
@@ -44,7 +44,6 @@ jobs:
FREEZE_REQUIREMENTS: 1
timeout-minutes: 20
run: |
- # TODO: investigate hanging installation with app sub-package
pip install -e '.[pytorch-all,fabric-all]' -r requirements/typing.txt
pip list
diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml
index 7f7a09ce44007..4a9889eae02e6 100644
--- a/.github/workflows/docs-build.yml
+++ b/.github/workflows/docs-build.yml
@@ -15,8 +15,6 @@ on:
- "docs/**"
- "_notebooks"
- "requirements/**"
- - "src/lightning/app/**"
- - "src/lightning_app/*"
- "src/lightning/fabric/**"
- "src/lightning_fabric/*"
- "src/lightning/pytorch/**"
@@ -58,7 +56,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- pkg-name: ["app", "fabric", "pytorch"]
+ pkg-name: ["fabric", "pytorch"]
target: ["html", "doctest", "linkcheck"]
env:
DOCS_COPY_NOTEBOOKS: 1
@@ -144,7 +142,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- pkg-name: ["app", "fabric", "pytorch"]
+ pkg-name: ["fabric", "pytorch"]
env:
GCP_TARGET: "gs://lightning-docs-${{ matrix.pkg-name }}"
# use input if dispatch or git tag
diff --git a/.github/workflows/release-pkg.yml b/.github/workflows/release-pkg.yml
index 696efd8b68291..e922a34446d97 100644
--- a/.github/workflows/release-pkg.yml
+++ b/.github/workflows/release-pkg.yml
@@ -165,7 +165,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- name: ["APP", "FABRIC", "PYTORCH", "LIGHTNING"]
+ name: ["FABRIC", "PYTORCH", "LIGHTNING"]
steps:
- uses: actions/checkout@v4 # needed for local action below
- uses: actions/download-artifact@v3
@@ -190,7 +190,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- name: ["APP", "FABRIC", "PYTORCH", "LIGHTNING"]
+ name: ["FABRIC", "PYTORCH", "LIGHTNING"]
steps:
- uses: actions/checkout@v4 # needed for local action below
- uses: actions/download-artifact@v3
diff --git a/.gitignore b/.gitignore
index de1de44fec235..cf5face7db3d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,10 +9,6 @@ lightning_logs/
# Documentations
docs/venv*/
docs/build*/
-docs/source-app/*/api
-docs/source-app/generated
-docs/source-app/*/generated
-docs/source-app/_static/fetched-s3-assets
docs/source-fabric/_static/fetched-s3-assets
docs/source-pytorch/api
docs/source-pytorch/*.md
@@ -59,7 +55,6 @@ wheels/
.installed.cfg
*.egg
src/*/version.info
-src/lightning_app/*
src/lightning_fabric/*
src/pytorch_lightning/*
!src/*/__about__.py
@@ -182,8 +177,6 @@ cifar-10-batches-py
# ctags
tags
.tags
-src/lightning_app/ui/*
-src/lightning/app/ui/*
*examples/template_react_ui*
hars*
artifacts/*
diff --git a/.lightningignore b/.lightningignore
deleted file mode 100644
index 4ce8d526e30e3..0000000000000
--- a/.lightningignore
+++ /dev/null
@@ -1,16 +0,0 @@
-_notebooks
-.azure
-.github
-.ipynb_checkpoints
-.pytest_cache
-.shared
-.storage
-.venv
-.vscode
-.git
-artifacts
-Datasets
-dist
-docs
-examples
-tests
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 834a903bcac19..fbf4b2de6a999 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -90,7 +90,6 @@ repos:
- mdformat_frontmatter
exclude: |
(?x)^(
- src/lightning/app/CHANGELOG.md|
src/lightning/fabric/CHANGELOG.md|
src/lightning/pytorch/CHANGELOG.md|
README.md
diff --git a/.readthedocs.yml b/.readthedocs.yml
index a42fbb7e88214..625c56a5fe61b 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -42,8 +42,7 @@ build:
- pip install -U pip awscli py-tree --user
- python -m awscli s3 sync --no-sign-request s3://sphinx-packages/ dist/ ; ls -lh dist/
- >
- pip install -e ".[app]" -q -r _notebooks/.actions/requires.txt \
- -r requirements/app/docs.txt \
+ pip install -e . -q -r _notebooks/.actions/requires.txt \
-r requirements/fabric/docs.txt \
-r requirements/pytorch/docs.txt \
-f 'https://download.pytorch.org/whl/cpu/torch_stable.html' -f dist/ ;
diff --git a/Makefile b/Makefile
index 2f7ca37222a10..426c18042994c 100644
--- a/Makefile
+++ b/Makefile
@@ -21,13 +21,10 @@ clean:
rm -rf ./docs/source-pytorch/generated
rm -rf ./docs/source-pytorch/*/generated
rm -rf ./docs/source-pytorch/api
- rm -rf ./docs/source-app/generated
- rm -rf ./docs/source-app/*/generated
rm -rf build
rm -rf dist
rm -rf *.egg-info
rm -rf src/*.egg-info
- rm -rf src/lightning_app/*/
rm -rf src/lightning_fabric/*/
rm -rf src/pytorch_lightning/*/
@@ -35,14 +32,11 @@ test: clean
# Review the CONTRIBUTING documentation for other ways to test.
pip install -e . \
-r requirements/pytorch/base.txt \
- -r requirements/app/app.txt \
-r requirements/fabric/base.txt \
-r requirements/pytorch/test.txt \
- -r requirements/app/test.txt
# run tests with coverage
python -m coverage run --source src/lightning/pytorch -m pytest src/lightning/pytorch tests/tests_pytorch -v
- python -m coverage run --source src/lightning/app -m pytest tests/tests/app -v
python -m coverage run --source src/lightning/fabric -m pytest src/lightning/fabric tests/tests_fabric -v
python -m coverage report
@@ -54,10 +48,6 @@ sphinx-theme:
aws s3 sync --no-sign-request s3://sphinx-packages/ dist/
pip install lai-sphinx-theme -f dist/
-docs-app: clean sphinx-theme
- pip install -e .[all] --quiet -r requirements/app/docs.txt
- cd docs/source-app && $(MAKE) html --jobs $(nproc)
-
docs-fabric: clean sphinx-theme
pip install -e .[all] --quiet -r requirements/fabric/docs.txt
cd docs/source-fabric && $(MAKE) html --jobs $(nproc)
diff --git a/docs/crossroad.html b/docs/crossroad.html
index fc072aba60df5..0c25930c6343a 100644
--- a/docs/crossroad.html
+++ b/docs/crossroad.html
@@ -9,7 +9,6 @@
- App
Fabric
PyTorch
diff --git a/docs/rtfd-build.sh b/docs/rtfd-build.sh
index 2aa6928f10a19..1b1d5dcab319b 100644
--- a/docs/rtfd-build.sh
+++ b/docs/rtfd-build.sh
@@ -5,7 +5,7 @@ if ! [ $READTHEDOCS_VERSION == "latest" -o $READTHEDOCS_VERSION == "stable" ];
then
export FAST_DOCS_DEV=1 ;
root=$(pwd) ;
- for pkg in 'app' 'fabric' 'pytorch' ;
+ for pkg in 'fabric' 'pytorch' ;
do
cd $root/docs/source-$pkg ;
make html --jobs $(nproc) ;
diff --git a/docs/source-app/Makefile b/docs/source-app/Makefile
deleted file mode 100644
index 268e09561bb72..0000000000000
--- a/docs/source-app/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS = -T -W
-SPHINXBUILD = sphinx-build
-SOURCEDIR = .
-BUILDDIR = ../build
-
-# Put it first so that "make" without argument is like "make help".
-help:
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source-app/_static/copybutton.js b/docs/source-app/_static/copybutton.js
deleted file mode 100644
index aef241ab6ac40..0000000000000
--- a/docs/source-app/_static/copybutton.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Copied from the official Python docs: https://docs.python.org/3/_static/copybutton.js */
-$(document).ready(function () {
- /* Add a [>>>] button on the top-right corner of code samples to hide
- * the >>> and ... prompts and the output and thus make the code
- * copyable. */
- var div = $(
- ".highlight-python .highlight," +
- ".highlight-python3 .highlight," +
- ".highlight-pycon .highlight," +
- ".highlight-default .highlight",
- );
- var pre = div.find("pre");
-
- // get the styles from the current theme
- pre.parent().parent().css("position", "relative");
- var hide_text = "Hide the prompts and output";
- var show_text = "Show the prompts and output";
- var border_width = pre.css("border-top-width");
- var border_style = pre.css("border-top-style");
- var border_color = pre.css("border-top-color");
- var button_styles = {
- cursor: "pointer",
- position: "absolute",
- top: "0",
- right: "0",
- "border-color": border_color,
- "border-style": border_style,
- "border-width": border_width,
- color: border_color,
- "text-size": "75%",
- "font-family": "monospace",
- "padding-left": "0.2em",
- "padding-right": "0.2em",
- "border-radius": "0 3px 0 0",
- };
-
- // create and add the button to all the code blocks that contain >>>
- div.each(function (index) {
- var jthis = $(this);
- if (jthis.find(".gp").length > 0) {
- var button = $('>>> ');
- button.css(button_styles);
- button.attr("title", hide_text);
- button.data("hidden", "false");
- jthis.prepend(button);
- }
- // tracebacks (.gt) contain bare text elements that need to be
- // wrapped in a span to work with .nextUntil() (see later)
- jthis
- .find("pre:has(.gt)")
- .contents()
- .filter(function () {
- return this.nodeType == 3 && this.data.trim().length > 0;
- })
- .wrap("");
- });
-
- // define the behavior of the button when it's clicked
- $(".copybutton").click(function (e) {
- e.preventDefault();
- var button = $(this);
- if (button.data("hidden") === "false") {
- // hide the code output
- button.parent().find(".go, .gp, .gt").hide();
- button.next("pre").find(".gt").nextUntil(".gp, .go").css("visibility", "hidden");
- button.css("text-decoration", "line-through");
- button.attr("title", show_text);
- button.data("hidden", "true");
- } else {
- // show the code output
- button.parent().find(".go, .gp, .gt").show();
- button.next("pre").find(".gt").nextUntil(".gp, .go").css("visibility", "visible");
- button.css("text-decoration", "none");
- button.attr("title", hide_text);
- button.data("hidden", "false");
- }
- });
-});
diff --git a/docs/source-app/_static/images/icon.svg b/docs/source-app/_static/images/icon.svg
deleted file mode 100644
index e88fc19036178..0000000000000
--- a/docs/source-app/_static/images/icon.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/docs/source-app/_static/images/logo-large.svg b/docs/source-app/_static/images/logo-large.svg
deleted file mode 100644
index 39531f95e9dba..0000000000000
--- a/docs/source-app/_static/images/logo-large.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/docs/source-app/_static/images/logo-small.svg b/docs/source-app/_static/images/logo-small.svg
deleted file mode 100644
index 1f523a57c4a16..0000000000000
--- a/docs/source-app/_static/images/logo-small.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/docs/source-app/_static/images/logo.png b/docs/source-app/_static/images/logo.png
deleted file mode 100644
index 308a6ee419d44..0000000000000
Binary files a/docs/source-app/_static/images/logo.png and /dev/null differ
diff --git a/docs/source-app/_static/images/logo.svg b/docs/source-app/_static/images/logo.svg
deleted file mode 100644
index b73eaa8cedb50..0000000000000
--- a/docs/source-app/_static/images/logo.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/source-app/_static/main.css b/docs/source-app/_static/main.css
deleted file mode 100644
index c1bd8ad0305b7..0000000000000
--- a/docs/source-app/_static/main.css
+++ /dev/null
@@ -1,3 +0,0 @@
-col {
- width: 50% !important;
-}
diff --git a/docs/source-app/_templates/classtemplate.rst b/docs/source-app/_templates/classtemplate.rst
deleted file mode 100644
index 5b7f465516787..0000000000000
--- a/docs/source-app/_templates/classtemplate.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-.. role:: hidden
- :class: hidden-section
-.. currentmodule:: {{ module }}
-
-
-{{ name | underline }}
-
-.. autoclass:: {{ name }}
- :members:
diff --git a/docs/source-app/_templates/classtemplate_no_index.rst b/docs/source-app/_templates/classtemplate_no_index.rst
deleted file mode 100644
index 858c37b51567a..0000000000000
--- a/docs/source-app/_templates/classtemplate_no_index.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-:orphan:
-
-.. role:: hidden
- :class: hidden-section
-.. currentmodule:: {{ module }}
-
-
-{{ name | underline }}
-
-.. autoclass:: {{ name }}
- :members:
- :noindex:
diff --git a/docs/source-app/_templates/layout.html b/docs/source-app/_templates/layout.html
deleted file mode 100644
index 90e6125f9ad28..0000000000000
--- a/docs/source-app/_templates/layout.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "!layout.html" %}
-
-
-{% block footer %} {{ super() }}
-
-
-{% endblock %}
diff --git a/docs/source-app/_templates/theme_variables.jinja b/docs/source-app/_templates/theme_variables.jinja
deleted file mode 100644
index 914f8dcafc96b..0000000000000
--- a/docs/source-app/_templates/theme_variables.jinja
+++ /dev/null
@@ -1,18 +0,0 @@
-{%- set external_urls = {
- 'github': 'https://github.com/Lightning-AI/lightning',
- 'github_issues': 'https://github.com/Lightning-AI/lightning/issues',
- 'contributing': 'https://github.com/Lightning-AI/lightning/blob/master/.github/CONTRIBUTING.md',
- 'governance': 'https://github.com/Lightning-AI/lightning/blob/master/docs/source-pytorch/governance.rst',
- 'docs': 'https://lightning.rtfd.io/en/latest',
- 'twitter': 'https://twitter.com/PyTorchLightnin',
- 'discuss': 'https://discord.gg/VptPCZkGNa',
- 'tutorials': 'https://pt-lightning.readthedocs.io/en/latest/#tutorials',
- 'previous_pytorch_versions': 'https://pt-lightning.rtfd.io/en/latest/',
- 'home': 'https://lightning.ai/',
- 'get_started': 'https://pt-lightning.readthedocs.io/en/latest/introduction_guide.html',
- 'features': 'https://pt-lightning.rtfd.io/en/latest/',
- 'blog': 'https://www.pytorchlightning.ai/blog',
- 'resources': 'https://pt-lightning.readthedocs.io/en/latest/#community-examples',
- 'support': 'https://pt-lightning.rtfd.io/en/latest/',
-}
--%}
diff --git a/docs/source-app/api_reference/components.rst b/docs/source-app/api_reference/components.rst
deleted file mode 100644
index 69d53b79e76ce..0000000000000
--- a/docs/source-app/api_reference/components.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-########################
-lightning.app.components
-########################
-
-.. contents::
- :depth: 1
- :local:
- :backlinks: top
-
-.. currentmodule:: lightning.app.components
-
-
-Built-in Components
-___________________
-
-.. autosummary::
- :toctree: generated/
- :nosignatures:
- :template: classtemplate.rst
-
- ~database.client.DatabaseClient
- ~database.server.Database
- ~python.popen.PopenPythonScript
- ~python.tracer.TracerPythonScript
- ~training.LightningTrainerScript
- ~serve.gradio_server.ServeGradio
- ~serve.serve.ModelInferenceAPI
- ~serve.python_server.PythonServer
- ~serve.streamlit.ServeStreamlit
- ~multi_node.base.MultiNode
- ~multi_node.fabric.FabricMultiNode
- ~multi_node.pytorch_spawn.PyTorchSpawnMultiNode
- ~multi_node.trainer.LightningTrainerMultiNode
- ~serve.auto_scaler.AutoScaler
- ~serve.auto_scaler.ColdStartProxy
diff --git a/docs/source-app/api_reference/core.rst b/docs/source-app/api_reference/core.rst
deleted file mode 100644
index 324f3c448978f..0000000000000
--- a/docs/source-app/api_reference/core.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-:orphan:
-
-##################
-lightning.app.core
-##################
-
-.. contents::
- :depth: 1
- :local:
- :backlinks: top
-
-.. currentmodule:: lightning.app.core
-
-Core APIs
-___________________
-
-.. autosummary::
- :toctree: api/
- :nosignatures:
- :template: classtemplate.rst
-
- LightningApp
- LightningFlow
- LightningWork
-
-Learn more about :ref:`Lightning Core `.
diff --git a/docs/source-app/api_reference/frontend.rst b/docs/source-app/api_reference/frontend.rst
deleted file mode 100644
index 514b2cf35bc75..0000000000000
--- a/docs/source-app/api_reference/frontend.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-######################
-lightning.app.frontend
-######################
-
-.. contents::
- :depth: 1
- :local:
- :backlinks: top
-
-.. currentmodule:: lightning.app.frontend
-
-Lightning FrontEnds
-___________________
-
-.. autosummary::
- :toctree: generated/
- :nosignatures:
- :template: classtemplate.rst
-
- ~frontend.Frontend
- ~web.StaticWebFrontend
- ~stream_lit.StreamlitFrontend
- ~panel.PanelFrontend
-
-Learn more about :ref:`Frontend's `.
diff --git a/docs/source-app/api_reference/runners.rst b/docs/source-app/api_reference/runners.rst
deleted file mode 100644
index f7e550b7c7733..0000000000000
--- a/docs/source-app/api_reference/runners.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-#####################
-lightning.app.runners
-#####################
-
-.. contents::
- :depth: 1
- :local:
- :backlinks: top
-
-.. currentmodule:: lightning.app.runners
-
-Lightning Core
-______________
-
-.. autosummary::
- :toctree: generated/
- :nosignatures:
- :template: classtemplate.rst
-
- ~cloud.CloudRuntime
- ~multiprocess.MultiProcessRuntime
diff --git a/docs/source-app/api_reference/storage.rst b/docs/source-app/api_reference/storage.rst
deleted file mode 100644
index 3173914427586..0000000000000
--- a/docs/source-app/api_reference/storage.rst
+++ /dev/null
@@ -1,71 +0,0 @@
-#####################
-lightning.app.storage
-#####################
-
-Lightning Core
-______________
-
-.. contents::
- :depth: 1
- :local:
- :backlinks: top
-
-.. currentmodule:: lightning.app.storage
-
-.. autosummary::
- :toctree: generated/
- :nosignatures:
- :template: classtemplate.rst
-
- ~path.Path
- ~drive.Drive
- ~payload.Payload
- ~mount.Mount
-
-----
-
-************************
-Learn more about Storage
-************************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Learn about the differences between Drive vs Path.
- :description: Learn about their differences.
- :col_css: col-md-4
- :button_link: ../glossary/storage/differences.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: The Drive Object.
- :description: Put, List and Get Files From a Shared Drive Disk.
- :col_css: col-md-4
- :button_link: ../glossary/storage/drive.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: The Path Object.
- :description: Transfer Files From One Component to Another by Reference.
- :col_css: col-md-4
- :button_link: ../glossary/storage/path.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: The Mount Object.
- :description: Mount an AWS S3 Bucket When Running on the Cloud.
- :col_css: col-md-4
- :button_link: ../workflows/mount_aws_s3_bucket.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/basics.rst b/docs/source-app/basics.rst
deleted file mode 100644
index 69b1bf751014b..0000000000000
--- a/docs/source-app/basics.rst
+++ /dev/null
@@ -1,259 +0,0 @@
-:orphan:
-
-.. _basics:
-
-######
-Basics
-######
-
-In this guide, we'll cover the basic terminology associated with the Lightning framework.
-
-----
-
-**************
-Lightning App
-**************
-
-The :class:`~lightning.app.core.app.LightningApp` runs a tree of one or more components that interact to create end-to-end applications. There are two kinds of components: :class:`~lightning.app.core.flow.LightningFlow` and :class:`~lightning.app.core.work.LightningWork`. This modular design enables you to reuse components created by other users.
-
-----
-
-Lightning Work
-^^^^^^^^^^^^^^
-
-The :class:`~lightning.app.core.work.LightningWork` component is a building block optimized for long-running jobs or integrating third-party services. LightningWork can be used for training large models, downloading a dataset, or any long-lasting operation.
-
-----
-
-Lightning Flow
-^^^^^^^^^^^^^^
-
-The :class:`~lightning.app.core.flow.LightningFlow` component coordinates long-running tasks :class:`~lightning.app.core.work.LightningWork` and runs its children :class:`~lightning.app.core.flow.LightningFlow` components.
-
-----
-
-Lightning App Tree
-^^^^^^^^^^^^^^^^^^
-
-Components can be nested to form component trees where the LightningFlows are its branches and LightningWorks are its leaves.
-
-Here's a basic application with four flows and two works:
-
-.. literalinclude:: code_samples/quickstart/app_comp.py
-
-And here's its associated tree structure:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/tree.gif
- :alt: Basic App Components
- :width: 100 %
-
-A Lightning App runs all flows into a single process. Its flows coordinate the execution of the works each running in their own independent processes.
-
-----
-
-Lightning Distributed Event Loop
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Drawing inspiration from modern web frameworks like `React.js `_, the Lightning app runs all flows in an **event loop** (forever), which is triggered every 0.1 seconds after collecting any works' state change.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_loop.gif
-
-When running an app in the cloud, the :class:`~lightning.app.core.work.LightningWork` run on different machines. Lightning communicates any :class:`~lightning.app.core.work.LightningWork` state changes to the **event loop** which re-executes the flow with the newly-collected works' state.
-
-----
-
-Lightning App State
-^^^^^^^^^^^^^^^^^^^
-
-By design, each component is stateful and its state is composed of all its attributes. The **Lightning App State** is the collection of all its components state.
-
-With this mechanism, any component can **react** to any other component **state changes**, simply by relying on its attributes within the flow.
-
-For example, here we define two flow components, **RootFlow** and **ChildFlow**, where the child flow prints and increments a counter indefinitely and gets reflected in **RootFlow** state.
-
-You can easily check the state of your entire app:
-
-.. literalinclude:: code_samples/quickstart/app_01.py
-
-Here's the entire tree structure associated with your app:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/parent_child.png
- :alt: Parent Child Components
- :width: 100 %
-
-And here's the output you get when running the above application using **Lightning CLI**:
-
-.. code-block:: console
-
- $ lightning_app run app docs/source/code_samples/quickstart/app_01.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- State: {'works': {'w_1': {'vars': {'counter': 1}}, 'w_2': {'vars': {'counter': 0}}}}
-
- State: {'works': {'w_1': {'vars': {'counter': 3}}, 'w_2': {'vars': {'counter': 1}}}}
-
- State: {'works': {'w_1': {'vars': {'counter': 4}}, 'w_2': {'vars': {'counter': 1}}}}
-
- State: {'works': {'w_1': {'vars': {'counter': 5}}, 'w_2': {'vars': {'counter': 2}}}}
-
- State: {'works': {'w_1': {'vars': {'counter': 6}}, 'w_2': {'vars': {'counter': 2}}}}
-
- State: {'works': {'w_1': {'vars': {'counter': 7}}, 'w_2': {'vars': {'counter': 3}}}}
- ...
-
-This app will count forever because the **lightning event loop** indefinitely calls the root flow run method.
-
-----
-
-*******************************
-Controlling the Execution Flow
-*******************************
-
-
-LightningWork: To Cache or Not to Cache Calls
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-With Lightning, you can control how to run your components.
-
-By default, the :class:`~lightning.app.core.flow.LightningFlow` is executed infinitely by the **Lightning Infinite Loop** and the :class:`~lightning.app.core.work.LightningWork` does not run in **parallel**,
-meaning the **Lightning Infinite Loop** (a.k.a the flow) waits until that long-running work is completed to continue.
-
-Similar to `React.js Components and Props `_, the :class:`~lightning.app.core.work.LightningWork`
-component accepts arbitrary inputs (the "props") to its **run** method and by default runs **once** for each unique input provided.
-
-Here's an example of this behavior:
-
-.. literalinclude:: code_samples/basics/0.py
- :language: python
- :emphasize-lines: 10, 19
-
-And you should see the following by running the code above:
-
-.. code-block:: console
-
- $ python example.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- # After you have clicked `run` on the UI.
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 10}
-
-As you can see, the intermediate run didn't execute as already cached.
-
-To disable this behavior, set ``cache_calls=False`` to make any LightningWork run infinitely.
-
-.. literalinclude:: code_samples/basics/1.py
- :diff: code_samples/basics/0.py
-
-.. code-block:: console
-
- $ python example.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- # After you have clicked `run` on the UI.
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 10}
-
-
-.. note:: Passing a sequence of different props to the work run method queues their execution. We recommend avoiding this behavior as it can be hard to debug. Instead, wait for the previous run to execute.
-
-----
-
-LightningWork: Parallel vs Non Parallel
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The LightningWork component is made for long-running jobs.
-
-As an example, let's create a long-running **LightningWork** component that will take 1 hour to do its "work".
-
-.. literalinclude:: code_samples/quickstart/app_02.py
- :language: python
- :emphasize-lines: 15
-
-Here's the output you get when running the above application using **Lightning CLI**:
-
-.. code-block:: console
-
- $ lightning_app run app docs/source/code_samples/quickstart/app_02.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- # After you have clicked `run` on the UI.
- 0.0 0.0
- ...
- 0.0003 0.0003
- ...
- 1.0 1.0
- ...
- 1 hour later!
- 1.0 1.0
- 1 hour later!
- 1.0 1.0
- 1 hour later!
- ...
-
-The child work runs only once, hence why the progress counter stops increasing once the work is completed.
-
-This is useful for monitoring the progress of a long-running operation, like training a big model.
-
-.. note ::
- The Lightning Infinite Loop runs multiple cycles per second.
- It is good practice to keep the loop running fast, so that your application stays responsive,
- especially when it contains user-interface components.
-
-----
-
-****************
-Multiple works
-****************
-
-In practical use cases, you might want to execute multiple long-running works in parallel.
-
-To enable this behavior, set ``parallel=True`` in the ``__init__`` method of
-your :class:`~lightning.app.core.work.LightningWork`.
-
-Here's an example of the interaction between parallel and non-parallel behaviors:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/blocking_non_blocking.gif
- :alt: mnist GPU bar
- :width: 100 %
-
-Below, we reuse the **HourLongWork** work defined in the previous example, but modify the **RootFlow**
-to run two **HourLongWork** works in a parallel way.
-
-.. literalinclude:: code_samples/quickstart/app/app_0.py
- :emphasize-lines: 21
-
-Above, both ``child_work_1`` and ``child_work_2`` are long-running works that are executed
-asynchronously in parallel.
-
-When running the above app, we see the following logs:
-
-.. code-block:: console
-
- $ lightning_app run app docs/source/code_samples/quickstart/app/app_0.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- # After you have clicked `run` on the UI.
- 0.0, 0.0
- ...
- 0.0003, 0.0003
- ...
- 1.0, 1.0
- ...
- 1 hour later `child_work_1` started!
- 1 hour later `child_work_2` started!
- 0.0, 0.0
- ...
- 0.0003, 0.0003
- ...
- 1.0, 1.0
- 1 hour later `child_work_1` started!
- 1 hour later `child_work_2` started!
- ...
-
-----
-
-***********
-Next Steps
-***********
-
-To keep learning about Lightning, build a :ref:`ui_and_frontends`.
diff --git a/docs/source-app/code_samples/basics/0.py b/docs/source-app/code_samples/basics/0.py
deleted file mode 100644
index 7f4658e3977af..0000000000000
--- a/docs/source-app/code_samples/basics/0.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from lightning.app import LightningWork
-
-
-class ExampleWork(LightningWork):
- def run(self, *args, **kwargs):
- print(f"I received the following props: args: {args} kwargs: {kwargs}")
-
-
-work = ExampleWork()
-work.run(value=1)
-
-# Providing the same value. This won't run as already cached.
-work.run(value=1)
-work.run(value=1)
-work.run(value=1)
-work.run(value=1)
-
-# Changing the provided value. This isn't cached and will run again.
-work.run(value=10)
diff --git a/docs/source-app/code_samples/basics/1.py b/docs/source-app/code_samples/basics/1.py
deleted file mode 100644
index 1a696b8e4c45c..0000000000000
--- a/docs/source-app/code_samples/basics/1.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from lightning.app import LightningWork
-
-
-class ExampleWork(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
-
- def run(self, *args, **kwargs):
- print(f"I received the following props: args: {args} kwargs: {kwargs}")
-
-
-work = ExampleWork()
-work.run(value=1)
-
-# Providing the same value. This won't run as already cached.
-work.run(value=1)
-work.run(value=1)
-work.run(value=1)
-work.run(value=1)
-
-# Changing the provided value. This isn't cached and will run again.
-work.run(value=10)
diff --git a/docs/source-app/code_samples/convert_pl_to_app/app.py b/docs/source-app/code_samples/convert_pl_to_app/app.py
deleted file mode 100644
index a590cbaab8ea0..0000000000000
--- a/docs/source-app/code_samples/convert_pl_to_app/app.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from lightning.app import LightningFlow, LightningApp, CloudCompute
-from lightning.app.components import TracerPythonScript
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.runner = TracerPythonScript(
- "train.py",
- cloud_compute=CloudCompute("gpu"),
- )
-
- def run(self):
- self.runner.run()
-
-
-app = LightningApp(RootFlow())
diff --git a/docs/source-app/code_samples/convert_pl_to_app/requirements.txt b/docs/source-app/code_samples/convert_pl_to_app/requirements.txt
deleted file mode 100644
index e8fb43ef7dc83..0000000000000
--- a/docs/source-app/code_samples/convert_pl_to_app/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-torch
-torchvision
-pytorch_lightning
diff --git a/docs/source-app/code_samples/convert_pl_to_app/train.py b/docs/source-app/code_samples/convert_pl_to_app/train.py
deleted file mode 100644
index d0b7919b75843..0000000000000
--- a/docs/source-app/code_samples/convert_pl_to_app/train.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import os
-
-import torch
-import torch.nn.functional as F
-from torch import nn
-from torch.utils.data import DataLoader, random_split
-from torchvision import transforms as T
-from torchvision.datasets import MNIST
-
-import lightning.pytorch as pl
-
-
-class LitAutoEncoder(pl.LightningModule):
- def __init__(self):
- super().__init__()
- self.encoder = nn.Sequential(nn.Linear(28 * 28, 128), nn.ReLU(), nn.Linear(128, 3))
- self.decoder = nn.Sequential(nn.Linear(3, 128), nn.ReLU(), nn.Linear(128, 28 * 28))
-
- def forward(self, x):
- # in lightning,
- # forward defines the prediction/inference actions
- embedding = self.encoder(x)
- return embedding
-
- def training_step(self, batch, batch_idx):
- # training_step defines the train loop.
- # It is independent of forward
- x, _ = batch
- x = x.view(x.size(0), -1)
- z = self.encoder(x)
- x_hat = self.decoder(z)
- loss = F.mse_loss(x_hat, x)
- self.log("train_loss", loss)
- return loss
-
- def configure_optimizers(self):
- optimizer = torch.optim.Adam(self.parameters(), lr=1e-3)
- return optimizer
-
-
-dataset = MNIST(os.getcwd(), download=True, transform=T.ToTensor())
-train, val = random_split(dataset, [55000, 5000])
-
-autoencoder = LitAutoEncoder()
-trainer = pl.Trainer(accelerator="auto")
-trainer.fit(autoencoder, DataLoader(train), DataLoader(val))
diff --git a/docs/source-app/code_samples/quickstart/__init__.py b/docs/source-app/code_samples/quickstart/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/docs/source-app/code_samples/quickstart/app/__init__.py b/docs/source-app/code_samples/quickstart/app/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/docs/source-app/code_samples/quickstart/app/app_0.py b/docs/source-app/code_samples/quickstart/app/app_0.py
deleted file mode 100644
index 370f6818a778f..0000000000000
--- a/docs/source-app/code_samples/quickstart/app/app_0.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from docs.quickstart.app_02 import HourLongWork
-
-
-class RootFlow(LightningFlow):
- def __init__(self, child_work_1: LightningWork, child_work_2: LightningWork):
- super().__init__()
- self.child_work_1 = child_work_1
- self.child_work_2 = child_work_2
-
- def run(self):
- print(round(self.child_work_1.progress, 4), round(self.child_work_2.progress, 4))
- self.child_work_1.run()
- self.child_work_2.run()
- if self.child_work_1.progress == 1.0:
- print("1 hour later `child_work_1` started!")
- if self.child_work_2.progress == 1.0:
- print("1 hour later `child_work_2` started!")
-
-
-app = LightningApp(RootFlow(HourLongWork(parallel=True), HourLongWork(parallel=True)))
diff --git a/docs/source-app/code_samples/quickstart/app/app_1.py b/docs/source-app/code_samples/quickstart/app/app_1.py
deleted file mode 100644
index e6b876e452f2b..0000000000000
--- a/docs/source-app/code_samples/quickstart/app/app_1.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import flash
-from flash.core.data.utils import download_data
-from flash.image import ImageClassificationData, ImageClassifier
-
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-from lightning.pytorch.callbacks import ModelCheckpoint
-
-
-# Step 1: Create a training LightningWork component that gets a backbone as input
-# and saves the best model and its score
-class ImageClassifierTrainWork(LightningWork):
- def __init__(self, max_epochs: int, backbone: str, cloud_compute: CloudCompute):
- # parallel is set to True to run asynchronously
- super().__init__(parallel=True, cloud_compute=cloud_compute)
- # Number of epochs to run
- self.max_epochs = max_epochs
- # The model backbone to train on
- self.backbone = backbone
- self.best_model_path = None
- self.best_model_score = None
-
- def run(self, train_folder):
- # Create a datamodule from the given dataset
- datamodule = ImageClassificationData.from_folders(
- train_folder=train_folder,
- batch_size=1,
- val_split=0.5,
- )
- # Create an image classfier task with the given backbone
- model = ImageClassifier(datamodule.num_classes, backbone=self.backbone)
- # Start a Lightning trainer, with 1 training batch and 4 validation batches
- trainer = flash.Trainer(
- max_epochs=self.max_epochs,
- limit_train_batches=1,
- limit_val_batches=4,
- callbacks=[ModelCheckpoint(monitor="val_cross_entropy")],
- )
- # Train the model
- trainer.fit(model, datamodule=datamodule)
- # Save the model path
- self.best_model_path = trainer.checkpoint_callback.best_model_path
- # Save the model score
- self.best_model_score = trainer.checkpoint_callback.best_model_score.item()
-
-
-# Step 2: Create a serving LightningWork component that gets a model input and serves it
-class ImageClassifierServeWork(LightningWork):
- def run(self, best_model_path: str):
- # Load the model from the model path
- model = ImageClassifier.load_from_checkpoint(best_model_path)
- model.serve(output="labels")
-
-
-# Step 3: Create a root LightningFlow component that gets number of epochs and a path to
-# a dataset as inputs, initialize 2 training components and serves the best model
-class RootFlow(LightningFlow):
- def __init__(self, max_epochs: int, data_dir: str):
- super().__init__()
- self.data_dir = data_dir
- # Init an image classifier with resnet18 backbone
- self.train_work_1 = ImageClassifierTrainWork(
- max_epochs,
- "resnet18",
- )
- # Init an image classifier with resnet26 backbone
- self.train_work_2 = ImageClassifierTrainWork(
- max_epochs,
- "resnet26",
- )
- # Init the serving component
- self.server_work = ImageClassifierServeWork()
-
- def run(self):
- # running both `train_work_1` and `train_work_2` in parallel and asynchronously.
- self.train_work_1.run(self.data_dir)
- self.train_work_2.run(self.data_dir)
-
- # run serve_work only when both `best_model_score` are available.
- if self.train_work_1.best_model_score and self.train_work_2.best_model_score:
- # serve only the best model between `train_work_1` and `train_work_2`.
- self.server_work.run(
- self.train_work_1.best_model_path
- if self.train_work_1.best_model_score < self.train_work_2.best_model_score
- else self.train_work_2.best_model_path
- )
-
-
-# Step 4: download a dataset to your local directory under `/data`
-download_data("https://pl-flash-data.s3.amazonaws.com/hymenoptera_data.zip", "./data")
-
-# Initialize your Lightning app with 5 epochs
-app = LightningApp(RootFlow(5, "./data/hymenoptera_data"))
diff --git a/docs/source-app/code_samples/quickstart/app_01.py b/docs/source-app/code_samples/quickstart/app_01.py
deleted file mode 100644
index 42f716f99ffb2..0000000000000
--- a/docs/source-app/code_samples/quickstart/app_01.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.utilities.app_helpers import pretty_state
-
-
-class Work(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
- # Attributes are registered automatically in the state.
- self.counter = 0
-
- def run(self):
- # Incrementing an attribute gets reflected in the `Flow` state.
- self.counter += 1
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = Work()
-
- def run(self):
- if self.w.has_started:
- print(f"State: {pretty_state(self.state)} \n")
- self.w.run()
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/code_samples/quickstart/app_02.py b/docs/source-app/code_samples/quickstart/app_02.py
deleted file mode 100644
index c5c6445a0e32e..0000000000000
--- a/docs/source-app/code_samples/quickstart/app_02.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from time import sleep
-
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-
-# This work takes an hour to run
-class HourLongWork(LightningWork):
- def __init__(self, parallel: bool = False):
- super().__init__(parallel=parallel)
- self.progress = 0.0
-
- def run(self):
- self.progress = 0.0
- for _ in range(3600):
- self.progress += 1.0 / 3600 # Reporting my progress to the Flow.
- sleep(1)
-
-
-class RootFlow(LightningFlow):
- def __init__(self, child_work: LightningWork):
- super().__init__()
- self.child_work = child_work
-
- def run(self):
- # prints the progress from the child work
- print(round(self.child_work.progress, 4))
- self.child_work.run()
- if self.child_work.counter == 1.0:
- print("1 hour later!")
-
-
-app = LightningApp(RootFlow(HourLongWork()))
diff --git a/docs/source-app/code_samples/quickstart/app_03.py b/docs/source-app/code_samples/quickstart/app_03.py
deleted file mode 100644
index 6fdbc3ac67083..0000000000000
--- a/docs/source-app/code_samples/quickstart/app_03.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from time import sleep
-
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-
-class HourLongWork(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
- self.progress = 0.0
-
- def run(self):
- self.progress = 0.0
- for _ in range(3600):
- self.progress += 1.0 / 3600
- sleep(1)
-
-
-class RootFlow(LightningFlow):
- def __init__(self, child_work: LightningWork):
- super().__init__()
- self.child_work = child_work
-
- def run(self):
- # prints the progress from the child work
- print(round(self.child_work.progress, 4))
- self.child_work.run()
- if self.child_work.counter == 1.0:
- print("1 hour later!")
-
-
-app = LightningApp(RootFlow(HourLongWork()))
diff --git a/docs/source-app/code_samples/quickstart/app_comp.py b/docs/source-app/code_samples/quickstart/app_comp.py
deleted file mode 100644
index 9aee70009f478..0000000000000
--- a/docs/source-app/code_samples/quickstart/app_comp.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from lightning.app import LightningFlow, LightningApp
-from lightning.app.testing import EmptyFlow, EmptyWork
-
-
-class FlowB(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flow_d = EmptyFlow()
- self.work_b = EmptyWork()
-
- def run(self):
- ...
-
-
-class FlowA(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flow_b = FlowB()
- self.flow_c = EmptyFlow()
- self.work_a = EmptyWork()
-
- def run(self):
- ...
-
-
-app = LightningApp(FlowA())
diff --git a/docs/source-app/code_samples/quickstart/hello_world/app.py b/docs/source-app/code_samples/quickstart/hello_world/app.py
deleted file mode 100644
index 18dd2d78a0c6f..0000000000000
--- a/docs/source-app/code_samples/quickstart/hello_world/app.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from lightning.app import LightningFlow, LightningApp
-
-
-# Step 1: Subclass LightningFlow component to define the app flow.
-class HelloWorld(LightningFlow):
- # Step 2: Add the app logic to the LightningFlow run method to
- # ``print("Hello World!")`.
- # The LightningApp executes the run method of the main LightningFlow
- # within an infinite loop.
- def run(self):
- print("Hello World!")
-
-
-# Step 3: Initialize a LightningApp with the LightningFlow you defined (in step 1)
-app = LightningApp(HelloWorld())
diff --git a/docs/source-app/code_samples/quickstart/hello_world/app_ui.py b/docs/source-app/code_samples/quickstart/hello_world/app_ui.py
deleted file mode 100644
index ad0e5065f5bb5..0000000000000
--- a/docs/source-app/code_samples/quickstart/hello_world/app_ui.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import os
-
-from lightning.app import LightningFlow, LightningApp
-from lightning.app.frontend import StaticWebFrontend, StreamlitFrontend
-from lightning.app.utilities.state import AppState
-
-
-# Step 1: Define your LightningFlow component with the app UI
-class UIStreamLit(LightningFlow):
- def __init__(self):
- super().__init__()
- self.should_print = False
-
- # Step 2: Override `configure_layout` to define the layout of the UI
- # In this example, we are using `StreamlitFrontend`
- def configure_layout(self):
- return StreamlitFrontend(render_fn=render_fn)
-
-
-# Step 3: Implement the StreamLit render method
-def render_fn(state: AppState):
- import streamlit as st
- from streamlit_autorefresh import st_autorefresh
-
- st_autorefresh(interval=2000, limit=None, key="refresh")
-
- state.should_print = st.select_slider(
- "Should the Application print 'Hello World !' to the terminal:",
- [False, True],
- )
-
-
-# Step 4: Implement a Static Web Frontend. This could be react, vue, etc.
-class UIStatic(LightningFlow):
- # Step 5:
- def configure_layout(self):
- return StaticWebFrontend(os.path.join(os.path.dirname(__file__), "ui"))
-
-
-# Step 6: Implement the root flow.
-class HelloWorld(LightningFlow):
- def __init__(self):
- super().__init__()
- self.static_ui = UIStatic()
- self.streamlit_ui = UIStreamLit()
-
- def run(self):
- print("Hello World!" if self.streamlit_ui.should_print else "")
-
- def configure_layout(self):
- return [
- {"name": "StreamLit", "content": self.streamlit_ui},
- {"name": "Static", "content": self.static_ui},
- ]
-
-
-app = LightningApp(HelloWorld())
diff --git a/docs/source-app/code_samples/quickstart/hello_world/ui/index.html b/docs/source-app/code_samples/quickstart/hello_world/ui/index.html
deleted file mode 100644
index fe38c432f504c..0000000000000
--- a/docs/source-app/code_samples/quickstart/hello_world/ui/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Hello from component UIStatic
diff --git a/docs/source-app/conf.py b/docs/source-app/conf.py
deleted file mode 100644
index 5399d8205cd49..0000000000000
--- a/docs/source-app/conf.py
+++ /dev/null
@@ -1,412 +0,0 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# This file does only contain a selection of the most common options. For a
-# full list see the documentation:
-# http://www.sphinx-doc.org/en/master/config
-
-# -- Path setup --------------------------------------------------------------
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-
-import glob
-import inspect
-import os
-import shutil
-import sys
-
-import lai_sphinx_theme
-from lightning_utilities.docs import fetch_external_assets
-
-import lightning
-
-_PATH_HERE = os.path.abspath(os.path.dirname(__file__))
-_PATH_ROOT = os.path.realpath(os.path.join(_PATH_HERE, "..", ".."))
-sys.path.insert(0, os.path.abspath(_PATH_ROOT))
-
-_SPHINX_MOCK_REQUIREMENTS = int(os.environ.get("SPHINX_MOCK_REQUIREMENTS", True))
-_FAST_DOCS_DEV = int(os.environ.get("FAST_DOCS_DEV", True))
-_FETCH_S3_ASSETS = int(os.getenv("DOCS_FETCH_ASSETS", not _FAST_DOCS_DEV))
-
-# -- Project information -----------------------------------------------------
-
-# this name shall match the project name in Github as it is used for linking to code
-project = "lightning"
-copyright = lightning.__copyright__
-author = lightning.__author__
-
-# The short X.Y version
-version = lightning.__version__
-# The full version, including alpha/beta/rc tags
-release = lightning.__version__
-
-# -- Project documents -------------------------------------------------------
-
-if _FETCH_S3_ASSETS:
- fetch_external_assets(
- docs_folder=_PATH_HERE,
- assets_folder="_static/fetched-s3-assets",
- retrieve_pattern=r"https?://[-a-zA-Z0-9_]+\.s3\.[-a-zA-Z0-9()_\\+.\\/=]+"
- )
-
-# -- General configuration ---------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-
-needs_sphinx = "5.3"
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
- "sphinx.ext.autodoc",
- "sphinx.ext.doctest",
- "sphinx.ext.intersphinx",
- "sphinx_toolbox.collapse",
- "sphinx.ext.todo",
- "sphinx.ext.coverage",
- # "sphinx.ext.linkcode",
- "sphinx.ext.autosummary",
- "sphinx.ext.napoleon",
- # 'sphinxcontrib.mockautodoc', # raises error: directive 'automodule' is already registered ...
- # 'sphinxcontrib.fulltoc', # breaks pytorch-theme with unexpected kw argument 'titles_only'
- "sphinxcontrib.video",
- "myst_parser",
- "sphinx.ext.autosectionlabel",
- "nbsphinx",
- "sphinx_autodoc_typehints",
- "sphinx_copybutton",
- "sphinx_paramlinks",
- "sphinx_togglebutton",
- "sphinx.ext.githubpages",
- "lai_sphinx_theme.extensions.lightning",
- 'sphinx.ext.mathjax',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ["_templates"]
-
-# myst-parser, forcing to parse all html pages with mathjax
-# https://github.com/executablebooks/MyST-Parser/issues/394
-myst_update_mathjax = False
-# https://myst-parser.readthedocs.io/en/latest/syntax/optional.html?highlight=anchor#auto-generated-header-anchors
-myst_heading_anchors = 3
-
-# https://berkeley-stat159-f17.github.io/stat159-f17/lectures/14-sphinx..html#conf.py-(cont.)
-# https://stackoverflow.com/questions/38526888/embed-ipython-notebook-in-sphinx-document
-# I execute the notebooks manually in advance. If notebooks test the code,
-# they should be run at build time.
-nbsphinx_execute = "never"
-nbsphinx_allow_errors = True
-nbsphinx_requirejs_path = ""
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-#
-# source_suffix = ['.rst', '.md']
-# source_suffix = ['.rst', '.md', '.ipynb']
-source_suffix = {
- ".rst": "restructuredtext",
- ".txt": "markdown",
- ".md": "markdown",
- ".ipynb": "nbsphinx",
-}
-
-# The master toctree document.
-master_doc = "index"
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = 'en'
-
-# List of patterns, relative to source-app directory, that match files and
-# directories to ignore when looking for source-app files.
-# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = [
- "PULL_REQUEST_TEMPLATE.md",
- "**/README.md/*",
- "readme.md",
- "_templates",
- "code_samples/convert_pl_to_app/requirements.txt",
- "**/_static/*"
-]
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = None
-
-# -- Options for HTML output -------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-#
-html_theme = "lai_sphinx_theme"
-html_theme_path = [os.environ.get('LIT_SPHINX_PATH', lai_sphinx_theme.get_html_theme_path())]
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-
-html_theme_options = {
- "pytorch_project": lightning.__homepage__,
- "analytics_id": "G-D3Q2ESCTZR",
- "canonical_url": lightning.__homepage__,
- "collapse_navigation": False,
- "display_version": True,
- "logo_only": False,
-}
-
-html_favicon = "_static/images/icon.svg"
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_templates", "_static"]
-
-# Custom sidebar templates, must be a dictionary that maps document names
-# to template names.
-#
-# The default sidebars (for documents that don't match any pattern) are
-# defined by theme itself. Builtin themes are using these templates by
-# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
-# 'searchbox.html']``.
-#
-# html_sidebars = {}
-
-# -- Options for HTMLHelp output ---------------------------------------------
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = project + "-doc"
-
-# -- Options for LaTeX output ------------------------------------------------
-
-latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- # 'papersize': 'letterpaper',
- # The font size ('10pt', '11pt' or '12pt').
- # 'pointsize': '10pt',
- # Additional stuff for the LaTeX preamble.
- # 'preamble': '',
- # Latex figure (float) alignment
- "figure_align": "htbp",
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source-app start file, target name, title,
-# author, documentclass [howto, manual, or own class]).
-latex_documents = [
- (master_doc, project + ".tex", project + " Documentation", author, "manual"),
-]
-
-# MathJax configuration
-mathjax3_config = {
- 'tex': {
- 'packages': {'[+]': ['ams', 'newcommand', 'configMacros']}
- },
-}
-
-# -- Options for manual page output ------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source-app start file, name, description, authors, manual section).
-man_pages = [(master_doc, project, project + " Documentation", [author], 1)]
-
-# -- Options for Texinfo output ----------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source-app start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- (
- master_doc,
- project,
- project + " Documentation",
- author,
- project,
- lightning.__docs__,
- "Miscellaneous",
- ),
-]
-
-# -- Options for Epub output -------------------------------------------------
-
-# Bibliographic Dublin Core info.
-epub_title = project
-
-# The unique identifier of the text. This can be a ISBN number
-# or the project homepage.
-#
-# epub_identifier = ''
-
-# A unique identification for the text.
-#
-# epub_uid = ''
-
-# A list of files that should not be packed into the epub file.
-epub_exclude_files = ["search.html"]
-
-# -- Extension configuration -------------------------------------------------
-
-# -- Options for intersphinx extension ---------------------------------------
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {
- "python": ("https://docs.python.org/3", None),
- "torch": ("https://pytorch.org/docs/stable/", None),
- "numpy": ("https://numpy.org/doc/stable/", None),
-}
-
-nitpicky = True
-
-
-nitpick_ignore = [
- ("py:class", "typing.Self"),
- # missing in generated API
- ("py:exc", "MisconfigurationException"),
- # TODO: generated list of all existing ATM, need to be fixed
- ('py:exc', 'ApiException'),
- ('py:class', 'BaseModel'),
- ('py:exc', 'LightningPlatformException'),
- ('py:class', 'forwarded'),
- ('py:class', 'lightning.app.api.http_methods.Delete'),
- ('py:class', 'lightning.app.api.http_methods.Get'),
- ('py:class', 'lightning.app.api.http_methods.HttpMethod'),
- ('py:class', 'lightning.app.api.http_methods.Post'),
- ('py:class', 'lightning.app.api.http_methods.Put'),
- ('py:class', 'lightning.app.components.python.TracerPythonScript'),
- ('py:func', 'lightning.app.pdb.set_trace'),
- ('py:class', 'lightning.app.runners.runtime.Runtime'),
- ('py:class', 'lightning.app.source_code.local.LocalSourceCodeDir'),
- ('py:class', 'lightning.app.storage.payload._BasePayload'),
- ('py:class', 'lightning.app.structures.Dict'),
- ('py:class', 'lightning.app.structures.List'),
- ('py:class', 'lightning.app.testing.testing.LightningTestApp'),
- ('py:class', 'lightning.app.utilities.app_status.WorkStatus'),
- ('py:class', 'lightning.app.utilities.frontend.AppInfo'),
- ('py:class', 'lightning.app.utilities.packaging.app_config.AppConfig'),
- ('py:class', 'lightning.app.utilities.packaging.build_config.BuildConfig'),
- ('py:class', 'lightning.app.utilities.packaging.cloud_compute.CloudCompute'),
- ('py:class', 'lightning.app.utilities.proxies.WorkRunExecutor'),
- ('py:class', 'lightning.app.utilities.tracer.Tracer'),
- ('py:class', 'lightning_cloud.openapi.models.cloudspace_id_runs_body.CloudspaceIdRunsBody'),
- ('py:class', 'lightning_cloud.openapi.models.externalv1_lightningapp_instance.Externalv1LightningappInstance'),
- ('py:class', 'lightning_cloud.openapi.models.v1_cloud_space.V1CloudSpace'),
- ('py:class', 'lightning_cloud.openapi.models.v1_env_var.V1EnvVar'),
- ('py:class', 'lightning_cloud.openapi.models.v1_flowserver.V1Flowserver'),
- ('py:class', 'lightning_cloud.openapi.models.v1_lightning_auth.V1LightningAuth'),
- ('py:class', 'lightning_cloud.openapi.models.v1_lightning_run.V1LightningRun'),
- ('py:class', 'lightning_cloud.openapi.models.v1_lightningwork_drives.V1LightningworkDrives'),
- ('py:class', 'lightning_cloud.openapi.models.v1_membership.V1Membership'),
- ('py:class', 'lightning_cloud.openapi.models.v1_network_config.V1NetworkConfig'),
- ('py:class', 'lightning_cloud.openapi.models.v1_queue_server_type.V1QueueServerType'),
- ('py:class', 'lightning_cloud.openapi.models.v1_work.V1Work'),
- ('py:class', 'pydantic.main.BaseModel'),
- ('py:meth', 'transfer'),
-]
-
-# -- Options for todo extension ----------------------------------------------
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = True
-
-
-def setup(app):
- # this is for hiding doctest decoration,
- # see: http://z4r.github.io/python/2011/12/02/hides-the-prompts-and-output/
- app.add_js_file("copybutton.js")
- app.add_css_file("main.css")
-
-# copy all examples to local folder
-path_examples = os.path.join(_PATH_HERE, "..", "examples")
-if not os.path.isdir(path_examples):
- os.mkdir(path_examples)
-for path_app_example in glob.glob(os.path.join(_PATH_ROOT, "examples", "app_*")):
- path_app_example2 = os.path.join(path_examples, os.path.basename(path_app_example))
- if not os.path.isdir(path_app_example2):
- shutil.copytree(path_app_example, path_app_example2, dirs_exist_ok=True)
-
-
-# Ignoring Third-party packages
-# https://stackoverflow.com/questions/15889621/sphinx-how-to-exclude-imports-in-automodule
-def _package_list_from_file(file):
- list_pkgs = []
- with open(file) as fp:
- lines = fp.readlines()
- for ln in lines:
- found = [ln.index(ch) for ch in list(",=<>#") if ch in ln]
- pkg = ln[: min(found)] if found else ln
- if pkg.rstrip():
- list_pkgs.append(pkg.rstrip())
- return list_pkgs
-
-
-# define mapping from PyPI names to python imports
-PACKAGE_MAPPING = {
- "PyYAML": "yaml",
-}
-MOCK_PACKAGES = []
-if _SPHINX_MOCK_REQUIREMENTS:
- # mock also base packages when we are on RTD since we don't install them there
- MOCK_PACKAGES += _package_list_from_file(os.path.join(_PATH_ROOT, "requirements.txt"))
-MOCK_PACKAGES = [PACKAGE_MAPPING.get(pkg, pkg) for pkg in MOCK_PACKAGES]
-
-autodoc_mock_imports = MOCK_PACKAGES
-
-
-autosummary_generate = True
-
-autodoc_member_order = "groupwise"
-autoclass_content = "both"
-# the options are fixed and will be soon in release,
-# see https://github.com/sphinx-doc/sphinx/issues/5459
-autodoc_default_options = {
- "members": None,
- "methods": None,
- # 'attributes': None,
- "special-members": "__call__",
- "exclude-members": "_abc_impl",
- "show-inheritance": True,
- "private-members": True,
- "noindex": True,
-}
-
-# Sphinx will add “permalinks” for each heading and description environment as paragraph signs that
-# become visible when the mouse hovers over them.
-# This value determines the text for the permalink; it defaults to "¶". Set it to None or the empty
-# string to disable permalinks.
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_permalinks
-# html_add_permalinks = "¶"
-# True to prefix each section label with the name of the document it is in, followed by a colon.
-# For example, index:Introduction for a section called Introduction that appears in document index.rst.
-# Useful for avoiding ambiguity when the same section heading appears in different documents.
-# http://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html
-autosectionlabel_prefix_document = True
-
-# only run doctests marked with a ".. doctest::" directive
-doctest_test_doctest_blocks = ""
-doctest_global_setup = """
-import importlib
-import os
-
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-from lightning.fabric.loggers.tensorboard import _TENSORBOARD_AVAILABLE, _TENSORBOARDX_AVAILABLE
-"""
-coverage_skip_undoc_in_source = True
-
-# skip false positive linkcheck errors from anchors
-linkcheck_anchors = False
-
-# A timeout value, in seconds, for the linkcheck builder.
-linkcheck_timeout = 60
-
-# ignore all links in any CHANGELOG file
-linkcheck_exclude_documents = [r"^(.*\/)*CHANGELOG.*$"]
-
-
-# ignore the following relative links (false positive errors during linkcheck)
-linkcheck_ignore = [
- "https://www.openai.com/index/clip/",
-]
diff --git a/docs/source-app/contribute_app.rst b/docs/source-app/contribute_app.rst
deleted file mode 100644
index 2f690e8479062..0000000000000
--- a/docs/source-app/contribute_app.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:orphan:
-
-#################
-Contribute an app
-#################
-
-Show off your work! Contribute and example to be highlighted in our documentation and App gallery.
diff --git a/docs/source-app/core_api/lightning_app/app.py b/docs/source-app/core_api/lightning_app/app.py
deleted file mode 100644
index 42f716f99ffb2..0000000000000
--- a/docs/source-app/core_api/lightning_app/app.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.utilities.app_helpers import pretty_state
-
-
-class Work(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
- # Attributes are registered automatically in the state.
- self.counter = 0
-
- def run(self):
- # Incrementing an attribute gets reflected in the `Flow` state.
- self.counter += 1
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = Work()
-
- def run(self):
- if self.w.has_started:
- print(f"State: {pretty_state(self.state)} \n")
- self.w.run()
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/core_api/lightning_app/communication.rst b/docs/source-app/core_api/lightning_app/communication.rst
deleted file mode 100644
index d1aa1d35f54b1..0000000000000
--- a/docs/source-app/core_api/lightning_app/communication.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-##########################################
-Communication between Lightning Components
-##########################################
-
-**Audience:** Users that want to create interactive applications.
-
-**Level:** Intermediate
-
-**Prerequisite**: Read the :doc:`Communication in Lightning Apps article <../../../workflows/access_app_state>`.
-
-----
-
-.. include:: ../../core_api/lightning_app/communication_content.rst
diff --git a/docs/source-app/core_api/lightning_app/communication_content.rst b/docs/source-app/core_api/lightning_app/communication_content.rst
deleted file mode 100644
index 4b373dbb07588..0000000000000
--- a/docs/source-app/core_api/lightning_app/communication_content.rst
+++ /dev/null
@@ -1,160 +0,0 @@
-
-********************************
-Communication Between Components
-********************************
-
-When creating interactive Lightning Apps (App) with multiple components, you may need your components to share information with each other and rely on that information to control their execution, share progress in the UI, trigger a sequence of operations, etc.
-
-To accomplish that, Lightning components can communicate using the App State. The App State is composed of all attributes defined within each component's **__init__** method e.g anything attached to the component with **self.x = y**.
-
-All attributes of all **LightningWork (Work)** components are accessible in the **LightningFlow (Flow)** components in real-time.
-
-By design, the Flows communicate to all **Works** within the application. However, Works can't communicate with each other directly, they must use Flows as a proxy to communicate.
-
-Once a Work is running, any updates to the Work's state is automatically communicated to the Flow, as a delta (using `DeepDiff `_). The state communication isn't bi-directional, communication is only done from Work to Flow.
-
-Internally, the App is alternatively collecting deltas sent from all the registered Works and/or UI, and running the root Flow run method of the App.
-
-----
-
-*************************************************
-Communication from LightningWork to LightningFlow
-*************************************************
-
-LightningFlow (Flow) can access their children's LightningWork (Work) state.
-
-When a running Work attribute gets updated inside its method (separate process locally or remote machine), the app re-executes Flow's run method once it receives the state update from the Work.
-
-Here's an example to better understand communication from Work to Flow.
-
-The ``WorkCounter`` increments a counter until 1 million and the ``Flow`` prints the work counter.
-
-As the Work is running its own process, its state changes are sent to the Flow which contains the latest value of the counter.
-
-.. code-block:: python
-
- import lightning as L
-
-
- class WorkCounter(L.LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.counter = 0
-
- def run(self):
- for _ in range(int(10e6)):
- self.counter += 1
-
-
- class Flow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkCounter()
-
- def run(self):
- self.w.run()
- print(self.w.counter)
-
-
- app = L.LightningApp(Flow())
-
-
-A delta sent from the Work to the Flow looks like this:
-
-.. code-block:: python
-
- {"values_changed": {"root['works']['w']['vars']['counter']": {"new_value": 425}}}
-
-Here is the associated illustration:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/deltas.gif
- :alt: Mechanism showing how delta are sent.
- :width: 100 %
-
-Here's another example that is slightly different. Here we define a Flow and Work, where the Work increments a counter indefinitely and the Flow prints its state which contain the Work.
-
-You can easily check the state of your entire app as follows:
-
-.. literalinclude:: ../../core_api/lightning_app/app.py
-
-Run the app with:
-
-.. code-block:: bash
-
- lightning run app docs/source/core_api/lightning_app/app.py
-
-And here's the output you get when running the App using the **Lightning CLI**:
-
-.. code-block:: console
-
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- State: {'works': {'w': {'vars': {'counter': 1}}}}
- State: {'works': {'w': {'vars': {'counter': 2}}}}
- State: {'works': {'w': {'vars': {'counter': 3}}}}
- State: {'works': {'w': {'vars': {'counter': 3}}}}
- State: {'works': {'w': {'vars': {'counter': 4}}}}
- ...
-
-----
-
-*************************************************
-Communication from LightningFlow to LightningWork
-*************************************************
-
-Communication from the LightningFlow (Flow) to the LightningWork (Work) while running **isn't supported yet**. If your application requires this feature, please open an issue on Github.
-
-Here's an example of what would happen if you try to have the Flow communicate with the Work:
-
-.. code-block:: python
-
- import lightning as L
- from time import sleep
-
-
- class WorkCounter(L.LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.counter = 0
-
- def run(self):
- while True:
- sleep(1)
- print(f"Work {self.counter}")
-
-
- class Flow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkCounter()
-
- def run(self):
- self.w.run()
- sleep(1)
- print(f"Flow {self.w.counter}")
- self.w.counter += 1
-
-
- app = L.LightningApp(Flow())
-
-As you can see, there is a divergence between the values within the Work and the Flow.
-
-.. code-block:: console
-
- Flow 0
- Flow 1
- Flow 2
- Flow 3
- Work 0
- Flow 4
- Work 0
- Flow 5
- Work 0
- Flow 6
- Work 0
- Flow 7
- Work 0
- Flow 8
- Work 0
- Flow 9
- Work 0
- Flow 10
diff --git a/docs/source-app/core_api/lightning_app/compute_content.rst b/docs/source-app/core_api/lightning_app/compute_content.rst
deleted file mode 100644
index 8bb2e7039ad83..0000000000000
--- a/docs/source-app/core_api/lightning_app/compute_content.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-:orphan:
-
-***************************
-Customize my Flow resources
-***************************
-
-In the cloud, you can simply configure which machine to run on by passing
-a :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute` to your work ``__init__`` method:
-
-.. code-block:: python
-
- import lightning as L
-
- # Run on a small, shared CPU machine. This is the default for every LightningFlow.
- app = L.LightningApp(L.Flow(), flow_cloud_compute=L.CloudCompute())
-
-
-Here is the full list of supported machine names:
-
-.. list-table:: Hardware by Accelerator Type
- :widths: 25 25 25
- :header-rows: 1
-
- * - Name
- - # of CPUs
- - Memory
- * - flow-lite
- - 0.3
- - 4 GB
-
-The up-to-date prices for these instances can be found `here `_.
-
-----
-
-************
-CloudCompute
-************
-
-.. autoclass:: lightning.app.utilities.packaging.cloud_compute.CloudCompute
- :noindex:
diff --git a/docs/source-app/core_api/lightning_app/dynamic_work.rst b/docs/source-app/core_api/lightning_app/dynamic_work.rst
deleted file mode 100644
index bf202aa590a79..0000000000000
--- a/docs/source-app/core_api/lightning_app/dynamic_work.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-.. _dynamic_work:
-
-#####################
-Dynamic LightningWork
-#####################
-
-**Audience:** Users who want to create applications that adapt to user demands.
-
-**Level:** Advanced
-
-----
-
-.. include:: dynamic_work_content.rst
diff --git a/docs/source-app/core_api/lightning_app/dynamic_work_content.rst b/docs/source-app/core_api/lightning_app/dynamic_work_content.rst
deleted file mode 100644
index 31616a42a1493..0000000000000
--- a/docs/source-app/core_api/lightning_app/dynamic_work_content.rst
+++ /dev/null
@@ -1,202 +0,0 @@
-***************************************
-What Dynamic LightningWork does for you
-***************************************
-
-Dynamic LightningWork (Work) changes the resources your application uses while the application is running (aka at runtime).
-
-For example, imagine you want to create a research notebook app for your team. You want every member to be able to create multiple `JupyterLab `_ sessions on their hardware of choice.
-
-To allow every notebook to choose hardware, it needs to be set up in it's own :class:`~lightning.app.core.work.LightningWork`, but you can't know the number of notebooks user will need in advance. In this case you'll need to add ``LightningWorks`` dynamically at run time.
-
-----
-
-*****************
-Use Dynamic Works
-*****************
-
-Dynamic Works should be used anytime you want change the resources your application is using while it is running (aka at runtime).
-
-You're usually going to use the ``start`` and ``stop`` methods together.
-
-----
-
-Add a Dynamic Work
-^^^^^^^^^^^^^^^^^^
-
-There are a couple of ways you can add a dynamic Work:
-
-- Option 1: Attach your components in the **run** method using the Python functions.
-- Option 2: Use the Lightning built-in classes :class:`~lightning.app.structures.Dict` or :class:`~lightning.app.structures.List`.
-
-.. note:: Using the Lightning built-in classes is usually easier to read.
-
-----
-
-**OPTION 1:** Attach your components in the run method of a flow using the Python functions **hasattr**, **setattr**, and **getattr**:
-
-.. code-block:: python
-
- class RootFlow(lapp.LightningFlow):
-
- def run(self):
-
- if not hasattr(self, "work"):
- # The `Work` component is created and attached here.
- setattr(self, "work", Work())
- # Run the `Work` component.
- getattr(self, "work").run()
-
-**OPTION 2:** Use the built-in Lightning classes :class:`~lightning.app.structures.Dict` or :class:`~lightning.app.structures.List`
-
-.. code-block:: python
-
- from lightning.app.structures import Dict
-
- class RootFlow(lapp.LightningFlow):
-
- def __init__(self):
- super().__init__()
- self.dict = Dict()
-
- def run(self):
- if "work" not in self.dict:
- # The `Work` component is attached here.
- self.dict["work"] = Work()
- self.dict["work"].run()
-
-----
-
-Stop a Work
-^^^^^^^^^^^
-Stop a work when you are concerned about cost.
-
-To stop a work, use the work ``stop`` method:
-
-.. code-block:: python
-
- class RootFlow(L.LightningFlow):
-
- def __init__(self):
- super().__init__()
- self.work = Work()
-
- def run(self):
- self.work.stop()
-
-----
-
-*********************
-Dynamic Work Examples
-*********************
-
-..
- The entire application can be found `here `_.
-
-----
-
-Dynamic Work with Jupyter Notebooks
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In this example, we are dynamically creating ``JupyterLabWork`` every time a user clicks the **Create Jupyter Notebook** button.
-
-In order to do that, we are iterating over the list of ``jupyter_config_requests`` infinitely.
-
-.. code-block:: python
-
- import lightning as L
-
-
- class JupyterLabManager(L.LightningFlow):
-
- """This flow manages the users notebooks running within works.""""
-
- def __init__(self):
- super().__init__()
- self.jupyter_works = L.structures.Dict()
- self.jupyter_config_requests = []
-
- def run(self):
- for idx, jupyter_config in enumerate(self.jupyter_config_requests):
-
- # The Jupyter Config has this form is:
- # {"use_gpu": False/True, "token": None, "username": ..., "stop": False}
-
- # Step 1: Check if JupyterWork already exists for this username
- username = jupyter_config["username"]
- if username not in self.jupyter_works:
- jupyter_config["ready"] = False
-
- # Set the hardware selected by the user: GPU or CPU.
- cloud_compute = L.CloudCompute("gpu" if jupyter_config["use_gpu"] else "cpu-small")
-
- # Step 2: Create new JupyterWork dynamically !
- self.jupyter_works[username] = JupyterLabWork(cloud_compute=cloud_compute)
-
- # Step 3: Run the JupyterWork
- self.jupyter_works[username].run()
-
- # Step 4: Store the notebook token in the associated config.
- # We are using this to know when the notebook is ready
- # and display the stop button on the UI.
- if self.jupyter_works[username].token:
- jupyter_config["token"] = self.jupyter_works[username].token
-
- # Step 5: Stop the work if the user requested it.
- if jupyter_config['stop']:
- self.jupyter_works[username].stop()
- self.jupyter_config_requests.pop(idx)
-
- def configure_layout(self):
- return L.app.frontend.StreamlitFrontend(render_fn=render_fn)
-
-----
-
-Dynamic Works with StreamLit UI
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Continuing from the Jupyter Notebook example, in the UI, we receive the **state** of the Jupyter Manager and the state can be modified directly from the UI.
-
-.. code-block:: python
-
- import streamlit as st
-
-
- def render_fn(state):
-
- # Step 1: Enable users to select their notebooks and create them
- column_1, column_2, column_3 = st.columns(3)
- with column_1:
- create_jupyter = st.button("Create Jupyter Notebook")
- with column_2:
- username = st.text_input('Enter your username', "tchaton")
- assert username
- with column_3:
- use_gpu = st.checkbox('Use GPU')
-
- # Step 2: If a user clicked the button, add an element to the list of configs
- # Note: state.jupyter_config_requests = ... will sent the state update to the component.
- if create_jupyter:
- new_config = [{"use_gpu": use_gpu, "token": None, "username": username, "stop": False}]
- state.jupyter_config_requests = state.jupyter_config_requests + new_config
-
- # Step 3: List of running notebooks.
- for idx, config in enumerate(state.jupyter_config_requests):
- column_1, column_2, column_3 = st.columns(3)
- with column_1:
- if not idx:
- st.write(f"Idx")
- st.write(f"{idx}")
- with column_2:
- if not idx:
- st.write(f"Use GPU")
- st.write(config['use_gpu'])
- with column_3:
- if not idx:
- st.write(f"Stop")
- if config["token"]:
- should_stop = st.button("Stop this notebook")
-
- # Step 4: Change stop if the user clicked the button
- if should_stop:
- config["stop"] = should_stop
- state.jupyter_config_requests = state.jupyter_config_requests
diff --git a/docs/source-app/core_api/lightning_app/index.rst b/docs/source-app/core_api/lightning_app/index.rst
deleted file mode 100644
index bf0430f3bee5b..0000000000000
--- a/docs/source-app/core_api/lightning_app/index.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-#############
-Lightning App
-#############
-**Audience:** Users who want to know how an app works under the hood 🤯.
-
-**Lightning App:** We call workflows composed of multiple LightningWorks a **Lightning App**.
-
-----
-
-*******************
-Peek under the hood
-*******************
-
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: App Components Tree (Basic)
- :description: Learn more component composition and nesting.
- :col_css: col-md-4
- :button_link: ../../glossary/app_tree.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: The event loop (Basic)
- :description: Learn more about the event loop.
- :col_css: col-md-4
- :button_link: ../../glossary/event_loop.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Communication between Flow and Works
- :description: Learn more about components communicate.
- :col_css: col-md-4
- :button_link: communication.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Customize Flow compute resources
- :description: Learn more about Flow customizations.
- :col_css: col-md-4
- :button_link: compute_content.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Dynamically create, execute and stop Work
- :description: Learn more about components creation.
- :col_css: col-md-4
- :button_link: dynamic_work.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Sharing My Components (Intermediate)
- :description: Learn more component composition and nesting.
- :col_css: col-md-4
- :button_link: ../../glossary/sharing_components.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
-
-----
-
-*****************
-Lightning App API
-*****************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: LightningApp API
- :description: Look into the Lightning API reference.
- :col_css: col-md-4
- :button_link: lightning_app.html
- :height: 180
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/core_api/lightning_app/lightning_app.rst b/docs/source-app/core_api/lightning_app/lightning_app.rst
deleted file mode 100644
index af9592628a6f2..0000000000000
--- a/docs/source-app/core_api/lightning_app/lightning_app.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-:orphan:
-
-.. _lightning_app:
-
-############
-LightningApp
-############
-
-
-.. autoclass:: lightning.app.core.app.LightningApp
- :exclude-members: _run, connect, get_component_by_name, maybe_apply_changes, set_state
- :noindex:
diff --git a/docs/source-app/core_api/lightning_flow.rst b/docs/source-app/core_api/lightning_flow.rst
deleted file mode 100644
index 642112ae02793..0000000000000
--- a/docs/source-app/core_api/lightning_flow.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-.. _lightning_flow:
-
-#############
-LightningFlow
-#############
-
-.. autoclass:: lightning.app.core.flow.LightningFlow
- :exclude-members: _attach_backend, _exit, _is_state_attribute, set_state
diff --git a/docs/source-app/core_api/lightning_work/compute.rst b/docs/source-app/core_api/lightning_work/compute.rst
deleted file mode 100644
index 89313c4878cec..0000000000000
--- a/docs/source-app/core_api/lightning_work/compute.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-.. _cloud_compute:
-
-############################
-Customize your Cloud Compute
-############################
-
-**Audience:** Users who want to select the hardware to run in the cloud.
-
-**Level:** Intermediate
-
-----
-
-.. include:: compute_content.rst
diff --git a/docs/source-app/core_api/lightning_work/compute_content.rst b/docs/source-app/core_api/lightning_work/compute_content.rst
deleted file mode 100644
index 1ca64429dc342..0000000000000
--- a/docs/source-app/core_api/lightning_work/compute_content.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-
-***************************
-Customize my Work resources
-***************************
-
-In the cloud, you can simply configure which machine to run on by passing
-a :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute` to your work ``__init__`` method:
-
-.. code-block:: python
-
- import lightning as L
-
- # Run on a free, shared CPU machine. This is the default for every LightningWork.
- MyCustomWork(cloud_compute=L.CloudCompute())
-
- # Run on a dedicated, medium-size CPU machine (see specs below)
- MyCustomWork(cloud_compute=L.CloudCompute("cpu-medium"))
-
- # Run on cheap GPU machine with a single GPU (see specs below)
- MyCustomWork(cloud_compute=L.CloudCompute("gpu"))
-
- # Run on a fast multi-GPU machine (see specs below)
- MyCustomWork(cloud_compute=L.CloudCompute("gpu-fast-multi"))
-
-.. warning::
- Custom base images are not supported with the default CPU cloud compute. For example:
-
- .. code-block:: py
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__(cloud_build_config=BuildConfig(image="my-custom-image")) # no cloud compute, for example default work
-
-
-Here is the full list of supported machine names:
-
-.. list-table:: Hardware by Accelerator Type
- :widths: 25 25 25 25
- :header-rows: 1
-
- * - Name
- - # of CPUs
- - GPUs
- - Memory
- * - default
- - 1
- - 0
- - 4 GB
- * - cpu-small
- - 2
- - 0
- - 8 GB
- * - cpu-medium
- - 8
- - 0
- - 32 GB
- * - gpu
- - 4
- - 1 (T4, 16 GB)
- - 16 GB
- * - gpu-fast
- - 8
- - 1 (V100, 16 GB)
- - 61 GB
- * - gpu-fast-multi
- - 32
- - 4 (V100 16 GB)
- - 244 GB
-
-The up-to-date prices for these instances can be found `here `_.
-
-----
-
-**********************
-Stop my work when idle
-**********************
-
-By providing **idle_timeout=X Seconds**, the work is automatically stopped **X seconds** after doing nothing.
-
-.. code-block:: python
-
- import lightning as L
-
- # Run on a single CPU and turn down immediately when idle.
- MyCustomWork(cloud_compute=L.CloudCompute("gpu", idle_timeout=0))
-
-----
-
-************
-CloudCompute
-************
-
-.. autoclass:: lightning.app.utilities.packaging.cloud_compute.CloudCompute
- :noindex:
diff --git a/docs/source-app/core_api/lightning_work/handling_app_exception.rst b/docs/source-app/core_api/lightning_work/handling_app_exception.rst
deleted file mode 100644
index 20c9b618d97aa..0000000000000
--- a/docs/source-app/core_api/lightning_work/handling_app_exception.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-:orphan:
-
-###############################
-Handle Lightning App exceptions
-###############################
-
-**Audience:** Users who want to make Lightning Apps more robust to potential issues.
-
-**Level:** Advanced
-
-----
-
-.. include:: handling_app_exception_content.rst
diff --git a/docs/source-app/core_api/lightning_work/handling_app_exception_content.rst b/docs/source-app/core_api/lightning_work/handling_app_exception_content.rst
deleted file mode 100644
index 4840cf5fdf6f3..0000000000000
--- a/docs/source-app/core_api/lightning_work/handling_app_exception_content.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-
-***************************************************
-What handling Lightning App exceptions does for you
-***************************************************
-
-Imagine you are creating a Lightning App (App) where your team can launch model training by providing their own Github Repo any time they want.
-
-As the App admin, you don't want the App to go down if their code has a bug and breaks.
-
-Instead, you would like the LightningWork (Work) to capture the exception and present the issue to users.
-
-----
-
-****************************
-Configure exception handling
-****************************
-
-The LightningWork (Work) accepts an argument **raise_exception** which is **True** by default. This aligns with Python default behaviors.
-
-However, for the user case stated in the previous section, we want to capture the Work exceptions. This is done by providing ``raise_exception=False`` to the work ``__init__`` method.
-
-.. code-block:: python
-
- import lightning as L
-
- MyCustomWork(raise_exception=False) # <== HERE: The exception is captured.
-
- # Default behavior
- MyCustomWork(raise_exception=True) # <== HERE: The exception is raised within the flow and terminates the app
-
-
-And you can customize this behavior by overriding the ``on_exception`` hook to the Work.
-
-.. code-block:: python
-
- import lightning as L
-
- class MyCustomWork(L.LightningWork):
-
- def on_exception(self, exception: Exception):
- # do something when an exception is triggered.
-
-----
-
-**************************
-Exception handling example
-**************************
-
-This is the pseudo-code for the application described above.
-
-.. code-block:: python
-
- import lightning as L
-
- class RootFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.user_jobs = L.structures.Dict()
- self.requested_jobs = []
-
- def run(self):
- for request in self.requested_jobs:
- job_id = request["id"]
- if job_id not in self.user_jobs:
- # Note: The `GithubRepoLauncher` doesn't exist yet.
- self.user_jobs[job_id] = GithubRepoLauncher(
- **request,
- raise_exception=False, # <== HERE: The exception is captured.
- )
- self.user_jobs[job_id].run()
-
- if self.user_jobs[job_id].status.stage == "failed" and "printed" not in request:
- print(self.user_jobs[job_id].status) # <== HERE: Print the user exception.
- request["printed"] = True
diff --git a/docs/source-app/core_api/lightning_work/index.rst b/docs/source-app/core_api/lightning_work/index.rst
deleted file mode 100644
index 0b660f2209aba..0000000000000
--- a/docs/source-app/core_api/lightning_work/index.rst
+++ /dev/null
@@ -1,112 +0,0 @@
-##############
-Lightning Work
-##############
-
-**Audience:** Users who want to know how Lightning Work works under the hood 🤯.
-
-----
-
-*******************
-Peek under the hood
-*******************
-
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: To Cache or Not to Cache Calls
- :description: Learn more about work execution and internal caching.
- :col_css: col-md-4
- :button_link: ../../workflows/run_work_once.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Parallel Work
- :description: Learn more about work parallelization.
- :col_css: col-md-4
- :button_link: ../../workflows/run_work_in_parallel.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Sharing files between works
- :description: Learn more about data transfer works.
- :col_css: col-md-4
- :button_link: ../../glossary/storage/storage.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Sharing Python Objects between works
- :description: Learn more about sharing objects.
- :col_css: col-md-4
- :button_link: payload.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Checking Work Status
- :description: Learn more about work status.
- :col_css: col-md-4
- :button_link: status.html
- :height: 180
- :tag: Advanced
-
-.. displayitem::
- :header: Handling App Exceptions
- :description: Learn more about exception capturing.
- :col_css: col-md-4
- :button_link: handling_app_exception.html
- :height: 180
- :tag: Advanced
-
-.. raw:: html
-
-
-
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Customize your Cloud Compute
- :description: Learn more about changing hardware and requirements.
- :col_css: col-md-4
- :button_link: compute.html
- :height: 180
- :tag: Cloud
-
-.. raw:: html
-
-
-
-
-
-----
-
-******************
-Lightning Work API
-******************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: LightningWork API
- :description: Look into the Lightning API reference.
- :col_css: col-md-4
- :button_link: lightning_work.html
- :height: 180
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/core_api/lightning_work/lightning_work.rst b/docs/source-app/core_api/lightning_work/lightning_work.rst
deleted file mode 100644
index 54c7328411a3c..0000000000000
--- a/docs/source-app/core_api/lightning_work/lightning_work.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-:orphan:
-
-.. _lightning_work:
-
-#############
-LightningWork
-#############
-
-.. autoclass:: lightning.app.core.work.LightningWork
- :exclude-members: _aggregate_status_timeout, _is_state_attribute, _is_state_attribute, set_state
- :noindex:
diff --git a/docs/source-app/core_api/lightning_work/payload.rst b/docs/source-app/core_api/lightning_work/payload.rst
deleted file mode 100644
index cde42af1bd6d2..0000000000000
--- a/docs/source-app/core_api/lightning_work/payload.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-######################################
-Sharing Objects between LightningWorks
-######################################
-
-**Audience:** Users who want to know how to transfer Python objects between their LightningWorks.
-
-**Level:** Advanced
-
-**Prerequisite**: Reach Level 16+, know about the `pandas DataFrames `_ and read and read the :doc:`Access app state guide <../../workflows/access_app_state>`.
-
-----
-
-.. include:: payload_content.rst
diff --git a/docs/source-app/core_api/lightning_work/payload_content.rst b/docs/source-app/core_api/lightning_work/payload_content.rst
deleted file mode 100644
index 780f3985e30ea..0000000000000
--- a/docs/source-app/core_api/lightning_work/payload_content.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-
-**************************************
-What transferring objects does for you
-**************************************
-
-Imagine your application is processing some data using `pandas DaFrame `_ and you want to pass that data to another LightningWork (Work). This is what the **Payload API** is meant for.
-
-----
-
-*************************
-Use the Lightning Payload
-*************************
-
-The Payload enables non JSON-serializable attribute objects to be part of your Work's state and to be communicated to other Works.
-
-Here is an example:
-
-.. code-block:: python
-
- import lightning as L
- import pandas as pd
-
-
- class SourceWork(L.LightningWork):
- def __init__(self):
- super().__init__()
- self.df = None
-
- def run(self):
- # do some processing
-
- df = pd.DataFrame(data={"col1": [1, 2], "col2": [3, 4]})
-
- # The object you care about needs to be wrapped into a Payload object.
- self.df = L.storage.Payload(df)
-
- # You can access the original object from the payload using its value property.
- print("src", self.df.value)
- # src col1 col2
- # 0 1 3
- # 1 2 4
-
-Once the Payload object is attached to your Work's state, it can be passed to another work using the LightningFlow (Flow) as follows:
-
-.. code-block:: python
-
- import lightning as L
- import pandas as pd
-
-
- class DestinationWork(L.LightningWork):
- def run(self, df: L.storage.Payload):
- # You can access the original object from the payload using its value property.
- print("dst", df.value)
- # dst col1 col2
- # 0 1 3
- # 1 2 4
-
-
- class Flow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.src = SourceWork()
- self.dst = DestinationWork()
-
- def run(self):
- self.src.run()
- # The pandas DataFrame created by the ``SourceWork``
- # is passed to the ``DestinationWork``.
- # Internally, Lightning pickles and un-pickle the python object,
- # so you receive a copy of the original object.
- self.dst.run(df=self.src.df)
-
-
- app = L.LightningApp(Flow())
diff --git a/docs/source-app/core_api/lightning_work/status.rst b/docs/source-app/core_api/lightning_work/status.rst
deleted file mode 100644
index af3a27ac4047e..0000000000000
--- a/docs/source-app/core_api/lightning_work/status.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-:orphan:
-
-####################
-LightningWork Status
-####################
-
-**Audience:** Users who want to understand ``LightningWork`` under the hood.
-
-**Level:** Advanced
-
-----
-
-.. include:: status_content.rst
diff --git a/docs/source-app/core_api/lightning_work/status_content.rst b/docs/source-app/core_api/lightning_work/status_content.rst
deleted file mode 100644
index bb1f2f0273c84..0000000000000
--- a/docs/source-app/core_api/lightning_work/status_content.rst
+++ /dev/null
@@ -1,197 +0,0 @@
-
-*************************************
-Everything about LightningWork Status
-*************************************
-
-Statuses indicate transition points in the life of a LightningWork (Work) and contain metadata.
-
-The different stages are:
-
-.. code-block:: python
-
- class WorkStageStatus:
- NOT_STARTED = "not_started"
- STOPPED = "stopped"
- PENDING = "pending"
- RUNNING = "running"
- SUCCEEDED = "succeeded"
- FAILED = "failed"
-
-And a single status is as follows:
-
-.. code-block:: python
-
- @dataclass
- class WorkStatus:
- stage: WorkStageStatus
- timestamp: float
- reason: Optional[str] = None
- message: Optional[str] = None
- count: int = 1
-
-
-On creation, the Work's status flags all evaluate to ``False`` (in particular ``has_started``) and when calling ``work.run`` in your Lightning Flow (Flow),
-the Work transitions from ``is_pending`` to ``is_running`` and then to ``has_succeeded`` if everything went well or ``has_failed`` otherwise.
-
-.. code-block:: python
-
- from time import sleep
- import lightning as L
-
-
- class Work(L.LightningWork):
- def run(self, value: int):
- sleep(1)
- if value == 0:
- return
- raise Exception(f"The provided value was {value}")
-
-
- class Flow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = Work(raise_exception=False)
- self.counter = 0
-
- def run(self):
- if not self.work.has_started:
- print("NOT STARTED")
-
- elif self.work.is_pending:
- print("PENDING")
-
- elif self.work.is_running:
- print("RUNNING")
-
- elif self.work.has_succeeded:
- print("SUCCESS")
-
- elif self.work.has_failed:
- print("FAILED")
-
- elif self.work.has_stopped:
- print("STOPPED")
- self.stop()
-
- print(self.work.status)
- self.work.run(self.counter)
- self.counter += 1
-
-
- app = L.LightningApp(Flow())
-
-Run this app as follows:
-
-.. code-block:: bash
-
- lightning run app test.py > app_log.txt
-
-And here is the expected output inside ``app_log.txt`` and as expected,
-we are observing the following transition ``has_started``, ``is_pending``, ``is_running``, ``has_succeeded``, ``is_running`` and ``has_failed``
-
-.. code-block:: console
-
- NOT STARTED
- WorkStatus(stage='not_started', timestamp=1653498225.18468, reason=None, message=None, count=1)
- PENDING
- WorkStatus(stage='pending', timestamp=1653498225.217413, reason=None, message=None, count=1)
- PENDING
- WorkStatus(stage='pending', timestamp=1653498225.217413, reason=None, message=None, count=1)
- PENDING
- ...
- PENDING
- WorkStatus(stage='pending', timestamp=1653498225.217413, reason=None, message=None, count=1)
- PENDING
- WorkStatus(stage='pending', timestamp=1653498225.217413, reason=None, message=None, count=1)
- RUNNING
- WorkStatus(stage='running', timestamp=1653498228.825194, reason=None, message=None, count=1)
- ...
- SUCCESS
- WorkStatus(stage='succeeded', timestamp=1653498229.831793, reason=None, message=None, count=1)
- SUCCESS
- WorkStatus(stage='succeeded', timestamp=1653498229.831793, reason=None, message=None, count=1)
- SUCCESS
- WorkStatus(stage='succeeded', timestamp=1653498229.831793, reason=None, message=None, count=1)
- RUNNING
- WorkStatus(stage='running', timestamp=1653498229.846451, reason=None, message=None, count=1)
- RUNNING
- ...
- WorkStatus(stage='running', timestamp=1653498229.846451, reason=None, message=None, count=1)
- RUNNING
- WorkStatus(stage='running', timestamp=1653498229.846451, reason=None, message=None, count=1)
- FAILED
- WorkStatus(stage='failed', timestamp=1653498230.852565, reason='user_exception', message='The provided value was 1', count=1)
- FAILED
- WorkStatus(stage='failed', timestamp=1653498230.852565, reason='user_exception', message='The provided value was 1', count=1)
- FAILED
- WorkStatus(stage='failed', timestamp=1653498230.852565, reason='user_exception', message='The provided value was 1', count=1)
- FAILED
- WorkStatus(stage='failed', timestamp=1653498230.852565, reason='user_exception', message='The provided value was 1', count=1)
- ...
-
-In order to access all statuses:
-
-.. code-block:: python
-
- from time import sleep
- import lightning as L
-
-
- class Work(L.LightningWork):
- def run(self, value: int):
- sleep(1)
- if value == 0:
- return
- raise Exception(f"The provided value was {value}")
-
-
- class Flow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = Work(raise_exception=False)
- self.counter = 0
-
- def run(self):
- print(self.statuses)
- self.work.run(self.counter)
- self.counter += 1
-
-
- app = L.LightningApp(Flow())
-
-
-Run this app as follows:
-
-.. code-block:: bash
-
- lightning run app test.py > app_log.txt
-
-And here is the expected output inside ``app_log.txt``:
-
-
-.. code-block:: console
-
- # First execution with value = 0
-
- []
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1)]
- ...
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1)]
- ...
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1), WorkStatus(stage='succeeded', timestamp=1653498627.191053, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1), WorkStatus(stage='succeeded', timestamp=1653498627.191053, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498622.252016, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498626.185683, reason=None, message=None, count=1), WorkStatus(stage='succeeded', timestamp=1653498627.191053, reason=None, message=None, count=1)]
-
- # Second execution with value = 1
-
- [WorkStatus(stage='pending', timestamp=1653498627.204636, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498627.204636, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1)]
- ...
- [WorkStatus(stage='pending', timestamp=1653498627.204636, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1)]
- [WorkStatus(stage='pending', timestamp=1653498627.204636, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1), WorkStatus(stage='failed', timestamp=1653498628.210164, reason='user_exception', message='The provided value was 1', count=1)]
- [WorkStatus(stage='pending', timestamp=1653498627.204636, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1), WorkStatus(stage='running', timestamp=1653498627.205509, reason=None, message=None, count=1), WorkStatus(stage='failed', timestamp=1653498628.210164, reason='user_exception', message='The provided value was 1', count=1)]
diff --git a/docs/source-app/core_api/overview.rst b/docs/source-app/core_api/overview.rst
deleted file mode 100644
index 594433acce2aa..0000000000000
--- a/docs/source-app/core_api/overview.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-:orphan:
-
-.. _core_api:
-
-###############################
-Learn more about Lightning Core
-###############################
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Level-up with Lightning Apps
- :description: From Basics to Advanced Skills
- :col_css: col-md-6
- :button_link: ../levels/basic/index.html
- :height: 180
-
-.. displayitem::
- :header: Understand Lightning App
- :description: Detailed description
- :col_css: col-md-6
- :button_link: lightning_app/index.html
- :height: 180
-
-.. displayitem::
- :header: Understand Lightning Flow
- :description: Detailed description
- :col_css: col-md-6
- :button_link: lightning_flow.html
- :height: 180
-
-.. displayitem::
- :header: Understand Lightning Work
- :description: Detailed description
- :col_css: col-md-6
- :button_link: lightning_work/index.html
- :height: 180
diff --git a/docs/source-app/examples/dag/dag.rst b/docs/source-app/examples/dag/dag.rst
deleted file mode 100644
index 0df028ebbb0ce..0000000000000
--- a/docs/source-app/examples/dag/dag.rst
+++ /dev/null
@@ -1,81 +0,0 @@
-:orphan:
-
-######################################
-Develop a Directed Acyclic Graph (DAG)
-######################################
-
-.. _dag_example:
-
-**Audience:** Users coming from MLOps to Lightning Apps, looking for more flexibility.
-
-A typical ML training workflow can be implemented with a simple DAG.
-
-Below is a pseudo-code using the lightning framework that uses a LightningFlow to orchestrate the serial workflow: process data, train a model, and serve the model.
-
-.. code-block:: python
-
- import lightning as L
-
- class DAGFlow(L.LightningFlow):
-
- def __init__(self):
- super().__init__()
- self.processor = DataProcessorWork(...)
- self.train_work = TrainingWork(...)
- self.serve_work = ServeWork(...)
-
- def run(self):
- self.processor.run(...)
- self.train_work.run(...)
- self.serve_work.run(...)
-
-Below is a pseudo-code to run several works in parallel using a built-in :class:`~lightning.app.structures.Dict`.
-
-.. code-block:: python
-
- import lightning as L
-
- class DAGFlow(L.LightningFlow):
-
- def __init__(self):
- super().__init__()
- ...
- self.train_works = L.structures.Dict(**{
- "1": TrainingWork(..., parallel=True),
- "2": TrainingWork(..., parallel=True),
- "3": TrainingWork(..., parallel=True),
- ...
- })
- ...
-
- def run(self):
- self.processor.run(...)
-
- # The flow runs through them all, so we need to guard self.serve_work.run
- for work in self.train_works.values():
- work.run(...)
-
- # Wait for all to have finished without errors.
- if not all(w.has_succeeded for w in self.train_works):
- continue
-
- self.serve_work.run(...)
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Scheduled DAG with pandas and sklearn from scratch.
- :description: DAG example in pure Lightning.
- :col_css: col-md-4
- :button_link: dag_from_scratch.html
- :height: 180
- :tag: intermediate
diff --git a/docs/source-app/examples/dag/dag_from_scratch.rst b/docs/source-app/examples/dag/dag_from_scratch.rst
deleted file mode 100644
index 6625317471938..0000000000000
--- a/docs/source-app/examples/dag/dag_from_scratch.rst
+++ /dev/null
@@ -1,53 +0,0 @@
-:orphan:
-
-###################################################
-Scheduled DAG with pandas and sklearn from scratch.
-###################################################
-
-**Audience:** Users coming from MLOps to Lightning Apps, looking for more flexibility.
-
-**Level:** Intermediate.
-
-In this example, you will learn how to create a simple DAG which:
-
-* Download and process some data
-* Train several models and report their associated metrics
-
-and learn how to schedule this entire process.
-
-Find the complete example `here
`_.
-
-----
-
-**************************
-Step 1: Implement your DAG
-**************************
-
-Here is an illustration of the DAG to implement:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/simple_dag.png
- :alt: Simple DAG
- :width: 100 %
-
-First, let's define the component we need:
-
-* DataCollector is responsible to download the data
-* Processing is responsible to execute a ``processing.py`` script.
-* A collection of model work to train all models in parallel.
-
-.. literalinclude:: ../../../../examples/app/dag/app.py
- :lines: 52-74
-
-And its run method executes the steps described above.
-
-.. literalinclude:: ../../../../examples/app/dag/app.py
- :lines: 76-99
-
-----
-
-*****************************
-Step 2: Define the scheduling
-*****************************
-
-.. literalinclude:: ../../../../examples/app/dag/app.py
- :lines: 102-130
diff --git a/docs/source-app/examples/data_explore_app.rst b/docs/source-app/examples/data_explore_app.rst
deleted file mode 100644
index cd7011a10e93c..0000000000000
--- a/docs/source-app/examples/data_explore_app.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-##########################
-Build a Data Exploring App
-##########################
diff --git a/docs/source-app/examples/etl_app.rst b/docs/source-app/examples/etl_app.rst
deleted file mode 100644
index 5b494e943e445..0000000000000
--- a/docs/source-app/examples/etl_app.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-###############
-Build a ETL App
-###############
diff --git a/docs/source-app/examples/file_server/app.py b/docs/source-app/examples/file_server/app.py
deleted file mode 100644
index 6c54944fac10d..0000000000000
--- a/docs/source-app/examples/file_server/app.py
+++ /dev/null
@@ -1,243 +0,0 @@
-import json
-import os
-import tarfile
-import uuid
-import zipfile
-from pathlib import Path
-
-from lightning.app import LightningWork, LightningApp
-from lightning.app.storage import Drive
-
-
-class FileServer(LightningWork):
- def __init__(
- self,
- drive: Drive,
- base_dir: str = "file_server",
- chunk_size=10240,
- **kwargs
- ):
- """This component uploads, downloads files to your application.
-
- Arguments:
- drive: The drive can share data inside your application.
- base_dir: The local directory where the data will be stored.
- chunk_size: The quantity of bytes to download/upload at once.
-
- """
- super().__init__(
- cloud_build_config=BuildConfig(["flask, flask-cors"]),
- parallel=True,
- **kwargs,
- )
- # 1: Attach the arguments to the state.
- self.drive = drive
- self.base_dir = base_dir
- self.chunk_size = chunk_size
-
- # 2: Create a folder to store the data.
- os.makedirs(self.base_dir, exist_ok=True)
-
- # 3: Keep a reference to the uploaded filenames.
- self.uploaded_files = dict()
-
- def get_filepath(self, path: str) -> str:
- """Returns file path stored on the file server."""
- return os.path.join(self.base_dir, path)
-
- def get_random_filename(self) -> str:
- """Returns a random hash for the file name."""
- return uuid.uuid4().hex
-
- def upload_file(self, file):
- """Upload a file while tracking its progress."""
- # 1: Track metadata about the file
- filename = file.filename
- uploaded_file = self.get_random_filename()
- meta_file = uploaded_file + ".meta"
- self.uploaded_files[filename] = {
- "progress": (0, None), "done": False
- }
-
- # 2: Create a stream and write bytes of
- # the file to the disk under `uploaded_file` path.
- with open(self.get_filepath(uploaded_file), "wb") as out_file:
- content = file.read(self.chunk_size)
- while content:
- # 2.1 Write the file bytes
- size = out_file.write(content)
-
- # 2.2 Update the progress metadata
- self.uploaded_files[filename]["progress"] = (
- self.uploaded_files[filename]["progress"][0] + size,
- None,
- )
- # 4: Read next chunk of data
- content = file.read(self.chunk_size)
-
- # 3: Update metadata that the file has been uploaded.
- full_size = self.uploaded_files[filename]["progress"][0]
- self.drive.put(self.get_filepath(uploaded_file))
- self.uploaded_files[filename] = {
- "progress": (full_size, full_size),
- "done": True,
- "uploaded_file": uploaded_file,
- }
-
- # 4: Write down the metadata about the file to the disk
- meta = {
- "original_path": filename,
- "display_name": os.path.splitext(filename)[0],
- "size": full_size,
- "drive_path": uploaded_file,
- }
- with open(self.get_filepath(meta_file), "w") as f:
- json.dump(meta, f)
-
- # 5: Put the file to the drive.
- # It means other components can access get or list them.
- self.drive.put(self.get_filepath(meta_file))
- return meta
-
- def list_files(self, file_path: str):
- # 1: Get the local file path of the file server.
- file_path = self.get_filepath(file_path)
-
- # 2: If the file exists in the drive, transfer it locally.
- if not os.path.exists(file_path):
- self.drive.get(file_path)
-
- if os.path.isdir(file_path):
- result = set()
- for _, _, f in os.walk(file_path):
- for file in f:
- if not file.endswith(".meta"):
- for filename, meta in self.uploaded_files.items():
- if meta["uploaded_file"] == file:
- result.add(filename)
- return {"asset_names": [v for v in result]}
-
- # 3: If the filepath is a tar or zip file, list their contents
- if zipfile.is_zipfile(file_path):
- with zipfile.ZipFile(file_path, "r") as zf:
- result = zf.namelist()
- elif tarfile.is_tarfile(file_path):
- with tarfile.TarFile(file_path, "r") as tf:
- result = tf.getnames()
- else:
- raise ValueError("Cannot open archive file!")
-
- # 4: Returns the matching files.
- return {"asset_names": result}
-
- def run(self):
- # 1: Imports flask requirements.
- from flask import Flask, request
- from flask_cors import CORS
-
- # 2: Create a flask app
- flask_app = Flask(__name__)
- CORS(flask_app)
-
- # 3: Define the upload file endpoint
- @flask_app.post("/upload_file/")
- def upload_file():
- """Upload a file directly as form data."""
- f = request.files["file"]
- return self.upload_file(f)
-
- @flask_app.get("/")
- def list_files():
- return self.list_files(str(Path(self.base_dir).resolve()))
-
- # 5: Start the flask app while providing the `host` and `port`.
- flask_app.run(host=self.host, port=self.port, load_dotenv=False)
-
- def alive(self):
- """Hack: Returns whether the server is alive."""
- return self.url != ""
-
-
-import requests
-
-from lightning import LightningWork
-
-
-class TestFileServer(LightningWork):
- def __init__(self, drive: Drive):
- super().__init__(cache_calls=True)
- self.drive = drive
-
- def run(self, file_server_url: str, first=True):
- if first:
- with open("test.txt", "w") as f:
- f.write("Some text.")
-
- response = requests.post(
- file_server_url + "/upload_file/",
- files={'file': open("test.txt", 'rb')}
- )
- assert response.status_code == 200
- else:
- response = requests.get(file_server_url)
- assert response.status_code == 200
- assert response.json() == {"asset_names": ["test.txt"]}
-
-
-from lightning import LightningApp, LightningFlow
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- # 1: Create a drive to share data between works
- self.drive = Drive("lit://file_server")
- # 2: Create the filer server
- self.file_server = FileServer(self.drive)
- # 3: Create the file ser
- self.test_file_server = TestFileServer(self.drive)
-
- def run(self):
- # 1: Start the file server.
- self.file_server.run()
-
- # 2: Trigger the test file server work when ready.
- if self.file_server.alive():
- # 3 Execute the test file server work.
- self.test_file_server.run(self.file_server.url)
- self.test_file_server.run(self.file_server.url, first=False)
-
- # 4 When both execution are successful, exit the app.
- if self.test_file_server.num_successes == 2:
- self.stop()
-
- def configure_layout(self):
- # Expose the file_server component
- # in the UI using its `/` endpoint.
- return {"name": "File Server", "content": self.file_server}
-
-
-from lightning.app.runners import MultiProcessRuntime
-
-
-def test_file_server():
- app = LightningApp(Flow())
- MultiProcessRuntime(app).dispatch()
-
-
-from lightning.app.testing import run_app_in_cloud
-
-
-def test_file_server_in_cloud():
- # You need to provide the directory containing the app file.
- app_dir = "docs/source-app/examples/file_server"
- with run_app_in_cloud(app_dir) as (admin_page, view_page, get_logs_fn, name):
- """# 1. `admin_page` and `view_page` are playwright Page Objects.
-
- # Check out https://playwright.dev/python/ doc to learn more.
- # You can click the UI and trigger actions.
-
- # 2. By calling logs = get_logs_fn(),
- # you get all the logs currently on the admin page.
-
- """
diff --git a/docs/source-app/examples/file_server/file_server.rst b/docs/source-app/examples/file_server/file_server.rst
deleted file mode 100644
index f9f800a085bb8..0000000000000
--- a/docs/source-app/examples/file_server/file_server.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-:orphan:
-
-.. _fileserver_example:
-
-#####################
-Develop a File Server
-#####################
-
-**Prerequisite**: Reach :ref:`level 16+ ` and read the :ref:`Drive article `.
-
-----
-
-.. include:: file_server_content.rst
diff --git a/docs/source-app/examples/file_server/file_server_content.rst b/docs/source-app/examples/file_server/file_server_content.rst
deleted file mode 100644
index e9e9017232927..0000000000000
--- a/docs/source-app/examples/file_server/file_server_content.rst
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-*********
-Our Goal
-*********
-
-Create a simple Lightning App (App) that allows users to upload files and list the uploaded files.
-
-----
-
-*************
-Completed App
-*************
-
-Here is a recording of the final App built in this example, tested with pytest.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/file_server.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/file_server.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-**********
-App Design
-**********
-
-In order to create this App, we need to develop two components and an App:
-
-* A **File Server Component** that gives you the ability to download or list files shared with your App. This is particularly useful when you want to trigger an ML job but your users need to provide their own data or if the user wants to download the trained checkpoints.
-
-* A **Test File Server** Component to interact with the file server.
-
-* An App putting everything together and the App's associated pytest tests.
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the File Server general structure
- :description: Put together the shape of the Component
- :col_css: col-md-4
- :button_link: file_server_step_1.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 2: Implement the File Server upload and list files methods
- :description: Add the core functionalities to the Component
- :col_css: col-md-4
- :button_link: file_server_step_2.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 3: Implement a File Server Testing Component
- :description: Create a Component to test the file server
- :col_css: col-md-4
- :button_link: file_server_step_3.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 4: Implement tests for the File Server component with pytest
- :description: Create an App to validate the upload and list files endpoints
- :col_css: col-md-4
- :button_link: file_server_step_4.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/file_server/file_server_step_1.rst b/docs/source-app/examples/file_server/file_server_step_1.rst
deleted file mode 100644
index 8703a1d443ef2..0000000000000
--- a/docs/source-app/examples/file_server/file_server_step_1.rst
+++ /dev/null
@@ -1,49 +0,0 @@
-:orphan:
-
-##################################################
-Step 1: Implement the FileServer general structure
-##################################################
-
-Let’s dive in on how to develop the component with the following code:
-
-.. literalinclude:: ./app.py
- :lines: 1-41, 132-158
- :emphasize-lines: 16, 51-
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 2: Implement the File Server upload and list files methods
- :description: Add the core functionalities to the Component
- :col_css: col-md-4
- :button_link: file_server_step_2.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 3: Implement a File Server Testing Component
- :description: Create a Component to test the file server
- :col_css: col-md-4
- :button_link: file_server_step_3.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 4: Implement tests for the File Server component with pytest
- :description: Create an App to validate the upload and list files endpoints
- :col_css: col-md-4
- :button_link: file_server_step_4.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/file_server/file_server_step_2.rst b/docs/source-app/examples/file_server/file_server_step_2.rst
deleted file mode 100644
index 86471604a124f..0000000000000
--- a/docs/source-app/examples/file_server/file_server_step_2.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-:orphan:
-
-################################################################
-Step 2: Implement the File Server upload and list_files methods
-################################################################
-
-Let's dive in on how to implement these methods.
-
-***************************
-Implement the upload method
-***************************
-
-In this method, we are creating a stream between the uploaded file and the uploaded file stored on the file server disk.
-
-Once the file is uploaded, we are putting the file into the :class:`~lightning.app.storage.drive.Drive`, so it becomes persistent and accessible to all Components.
-
-.. literalinclude:: ./app.py
- :lines: 12, 51-99
- :emphasize-lines: 49
-
-*******************************
-Implement the fist_files method
-*******************************
-
-First, in this method, we get the file in the file server filesystem, if available in the Drive. Once done, we list the the files under the provided paths and return the results.
-
-.. literalinclude:: ./app.py
- :lines: 12, 100-130
- :emphasize-lines: 9
-
-
-*******************
-Implement utilities
-*******************
-
-.. literalinclude:: ./app.py
- :lines: 12, 43-49
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the File Server general structure
- :description: Put together the shape of the Component
- :col_css: col-md-4
- :button_link: file_server_step_1.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 3: Implement a File Server Testing Component
- :description: Create a Component to test the file server
- :col_css: col-md-4
- :button_link: file_server_step_3.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 4: Implement tests for the File Server component with pytest
- :description: Create an App to validate the upload and list files endpoints
- :col_css: col-md-4
- :button_link: file_server_step_4.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/file_server/file_server_step_3.rst b/docs/source-app/examples/file_server/file_server_step_3.rst
deleted file mode 100644
index 4703ef0750f1e..0000000000000
--- a/docs/source-app/examples/file_server/file_server_step_3.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-:orphan:
-
-#################################################
-Step 3: Implement a File Server Testing Component
-#################################################
-
-Let's dive in on how to implement a testing component for a server.
-
-This component needs to test two things:
-
-* The **/upload_file/** endpoint by creating a file and sending its content to it.
-
-* The **/** endpoint listing files, by validating the that previously uploaded file is present in the response.
-
-.. literalinclude:: ./app.py
- :lines: 165-182
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the File Server general structure
- :description: Put together the shape of the Component
- :col_css: col-md-4
- :button_link: file_server_step_1.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 2: Implement the File Server upload and list files methods
- :description: Add the core functionalities to the Component
- :col_css: col-md-4
- :button_link: file_server_step_2.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 4: Implement tests for the File Server component with pytest
- :description: Create an App to validate the upload and list files endpoints
- :col_css: col-md-4
- :button_link: file_server_step_4.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/file_server/file_server_step_4.rst b/docs/source-app/examples/file_server/file_server_step_4.rst
deleted file mode 100644
index 04517dfa7baf3..0000000000000
--- a/docs/source-app/examples/file_server/file_server_step_4.rst
+++ /dev/null
@@ -1,127 +0,0 @@
-:orphan:
-
-#################################################################
-Step 4: Implement tests for the File Server component with pytest
-#################################################################
-
-Let's create a simple App with our **File Server** and **File Server Test** components.
-
-Once the File Server is up and running, we'll execute the **test_file_server** LightningWork and when both calls are successful, we exit the App using ``self._exit``.
-
-.. literalinclude:: ./app.py
- :lines: 187-218
-
-
-Simply create a ``test.py`` file with the following code and run ``pytest tests.py``:
-
-.. literalinclude:: ./app.py
- :lines: 221-226
-
-To test the App in the cloud, create a ``cloud_test.py`` file with the following code and run ``pytest cloud_test.py``.
-Under the hood, we are using the end-to-end testing `playwright `_ library, so you can interact with the UI.
-
-.. literalinclude:: ./app.py
- :lines: 229-
-
-----
-
-********************
-Test the application
-********************
-
-Clone the Lightning repo and run the following command:
-
-.. code-block:: bash
-
- pytest docs/source/examples/file_server/app.py --capture=no -v
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the File Server general structure
- :description: Put together the shape of the Component
- :col_css: col-md-4
- :button_link: file_server_step_1.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 2: Implement the File Server upload and list files methods
- :description: Add the core functionalities to the Component
- :col_css: col-md-4
- :button_link: file_server_step_2.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: Step 3: Implement a File Server Testing Component
- :description: Create a Component to test the file server
- :col_css: col-md-4
- :button_link: file_server_step_3.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
-
-----
-
-******************
-Find more examples
-******************
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Develop a DAG
- :description: Create a dag pipeline
- :col_css: col-md-4
- :button_link: ../dag/dag.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a Github Repo Script Runner
- :description: Run any script on github in the cloud
- :col_css: col-md-4
- :button_link: ../github_repo_runner/github_repo_runner.html
- :height: 150
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Develop a HPO Sweeper
- :description: Train multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../hpo/hpo.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a Model Server
- :description: Serve multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../model_server_app/model_server_app.html
- :height: 150
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/github_repo_runner/app.py b/docs/source-app/examples/github_repo_runner/app.py
deleted file mode 100644
index f57cfcd12dc24..0000000000000
--- a/docs/source-app/examples/github_repo_runner/app.py
+++ /dev/null
@@ -1,309 +0,0 @@
-import io
-import os
-import subprocess
-import sys
-from copy import deepcopy
-from functools import partial
-from subprocess import Popen
-from typing import Dict, List, Optional
-
-from lightning import BuildConfig, CloudCompute, LightningApp, LightningFlow
-from lightning.app import structures
-from lightning.app.components import TracerPythonScript
-from lightning.app.frontend import StreamlitFrontend
-from lightning.app.storage.path import Path
-from lightning.app.utilities.state import AppState
-
-
-class GithubRepoRunner(TracerPythonScript):
- def __init__(
- self,
- id: str,
- github_repo: str,
- script_path: str,
- script_args: List[str],
- requirements: List[str],
- cloud_compute: Optional[CloudCompute] = None,
- **kwargs,
- ):
- """The GithubRepoRunner Component clones a repo, runs a specific script with provided arguments and collect
- logs.
-
- Arguments:
- id: Identified of the component.
- github_repo: The Github Repo URL to clone.
- script_path: The path to the script to execute.
- script_args: The arguments to be provided to the script.
- requirements: The python requirements tp run the script.
- cloud_compute: The object to select the cloud instance.
-
- """
- super().__init__(
- script_path=script_path,
- script_args=script_args,
- cloud_compute=cloud_compute,
- cloud_build_config=BuildConfig(requirements=requirements),
- **kwargs,
- )
- self.id = id
- self.github_repo = github_repo
- self.logs = []
-
- def run(self, *args, **kwargs):
- # 1. Hack: Patch stdout so we can capture the logs.
- string_io = io.StringIO()
- sys.stdout = string_io
-
- # 2: Use git command line to clone the repo.
- repo_name = self.github_repo.split("/")[-1].replace(".git", "")
- cwd = os.path.dirname(__file__)
- subprocess.Popen(
- f"git clone {self.github_repo}", cwd=cwd, shell=True).wait()
-
- # 3: Execute the parent run method of the TracerPythonScript class.
- os.chdir(os.path.join(cwd, repo_name))
- super().run(*args, **kwargs)
-
- # 4: Get all the collected logs and add them to the state.
- # This isn't optimal as heavy, but works for this demo purpose.
- self.logs = string_io.getvalue()
- string_io.close()
-
- def configure_layout(self):
- return {"name": self.id, "content": self}
-
-
-class PyTorchLightningGithubRepoRunner(GithubRepoRunner):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.best_model_path = None
- self.best_model_score = None
-
- def configure_tracer(self):
- from lightning.pytorch import Trainer
- from lightning.pytorch.callbacks import Callback
-
- tracer = super().configure_tracer()
-
- class TensorboardServerLauncher(Callback):
- def __init__(self, work):
- # The provided `work` is the
- # current ``PyTorchLightningScript`` work.
- self.w = work
-
- def on_train_start(self, trainer, *_):
- # Add `host` and `port` for tensorboard to work in the cloud.
- cmd = f"tensorboard --logdir='{trainer.logger.log_dir}'"
- server_args = f"--host {self.w.host} --port {self.w.port}"
- Popen(cmd + " " + server_args, shell=True)
-
- def trainer_pre_fn(self, *args, work=None, **kwargs):
- # Intercept Trainer __init__ call
- # and inject a ``TensorboardServerLauncher`` component.
- kwargs["callbacks"].append(TensorboardServerLauncher(work))
- return {}, args, kwargs
-
- # 5. Patch the `__init__` method of the Trainer
- # to inject our callback with a reference to the work.
- tracer.add_traced(
- Trainer, "__init__", pre_fn=partial(trainer_pre_fn, work=self))
- return tracer
-
- def on_after_run(self, end_script_globals):
- import torch
-
- # 1. Once the script has finished to execute,
- # we can collect its globals and access any objects.
- trainer = end_script_globals["cli"].trainer
- checkpoint_callback = trainer.checkpoint_callback
- lightning_module = trainer.lightning_module
-
- # 2. From the checkpoint_callback,
- # we are accessing the best model weights
- checkpoint = torch.load(checkpoint_callback.best_model_path)
-
- # 3. Load the best weights and torchscript the model.
- lightning_module.load_state_dict(checkpoint["state_dict"])
- lightning_module.to_torchscript(f"{self.name}.pt")
-
- # 4. Use lightning.app.storage.Pathto create a reference to the
- # torch scripted model. In the cloud with multiple machines,
- # by simply passing this reference to another work,
- # it triggers automatically a file transfer.
- self.best_model_path = Path(f"{self.name}.pt")
-
- # 5. Keep track of the metrics.
- self.best_model_score = float(checkpoint_callback.best_model_score)
-
-
-class KerasGithubRepoRunner(GithubRepoRunner):
- """Left to the users to implement."""
-
-
-class TensorflowGithubRepoRunner(GithubRepoRunner):
- """Left to the users to implement."""
-
-
-GITHUB_REPO_RUNNERS = {
- "PyTorch Lightning": PyTorchLightningGithubRepoRunner,
- "Keras": KerasGithubRepoRunner,
- "Tensorflow": TensorflowGithubRepoRunner,
-}
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- # 1: Keep track of the requests within the state
- self.requests = []
- # 2: Create a dictionary of components.
- self.ws = structures.Dict()
-
- def run(self):
- # Iterate continuously over all requests
- for request_id, request in enumerate(self.requests):
- self._handle_request(request_id, deepcopy(request))
-
- def _handle_request(self, request_id: int, request: Dict):
- # 1: Create a name and find selected framework
- name = f"w_{request_id}"
- ml_framework = request["train"].pop("ml_framework")
-
- # 2: If the component hasn't been created yet, create it.
- if name not in self.ws:
- work_cls = GITHUB_REPO_RUNNERS[ml_framework]
- work = work_cls(id=request["id"], **request["train"])
- self.ws[name] = work
-
- # 3: Run the component
- self.ws[name].run()
-
- # 4: Once the component has finished,
- # add metadata to the original request for the UI.
- if self.ws[name].best_model_path:
- request = self.requests[request_id]
- request["best_model_score"] = self.ws[name].best_model_score
- request["best_model_path"] = self.ws[name].best_model_path
-
- def configure_layout(self):
- # Create a StreamLit UI for the user to run his Github Repo.
- return StreamlitFrontend(render_fn=render_fn)
-
-
-def page_1__create_new_run(state):
- import streamlit as st
-
- st.markdown("# Create a new Run 🎈")
-
- # 1: Collect arguments from the users
- id = st.text_input("Name your run", value="my_first_run")
- github_repo = st.text_input(
- "Enter a Github Repo URL", value="https://github.com/Lightning-AI/lightning-quick-start.git"
- )
-
- default_script_args = (
- "--trainer.max_epochs=5"
- " --trainer.limit_train_batches=4"
- " --trainer.limit_val_batches=4"
- " --trainer.callbacks=ModelCheckpoint"
- " --trainer.callbacks.monitor=val_acc"
- )
- default_requirements = "torchvision, pytorch_lightning, jsonargparse[signatures]"
-
- script_path = st.text_input("Enter your script to run", value="train_script.py")
- script_args = st.text_input("Enter your base script arguments", value=default_script_args)
- requirements = st.text_input("Enter your requirements", value=default_requirements)
- ml_framework = st.radio(
- "Select your ML Training Frameworks", options=["PyTorch Lightning", "Keras", "Tensorflow"]
- )
-
- if ml_framework not in ("PyTorch Lightning"):
- st.write(f"{ml_framework} isn't supported yet.")
- return
-
- clicked = st.button("Submit")
-
- # 2: If clicked, create a new request.
- if clicked:
- new_request = {
- "id": id,
- "train": {
- "github_repo": github_repo,
- "script_path": script_path,
- "script_args": script_args.split(" "),
- "requirements": requirements.split(" "),
- "ml_framework": ml_framework,
- },
- }
- # 3: IMPORTANT: Add a new request to the state in-place.
- # The flow receives the UI request and dynamically create
- # and run the associated work from the request information.
- state.requests = state.requests + [new_request]
-
-
-def page_2__view_run_lists(state):
- import streamlit as st
-
- st.markdown("# Run Lists 🎈")
- # 1: Iterate through all the requests in the state.
- for i, r in enumerate(state.requests):
- i = str(i)
- # 2: Display information such as request, logs, work state, model score.
- work = state._state["structures"]["ws"]["works"][f"w_{i}"]
- with st.expander(f"Expand to view Run {i}", expanded=False):
- if st.checkbox("Expand to view your configuration", key=i):
- st.json(r)
- if st.checkbox("Expand to view logs", key=i):
- st.code(body=work["vars"]["logs"])
- if st.checkbox("Expand to view your work state", key=i):
- work["vars"].pop("logs")
- st.json(work)
- best_model_score = r.get("best_model_score", None)
- if best_model_score:
- if st.checkbox("Expand to view your run performance", key=i):
- st.json({"best_model_score": best_model_score, "best_model_path": r.get("best_model_path")})
-
-
-def page_3__view_app_state(state):
- import streamlit as st
-
- st.markdown("# App State 🎈")
- st.write(state._state)
-
-
-def render_fn(state: AppState):
- import streamlit as st
-
- page_names_to_funcs = {
- "Create a new Run": partial(page_1__create_new_run, state=state),
- "View your Runs": partial(page_2__view_run_lists, state=state),
- "View the App state": partial(page_3__view_app_state, state=state),
- }
- selected_page = st.sidebar.selectbox(
- "Select a page", page_names_to_funcs.keys())
- page_names_to_funcs[selected_page]()
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- # Create the flow
- self.flow = Flow()
-
- def run(self):
- # Run the flow
- self.flow.run()
-
- def configure_layout(self):
- # 1: Add the main StreamLit UI
- selection_tab = [{
- "name": "Run your Github Repo",
- "content": self.flow,
- }]
- # 2: Add a new tab whenever a new work is dynamically created
- run_tabs = [e.configure_layout() for e in self.flow.ws.values()]
- # 3: Returns the list of tabs.
- return selection_tab + run_tabs
-
-
-app = LightningApp(RootFlow())
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner.rst
deleted file mode 100644
index 7e239b2dfd33d..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-.. _github_repo_script_runner_example:
-
-###################################
-Develop a Github Repo Script Runner
-###################################
-
-**Audience:** Users that want to develop interactive applications which runs Github Repo in the cloud at any scale for multiple users.
-
-**Prerequisite**: Reach :ref:`level 16+ ` and read the docstring of of :class:`~lightning.app.components.python.tracer.TracerPythonScript` component.
-
-----
-
-.. include:: github_repo_runner_content.rst
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner_content.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner_content.rst
deleted file mode 100644
index 2a0a3aa35c5ac..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner_content.rst
+++ /dev/null
@@ -1,97 +0,0 @@
-
-********
-Our Goal
-********
-
-Create a simple Lightning App (App) where users can enter information in a UI to run a given PyTorch Lightning Script from a given Github Repo with some optional extra Python requirements and arguments.
-
-Users should be able to monitor their training progress in real-time, view the logs, and get the best monitored metric and associated checkpoint for their models.
-
-----
-
-Completed App
-^^^^^^^^^^^^^
-
-Here is a recording of the final application built in this example. The example is around 200 lines in total and should give you a great foundation to build your own Lightning App.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/github_app.mp4
- :poster: "https://pl-public-data.s3.amazonaws.com/assets_lightning/github_app.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-**********
-App Design
-**********
-
-In order to develop the App, we need to build several components:
-
-* A GithubRepoRunner Component that clones a repo, runs a specific script with provided arguments and collect logs.
-
-* A PyTorch Lightning GithubRepoRunner Component that augments the GithubRepoRunner component to track PyTorch Lightning Trainer.
-
-* A UI for the users to provide to trigger dynamically a new execution.
-
-* A Flow to dynamically create GithubRepoRunner once a user submits information from the UI.
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the GithubRepoRunner Component
- :description: Clone and execute script from a GitHub Repo.
- :col_css: col-md-4
- :button_link: github_repo_runner_step_1.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 2: Implement the PyTorch Lightning GithubRepoRunner Component
- :description: Automate PyTorch Lightning execution
- :col_css: col-md-4
- :button_link: github_repo_runner_step_2.html
- :height: 180
- :tag: Advanced
-
-.. displayitem::
- :header: Step 3: Implement the Flow to manage user requests
- :description: Dynamically create GithubRepoRunner
- :col_css: col-md-4
- :button_link: github_repo_runner_step_3.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 4: Implement the UI with StreamLit
- :description: Several pages application
- :col_css: col-md-4
- :button_link: github_repo_runner_step_4.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 5: Put it all together
- :description:
- :col_css: col-md-4
- :button_link: github_repo_runner_step_5.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_1.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner_step_1.rst
deleted file mode 100644
index e85ecc9da6b95..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_1.rst
+++ /dev/null
@@ -1,62 +0,0 @@
-:orphan:
-
-************************************************
-Step 1: Implement the GithubRepoRunner Component
-************************************************
-
-The GithubRepoRunner Component clones a repo, runs a specific script with provided arguments, and collect logs.
-
-Let's dive in on how to develop the component with the following code:
-
-.. literalinclude:: ./app.py
- :lines: -72
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 2: Implement the PyTorch Lightning GithubRepoRunner Component
- :description: Automate PyTorch Lightning execution
- :col_css: col-md-4
- :button_link: github_repo_runner_step_2.html
- :height: 180
- :tag: Advanced
-
-.. displayitem::
- :header: Step 3: Implement the Flow to manage user requests
- :description: Dynamically create GithubRepoRunner
- :col_css: col-md-4
- :button_link: github_repo_runner_step_3.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 4: Implement the UI with StreamLit
- :description: Several pages application
- :col_css: col-md-4
- :button_link: github_repo_runner_step_4.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 5: Put it all together
- :description:
- :col_css: col-md-4
- :button_link: github_repo_runner_step_5.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_2.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner_step_2.rst
deleted file mode 100644
index deae8844c8fc2..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_2.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-:orphan:
-
-******************************************************************
-Step 2: Implement the PyTorch Lightning GithubRepoRunner Component
-******************************************************************
-
-The PyTorch Lightning GithubRepoRunner Component subclasses the GithubRepoRunner but tailors the execution experience to PyTorch Lightning.
-
-As a matter of fact, this component adds two primary tailored features for PyTorch Lightning users:
-
-* It injects dynamically a custom callback ``TensorboardServerLauncher`` in the PyTorch Lightning Trainer to start a tensorboard server so it can be exposed in Lightning App UI.
-
-* Once the script has run, the ``on_after_run`` hook of the :class:`~lightning.app.components.python.tracer.TracerPythonScript` is invoked with the script globals, meaning we can collect anything we need. In particular, we are reloading the best model, torch scripting it, and storing its path in the state along side the best metric score.
-
-Let's dive in on how to develop the component with the following code:
-
-.. literalinclude:: ./app.py
- :lines: 75-136
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the GithubRepoRunner Component
- :description: Clone and execute script from a GitHub Repo.
- :col_css: col-md-4
- :button_link: github_repo_runner_step_1.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 3: Implement the Flow to manage user requests
- :description: Dynamically create GithubRepoRunner
- :col_css: col-md-4
- :button_link: github_repo_runner_step_3.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 4: Implement the UI with StreamLit
- :description: Several pages application
- :col_css: col-md-4
- :button_link: github_repo_runner_step_4.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 5: Put it all together
- :description:
- :col_css: col-md-4
- :button_link: github_repo_runner_step_5.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_3.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner_step_3.rst
deleted file mode 100644
index 44cf7dd5a6523..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_3.rst
+++ /dev/null
@@ -1,62 +0,0 @@
-:orphan:
-
-**************************************************
-Step 3: Implement the Flow to manage user requests
-**************************************************
-
-In step 1 and 2, we have implemented the ``GithubRepoRunner`` and ``PyTorchLightningGithubRepoRunner`` components.
-
-Now, we are going to develop a component to dynamically handle user requests.
-Let's dive in on how to develop the component with the following code:
-
-.. literalinclude:: ./app.py
- :lines: 142-190
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Step 1: Implement the GithubRepoRunner Component
- :description: Clone and execute script from a GitHub Repo.
- :col_css: col-md-4
- :button_link: github_repo_runner_step_1.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 2: Implement the PyTorch Lightning GithubRepoRunner Component
- :description: Automate PyTorch Lightning execution
- :col_css: col-md-4
- :button_link: github_repo_runner_step_2.html
- :height: 180
- :tag: Advanced
-
-.. displayitem::
- :header: Step 4: Implement the UI with StreamLit
- :description: Several pages application
- :col_css: col-md-4
- :button_link: github_repo_runner_step_4.html
- :height: 180
- :tag: Intermediate
-
-
-.. displayitem::
- :header: Step 5: Put it all together
- :description:
- :col_css: col-md-4
- :button_link: github_repo_runner_step_5.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_4.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner_step_4.rst
deleted file mode 100644
index 16893aafee183..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_4.rst
+++ /dev/null
@@ -1,86 +0,0 @@
-:orphan:
-
-***************************************
-Step 4: Implement the UI with StreamLit
-***************************************
-
-In step 3, we have implemented a Flow which dynamically creates a Work when a new request is added to the requests list.
-
-From the UI, we create 3 pages with `StreamLit `_:
-
-* **Page 1**: Create a form with add a new request to the Flow state **requests**.
-
-* **Page 2**: Iterate through all the requests and display the associated information.
-
-* **Page 3**: Display the entire App State.
-
-
-Render All Pages
-^^^^^^^^^^^^^^^^
-
-.. literalinclude:: ./app.py
- :lines: 274-284
-
-**Page 1**
-
-.. literalinclude:: ./app.py
- :lines: 193-241
- :emphasize-lines: 43
-
-**Page 2**
-
-.. literalinclude:: ./app.py
- :lines: 244-264
-
-**Page 3**
-
-.. literalinclude:: ./app.py
- :lines: 267-271
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: 1. Implement the GithubRepoRunner Component
- :description: Clone and execute script from a GitHub Repo.
- :col_css: col-md-4
- :button_link: github_repo_runner_step_1.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: 2. Implement the PyTorch Lightning GithubRepoRunner Component
- :description: Automate PyTorch Lightning execution
- :col_css: col-md-4
- :button_link: github_repo_runner_step_2.html
- :height: 180
- :tag: Advanced
-
-.. displayitem::
- :header: 3. Implement the Flow to manage user requests
- :description: Dynamically create GithubRepoRunner
- :col_css: col-md-4
- :button_link: github_repo_runner_step_3.html
- :height: 180
- :tag: Intermediate
-
-.. displayitem::
- :header: Step 5: Put it all together
- :description:
- :col_css: col-md-4
- :button_link: github_repo_runner_step_5.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_5.rst b/docs/source-app/examples/github_repo_runner/github_repo_runner_step_5.rst
deleted file mode 100644
index 9b5b469d5b793..0000000000000
--- a/docs/source-app/examples/github_repo_runner/github_repo_runner_step_5.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-:orphan:
-
-***************************
-Step 5: Put it all together
-***************************
-
-Let's dive in on how to develop the component with the following code:
-
-.. literalinclude:: ./app.py
- :lines: 287-
-
-Run the application
-^^^^^^^^^^^^^^^^^^^
-
-Clone the Lightning repo and run the following command:
-
-.. code-block:: bash
-
- lightning run app docs/source/examples/github_repo_runner/app.py
-
-Add ``--cloud`` to run this application in the cloud.
-
-.. code-block:: bash
-
- lightning run app docs/source/examples/github_repo_runner/app.py --cloud
-
-----
-
-**********************
-More hands-on examples
-**********************
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Develop a DAG
- :description: Create a dag pipeline
- :col_css: col-md-4
- :button_link: ../dag/dag.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a File Server
- :description: Train multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../file_server/file_server.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a HPO Sweeper
- :description: Train multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../hpo/hpo.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a Model Server
- :description: Serve multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../model_server/model_server.html
- :height: 150
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/hands_on_example.rst b/docs/source-app/examples/hands_on_example.rst
deleted file mode 100644
index 57fa1e5ff114a..0000000000000
--- a/docs/source-app/examples/hands_on_example.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-:orphan:
-
-#################
-Hands-on Examples
-#################
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Build a DAG
- :description: Learn how to orchestrate workflows
- :col_css: col-md-6
- :button_link: dag/dag.html
- :height: 180
-
-.. displayitem::
- :header: Build a File Server
- :description: Learn how to upload and download files
- :col_css: col-md-6
- :button_link: file_server/file_server.html
- :height: 180
-
-.. displayitem::
- :header: Build a Github Repo Script Runner
- :description: Learn how to configure dynamic execution from the UI
- :col_css: col-md-6
- :button_link: github_repo_runner/github_repo_runner.html
- :height: 180
-
-.. displayitem::
- :header: Build a HPO Sweeper
- :description: Learn how to scale your training
- :col_css: col-md-6
- :button_link: hpo/hpo.html
- :height: 180
-
-.. displayitem::
- :header: Build a Model Server
- :description: Learn how to server your models
- :col_css: col-md-6
- :button_link: model_server_app_content.html
- :height: 180
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/index.rst b/docs/source-app/examples/index.rst
deleted file mode 100644
index bb7e645da446e..0000000000000
--- a/docs/source-app/examples/index.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-########
-Examples
-########
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a DAG workflow
- :description: Develop sequential, non-reactive workflows
- :col_css: col-md-4
- :button_link: dag/dag.html
- :height: 150
-
-.. displayitem::
- :header: Develop a File Server
- :description: Develop a file server
- :col_css: col-md-4
- :button_link: file_server/file_server.html
- :height: 150
-
-.. displayitem::
- :header: Develop a Github Repo Script Runner
- :description: Build an app to run a Github repo
- :col_css: col-md-4
- :button_link: github_repo_runner/github_repo_runner.html
- :height: 150
-
-.. displayitem::
- :header: Deploy a model
- :description: Learn how to deploy a model
- :col_css: col-md-4
- :button_link: model_server_app/model_server_app.html
- :height: 150
diff --git a/docs/source-app/examples/model_server_app/app.py b/docs/source-app/examples/model_server_app/app.py
deleted file mode 100644
index 9985014b11912..0000000000000
--- a/docs/source-app/examples/model_server_app/app.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from locust_component import Locust
-from model_server import MLServer
-from train import TrainModel
-
-from lightning import LightningApp, LightningFlow
-
-
-class TrainAndServe(LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_model = TrainModel()
- self.model_server = MLServer(
- name="mnist-svm",
- implementation="mlserver_sklearn.SKLearnModel",
- workers=8,
- )
- self.performance_tester = Locust(num_users=100)
-
- def run(self):
- self.train_model.run()
- self.model_server.run(self.train_model.best_model_path)
- if self.model_server.alive():
- # The performance tester needs the model server to be up
- # and running to be started, so the URL is added in the UI.
- self.performance_tester.run(self.model_server.url)
-
- def configure_layout(self):
- return [
- {"name": "Server", "content": self.model_server.url + "/docs"},
- {"name": "Server Testing", "content": self.performance_tester},
- ]
-
-
-app = LightningApp(TrainAndServe())
diff --git a/docs/source-app/examples/model_server_app/load_testing.rst b/docs/source-app/examples/model_server_app/load_testing.rst
deleted file mode 100644
index 9ea347bd0dd44..0000000000000
--- a/docs/source-app/examples/model_server_app/load_testing.rst
+++ /dev/null
@@ -1,57 +0,0 @@
-:orphan:
-
-***********************************
-3. Build the Load Testing Component
-***********************************
-
-Now, we are going to create a component to test the performance of your model server.
-
-We are going to use a python performance testing tool called `Locust
`_.
-
-.. literalinclude:: ./locust_component.py
-
-
-Finally, once the component is done, we need to create a ``locustfile.py`` file which defines the format of the request to send to your model server.
-
-The endpoint to hit has the following format: ``/v2/models/{MODEL_NAME}/versions/{VERSION}/infer``.
-
-.. literalinclude:: ./locustfile.py
-
-
-----
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1. Build a Train Component
- :description: Train a model and store its checkpoints with SKlearn
- :col_css: col-md-4
- :button_link: train.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 2. Build a Model Server Component
- :description: Use MLServer to server your models
- :col_css: col-md-4
- :button_link: model_server.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 4. Putting everything together.
- :description: Ensemble the components together and run the app
- :col_css: col-md-4
- :button_link: putting_everything_together.html
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/model_server_app/locust_component.py b/docs/source-app/examples/model_server_app/locust_component.py
deleted file mode 100644
index 432336adf83b3..0000000000000
--- a/docs/source-app/examples/model_server_app/locust_component.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import os
-import subprocess
-
-from lightning import BuildConfig, LightningWork
-
-
-class Locust(LightningWork):
- def __init__(self, num_users: int = 100):
- """This component checks the performance of a server. The server url is passed to its run method.
-
- Arguments:
- num_users: Number of users emulated by Locust
-
- """
- # Note: Using the default port 8089 of Locust.
- super().__init__(
- port=8089,
- parallel=True,
- cloud_build_config=BuildConfig(requirements=["locust"]),
- )
- self.num_users = num_users
-
- def run(self, load_tested_url: str):
- # 1: Create the locust command line.
- cmd = " ".join(
- [
- "locust",
- "--master-host",
- str(self.host),
- "--master-port",
- str(self.port),
- "--host",
- str(load_tested_url),
- "-u",
- str(self.num_users),
- ]
- )
- # 2: Create another process with locust
- process = subprocess.Popen(cmd, cwd=os.path.dirname(__file__), shell=True)
-
- # 3: Wait for the process to finish. As locust is a server,
- # this waits infinitely or if killed.
- process.wait()
diff --git a/docs/source-app/examples/model_server_app/locustfile.py b/docs/source-app/examples/model_server_app/locustfile.py
deleted file mode 100644
index 198d6de6cb553..0000000000000
--- a/docs/source-app/examples/model_server_app/locustfile.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from locust import FastHttpUser, task
-from sklearn import datasets
-from sklearn.model_selection import train_test_split
-
-
-class HelloWorldUser(FastHttpUser):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._prepare_inference_request()
-
- @task
- def predict(self):
- self.client.post(
- "/v2/models/mnist-svm/versions/v0.0.1/infer",
- json=self.inference_request,
- )
-
- def _prepare_inference_request(self):
- # The digits dataset
- digits = datasets.load_digits()
-
- # To apply a classifier on this data,
- # we need to flatten the image, to
- # turn the data in a (samples, feature) matrix:
- n_samples = len(digits.images)
- data = digits.images.reshape((n_samples, -1))
-
- # Split data into train and test subsets
- _, X_test, _, _ = train_test_split(data, digits.target, test_size=0.5, shuffle=False)
-
- x_0 = X_test[0:1]
- self.inference_request = {
- "inputs": [
- {
- "name": "predict",
- "shape": x_0.shape,
- "datatype": "FP32",
- "data": x_0.tolist(),
- }
- ]
- }
diff --git a/docs/source-app/examples/model_server_app/model_server.py b/docs/source-app/examples/model_server_app/model_server.py
deleted file mode 100644
index 10ad770b012cc..0000000000000
--- a/docs/source-app/examples/model_server_app/model_server.py
+++ /dev/null
@@ -1,90 +0,0 @@
-import json
-import subprocess
-
-from lightning import BuildConfig, LightningWork
-from lightning.app.storage.path import Path
-
-# ML_SERVER_URL = https://github.com/SeldonIO/MLServer
-
-
-class MLServer(LightningWork):
- """This components uses SeldonIO MLServer library.
-
- The model endpoint: /v2/models/{MODEL_NAME}/versions/{VERSION}/infer.
-
- Arguments:
- name: The name of the model for the endpoint.
- implementation: The model loader class.
- Example: "mlserver_sklearn.SKLearnModel".
- Learn more here: $ML_SERVER_URL/tree/master/runtimes
- workers: Number of server worker.
-
- """
-
- def __init__(
- self,
- name: str,
- implementation: str,
- workers: int = 1,
- **kwargs,
- ):
- super().__init__(
- parallel=True,
- cloud_build_config=BuildConfig(
- requirements=["mlserver", "mlserver-sklearn"],
- ),
- **kwargs,
- )
- # 1: Collect the config's.
- self.settings = {
- "debug": True,
- "parallel_workers": workers,
- }
- self.model_settings = {
- "name": name,
- "implementation": implementation,
- }
- # 2: Keep track of latest version
- self.version = 1
-
- def run(self, model_path: Path):
- """The model is downloaded when the run method is invoked.
-
- Arguments:
- model_path: The path to the trained model.
-
- """
- # 1: Use the host and port at runtime so it works in the cloud.
- # $ML_SERVER_URL/blob/master/mlserver/settings.py#L50
- if self.version == 1:
- # TODO: Reload the next version model of the model.
-
- self.settings.update({"host": self.host, "http_port": self.port})
-
- with open("settings.json", "w") as f:
- json.dump(self.settings, f)
-
- # 2. Store the model-settings
- # $ML_SERVER_URL/blob/master/mlserver/settings.py#L120
- self.model_settings["parameters"] = {
- "version": f"v0.0.{self.version}",
- "uri": str(model_path.absolute()),
- }
- with open("model-settings.json", "w") as f:
- json.dump(self.model_settings, f)
-
- # 3. Launch the Model Server
- subprocess.Popen("mlserver start .", shell=True)
-
- # 4. Increment the version for the next time run is called.
- self.version += 1
-
- else:
- # TODO: Load the next model and unload the previous one.
- pass
-
- def alive(self):
- # Current hack, when the url is available,
- # the server is up and running.
- # This would be cleaned out and automated.
- return self.url != ""
diff --git a/docs/source-app/examples/model_server_app/model_server.rst b/docs/source-app/examples/model_server_app/model_server.rst
deleted file mode 100644
index 283dc97bc99e3..0000000000000
--- a/docs/source-app/examples/model_server_app/model_server.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-:orphan:
-
-*************************************
-2. Develop the Model Server Component
-*************************************
-
-In the code below, we use `MLServer `_ which aims to provide an easy way to start serving your machine learning models through a REST and gRPC interface,
-fully compliant with KFServing's V2 Dataplane spec.
-
-.. literalinclude:: ./model_server.py
-
-----
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1. Develop a Train Component
- :description: Train a model and store its checkpoints with SKlearn
- :col_css: col-md-4
- :button_link: train.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 3. Develop a Load Testing Component
- :description: Use Locust to test your model servers
- :col_css: col-md-4
- :button_link: load_testing.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 4. Putting everything together.
- :description: Ensemble the Components together and run the App
- :col_css: col-md-4
- :button_link: putting_everything_together.html
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/model_server_app/model_server_app.rst b/docs/source-app/examples/model_server_app/model_server_app.rst
deleted file mode 100644
index 933c89d035b00..0000000000000
--- a/docs/source-app/examples/model_server_app/model_server_app.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-.. _model_server_example:
-
-######################
-Develop a Model Server
-######################
-
-**Audience:** Users who want to serve their trained models.
-
-**Prerequisite**: Reach :ref:`level 16+ `.
-
-----
-
-.. include:: model_server_app_content.rst
diff --git a/docs/source-app/examples/model_server_app/model_server_app_content.rst b/docs/source-app/examples/model_server_app/model_server_app_content.rst
deleted file mode 100644
index 0a9280c53c2b1..0000000000000
--- a/docs/source-app/examples/model_server_app/model_server_app_content.rst
+++ /dev/null
@@ -1,84 +0,0 @@
-
-*********
-Objective
-*********
-
-Create a simple application that trains and serves a `Sklearn `_ machine learning model with `MLServer from SeldonIO `_
-
-----
-
-*****************
-Final Application
-*****************
-
-Here is a gif of the final application built in this example.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/ml_server_2.gif
-
-----
-
-*************
-System Design
-*************
-
-In order to create such application, we need to build several components:
-
-* A Model Train Component that trains a model and provides its trained weights
-
-* A Model Server Component that serves as an API endpoint for the model generated by the **Model Train Component**.
-
-* A Load Testing Component that tests the model server works as expected. This could be used to CI/CD the performance of newly generated models (left to the users).
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/model_server_app_2.png
-
-Let's dive into the tutorial.
-
-----
-
-********
-Tutorial
-********
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1. Build a Train Component
- :description: Train a model and store its checkpoints with SKlearn
- :col_css: col-md-4
- :button_link: train.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 2. Build a Model Server Component
- :description: Use MLServer to server your models
- :col_css: col-md-4
- :button_link: model_server.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 3. Build a Load Testing Component
- :description: Use Locust to test your model servers
- :col_css: col-md-4
- :button_link: load_testing.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 4. Putting everything together.
- :description: Ensemble the components together and run the app
- :col_css: col-md-4
- :button_link: putting_everything_together.html
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/model_server_app/putting_everything_together.rst b/docs/source-app/examples/model_server_app/putting_everything_together.rst
deleted file mode 100644
index 48162a911f1a0..0000000000000
--- a/docs/source-app/examples/model_server_app/putting_everything_together.rst
+++ /dev/null
@@ -1,80 +0,0 @@
-:orphan:
-
-******************************
-4. Putting everything together
-******************************
-
-In the code below, we put together the **TrainWork**, the **MLServer** and the **Locust** components in an ``app.py`` file.
-
-.. literalinclude:: ./app.py
-
-
-***********
-Run the App
-***********
-
-To run the app, simply open a terminal and execute this command:
-
-.. code-block:: bash
-
- lightning run app docs/source/examples/model_deploy_app/app.py
-
-Here is a gif of the UI.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/ml_server_2.gif
-
-.. raw:: html
-
-
-
-Congrats, you have finished the **Build a Model Server** example !
-
-----
-
-******************
-Find more examples
-******************
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Develop a DAG
- :description: Develop a DAG pipeline
- :col_css: col-md-4
- :button_link: ../dag/dag.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a File Server
- :description: Train multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../file_server/file_server.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a Github Repo Script Runner
- :description: Run code from the internet in the cloud
- :col_css: col-md-4
- :button_link: ../github_repo_runner/github_repo_runner.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Develop a HPO Sweeper
- :description: Train multiple models with different parameters
- :col_css: col-md-4
- :button_link: ../hpo/hpo.html
- :height: 150
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/model_server_app/train.py b/docs/source-app/examples/model_server_app/train.py
deleted file mode 100644
index 457cc03cda69d..0000000000000
--- a/docs/source-app/examples/model_server_app/train.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import joblib
-from sklearn import datasets, svm
-from sklearn.model_selection import train_test_split
-
-from lightning import LightningWork
-from lightning.app.storage.path import Path
-
-
-class TrainModel(LightningWork):
- """This component trains a Sklearn SVC model on digits dataset."""
-
- def __init__(self):
- super().__init__()
- # 1: Add element to the state.
- self.best_model_path = None
-
- def run(self):
- # 2: Load the Digits
- digits = datasets.load_digits()
-
- # 3: To apply a classifier on this data,
- # we need to flatten the image, to
- # turn the data in a (samples, feature) matrix:
- n_samples = len(digits.images)
- data = digits.images.reshape((n_samples, -1))
-
- # 4: Create a classifier: a support vector classifier
- classifier = svm.SVC(gamma=0.001)
-
- # 5: Split data into train and test subsets
- X_train, _, y_train, _ = train_test_split(data, digits.target, test_size=0.5, shuffle=False)
-
- # 6: We learn the digits on the first half of the digits
- classifier.fit(X_train, y_train)
-
- # 7: Save the Sklearn model with `joblib`.
- model_file_name = "mnist-svm.joblib"
- joblib.dump(classifier, model_file_name)
-
- # 8: Keep a reference the the generated model.
- self.best_model_path = Path("mnist-svm.joblib")
diff --git a/docs/source-app/examples/model_server_app/train.rst b/docs/source-app/examples/model_server_app/train.rst
deleted file mode 100644
index fdb6f6a93a0a7..0000000000000
--- a/docs/source-app/examples/model_server_app/train.rst
+++ /dev/null
@@ -1,49 +0,0 @@
-:orphan:
-
-****************************
-1. Build the Train Component
-****************************
-
-In the code below, we create a work which trains a simple `SVC `_ model on the digits dataset (classification).
-
-Once the model is trained, it is saved and a reference :class:`~lightning.app.storage.path.Path` with ``best_model_path`` state attribute.
-
-.. literalinclude:: ./train.py
-
-----
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 2. Build a Model Server Component
- :description: Use MLServer to server your models
- :col_css: col-md-4
- :button_link: model_server.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 3. Build a Load Testing Component
- :description: Use Locust to test your model servers
- :col_css: col-md-4
- :button_link: load_testing.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: 4. Putting everything together.
- :description: Ensemble the components together and run the app
- :col_css: col-md-4
- :button_link: putting_everything_together.html
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/examples/research_demo_app.rst b/docs/source-app/examples/research_demo_app.rst
deleted file mode 100644
index 90276f9b95f97..0000000000000
--- a/docs/source-app/examples/research_demo_app.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-#########################
-Build a Research Demo App
-#########################
diff --git a/docs/source-app/get_started/add_an_interactive_demo.rst b/docs/source-app/get_started/add_an_interactive_demo.rst
deleted file mode 100644
index 0ad0e6b9c8771..0000000000000
--- a/docs/source-app/get_started/add_an_interactive_demo.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-:orphan:
-
-#######################
-Add an Interactive Demo
-#######################
-
-.. _add_an_interactive_Demo:
-
-**Required background:** Basic Python familiarity and complete the install guide.
-
-**Goal:** We'll walk you through the 4 key steps to run a Lightning App that trains and demos a model.
-
-----
-
-.. include:: go_beyond_training_content.rst
diff --git a/docs/source-app/get_started/build_model.rst b/docs/source-app/get_started/build_model.rst
deleted file mode 100644
index 300b220ee61ce..0000000000000
--- a/docs/source-app/get_started/build_model.rst
+++ /dev/null
@@ -1,73 +0,0 @@
-:orphan:
-
-.. _build_model:
-
-#######################
-Build and Train a Model
-#######################
-
-**Required background:** Basic Python familiarity and complete the guide.
-
-**Goal:** We'll walk you through the creation of a model using PyTorch Lightning.
-
-----
-
-*********************************
-A simple PyTorch Lightning script
-*********************************
-
-Let's assume you already have a folder with those two files.
-
-.. code-block:: bash
-
- pl_project/
- train.py # your own script to train your models
- requirements.txt # your python requirements.
-
-If you don't, simply create a ``pl_project`` folder with those two files and add the following `PyTorch Lightning `_ code in the ``train.py`` file. This code trains a simple ``AutoEncoder`` on `MNIST Dataset `_.
-
-.. literalinclude:: ../code_samples/convert_pl_to_app/train.py
-
-Add the following to the ``requirements.txt`` file.
-
-.. literalinclude:: ../code_samples/convert_pl_to_app/requirements.txt
-
-Simply run the following commands in your terminal to install the requirements and train the model.
-
-.. code-block:: bash
-
- pip install -r requirements.txt
- python train.py
-
-Get through `PyTorch Lightning Introduction `_ to learn more.
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-
-.. displayitem::
- :header: Evolve a Model into an ML System
- :description: Develop an App to train a model in the cloud
- :col_css: col-md-6
- :button_link: training_with_apps.html
- :height: 180
-
-.. displayitem::
- :header: Start from a Template ML System
- :description: Learn about Apps, from a template.
- :col_css: col-md-6
- :button_link: go_beyond_training.html
- :height: 180
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/get_started/go_beyond_training.rst b/docs/source-app/get_started/go_beyond_training.rst
deleted file mode 100644
index f45e7f9f0ab62..0000000000000
--- a/docs/source-app/get_started/go_beyond_training.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-:orphan:
-
-################################
-Start from an ML system template
-################################
-
-.. _go_beyond_training:
-
-**Required background:** Basic Python familiarity and complete the install guide.
-
-**Goal:** We'll walk you through the 4 key steps to run a Lightning App that trains and demos a model.
-
-
-.. include:: go_beyond_training_content.rst
diff --git a/docs/source-app/get_started/go_beyond_training_content.rst b/docs/source-app/get_started/go_beyond_training_content.rst
deleted file mode 100644
index 5bc632b08ddd0..0000000000000
--- a/docs/source-app/get_started/go_beyond_training_content.rst
+++ /dev/null
@@ -1,405 +0,0 @@
-************************************************
-The *Train & Demo PyTorch Lightning* Application
-************************************************
-
-Find the *Train & Demo PyTorch Lightning* application in the `Lightning.ai App Gallery `_.
-
-Here is a recording of this App running locally and in the cloud with the same behavior.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_app_experience_cut.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_app_experience_cut.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-In the steps below, we are going to show you how to build this application.
-
-Here are `the entire App's code `_ and `its commented components. `_
-
-----
-
-*************************
-Step 1: Install Lightning
-*************************
-
-If you are using a virtual env, don't forget to activate it before running commands.
-You must do so in every new shell.
-
-.. tip:: We highly recommend using virtual environments.
-
-.. code:: bash
-
- pip install lightning
-
-----
-
-****************************************
-Step 2: Install the *Train and Demo* App
-****************************************
-The first Lightning App we'll explore is an App to train and demo a machine learning model.
-
-..
- [|qs_code|], [|qs_live_app|].
-
- .. |qs_live_app| raw:: html
-
- live app
-
- .. |qs_code| raw:: html
-
- code
-
-
-Install this App by typing:
-
-.. code-block:: bash
-
- lightning_app install app lightning/quick-start
-
-Verify the App was successfully installed:
-
-.. code-block:: bash
-
- cd lightning-quick-start
-
-----
-
-***************************
-Step 3: Run the App locally
-***************************
-
-Run the app locally with the ``run`` command 🤯
-
-.. code:: bash
-
- lightning_app run app app.py
-
-----
-
-********************************
-Step 4: Run the App in the cloud
-********************************
-
-Add the ``--cloud`` argument to run on the `Lightning.AI cloud `_. 🤯🤯🤯
-
-.. code:: bash
-
- lightning_app run app app.py --cloud
-
-..
- Your app should look like this one (|qs_live_app|)
-
-----
-
-*******************
-Understand the code
-*******************
-The App that we just launched trained a PyTorch Lightning model (although any framework works), then added an interactive demo.
-
-This is the App's code:
-
-.. code:: python
-
- # lightning-quick-start/app.py
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute())
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
- app = L.LightningApp(TrainDeploy())
-
-Let's break down the code section by section to understand what it is doing.
-
-----
-
-1: Define root component
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-A Lightning App provides a cohesive product experience for a set of unrelated components.
-
-The top-level component (Root) must subclass ``L.LightningFlow``
-
-
-.. code:: python
- :emphasize-lines: 6
-
- # lightning-quick-start/app.py
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu-small"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
- app = L.LightningApp(TrainDeploy())
-
-----
-
-2: Define components
-^^^^^^^^^^^^^^^^^^^^
-In the __init__ method, we define the components that make up the App. In this case, we have 2 components,
-a component to execute any PyTorch Lightning script (model training) and a second component to
-start a Gradio server for demo purposes.
-
-.. code:: python
- :emphasize-lines: 9, 14
-
- # lightning-quick-start/app.py
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu-small"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
- app = L.LightningApp(TrainDeploy())
-
-----
-
-3: Define how components Flow
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Every component has a ``run`` method. The run method defines the 🌊 Flow 🌊 of how components interact together.
-
-In this case, we train a model (until completion). When it's done AND there exists a checkpoint, we launch a
-demo server:
-
-.. code:: python
- :emphasize-lines: 18, 21, 22
-
- # lightning-quick-start/app.py
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu-small"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
- app = L.LightningApp(TrainDeploy())
-
-.. note:: If you've used other ML systems you'll be pleasantly surprised to not find decorators or YAML files.
-
-----
-
-4: Connect web user interfaces
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-All our favorite tools normally have their own web user interfaces (UI).
-
-Implement the ``configure_layout`` method to connect them together:
-
-.. code:: python
- :emphasize-lines: 24-27
-
- # lightning-quick-start/app.py
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu-small"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
- app = L.LightningApp(TrainDeploy())
-
-----
-
-5: Init the ``app`` object
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-Initialize an ``app`` object with the ``TrainDeploy`` component (this won't run the App yet):
-
-.. code:: python
- :emphasize-lines: 29
-
- # lightning-quick-start/app.py
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu-small"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
- app = L.LightningApp(TrainDeploy())
-
-----
-
-******************************
-What components are supported?
-******************************
-Any component can work with Lightning AI!
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/Lightning.gif
- :alt: What is Lightning gif.
- :width: 100 %
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add components to your App
- :description: Expand your App by adding components.
- :col_css: col-md-4
- :button_link: ../workflows/extend_app.html
- :height: 180
-
-.. displayitem::
- :header: Build a component
- :description: Learn to build your own component.
- :col_css: col-md-4
- :button_link: ../workflows/build_lightning_component/index.html
- :height: 180
-
-.. displayitem::
- :header: Explore more Apps
- :description: Explore more apps for inspiration.
- :col_css: col-md-4
- :button_link: https://lightning.ai/apps
- :height: 180
-
-.. displayitem::
- :header: Under the hood
- :description: Explore how it works under the hood.
- :col_css: col-md-4
- :button_link: ../core_api/lightning_app/index.html
- :height: 180
-
-.. displayitem::
- :header: Run on your private cloud
- :description: Run Lightning Apps on your private VPC or on-prem.
- :button_link: ../workflows/run_on_private_cloud.html
- :col_css: col-md-4
- :height: 180
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/get_started/jumpstart_from_app_gallery.rst b/docs/source-app/get_started/jumpstart_from_app_gallery.rst
deleted file mode 100644
index c6a49674c9aa9..0000000000000
--- a/docs/source-app/get_started/jumpstart_from_app_gallery.rst
+++ /dev/null
@@ -1,123 +0,0 @@
-:orphan:
-
-#####################################
-Start from Ready-to-Run Template Apps
-#####################################
-
-.. _jumpstart_from_app_gallery:
-
-Anyone can build Apps for their own use cases and promote them on the `App Gallery `_.
-
-In return, you can benefit from the work of others and get started faster by re-using a ready-to-run App close to your own use case.
-
-
-*************
-User Workflow
-*************
-
-#. Visit the `App Gallery `_ and look for an App close to your own use case.
-
- .. raw:: html
-
-
-
-#. If **Launch** is available, it means the App is live and ready to be used! Take it for a spin.
-
- .. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/launch_button.png
- :alt: Launch Button on lightning.ai
- :width: 100 %
-
-#. By clicking **Clone & Run**, a copy of the App is added to your account and an instance starts running.
-
-
- .. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/clone_and_run.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/clone_and_run.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-#. If you found an App that matches what you need, move to **step 5**! Otherwise, go back to **step 1**.
-
- .. raw:: html
-
-
-
-#. Copy the installation command (optionally from the clipboard on the right).
-
- .. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/install_command.png
- :alt: Install command on lightning.ai
- :width: 100 %
-
-#. Copy the command to your local terminal.
-
- .. code-block:: bash
-
- lightning_app install app lightning/hackernews-app
-
-#. Go through the installation steps.
-
- .. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/install_an_app.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/install_an_app.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-#. Run the App locally.
-
- .. code-block:: bash
-
- cd LAI-Hackernews-App
- lightning_app run app app.py
-
- .. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/hackernews.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/hackernews.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-#. Open the code with your favorite IDE, modify it, and run it back in the cloud.
-
- .. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/hackernews_modified.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/hackernews_modified.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add Component made by others to your App
- :description: Add more functionality to your projects
- :col_css: col-md-6
- :button_link: jumpstart_from_component_gallery.html
- :height: 180
-
-.. displayitem::
- :header: Level-up your skills with Lightning Apps
- :description: From Basic to Advanced Skills
- :col_css: col-md-6
- :button_link: ../levels/basic/index.html
- :height: 180
-
-.. raw:: html
-
-
-
-
diff --git a/docs/source-app/get_started/jumpstart_from_component_gallery.rst b/docs/source-app/get_started/jumpstart_from_component_gallery.rst
deleted file mode 100644
index 95f7d570d87d0..0000000000000
--- a/docs/source-app/get_started/jumpstart_from_component_gallery.rst
+++ /dev/null
@@ -1,151 +0,0 @@
-:orphan:
-
-########################################
-Add Component made by others to your App
-########################################
-
-.. _jumpstart_from_component_gallery:
-
-Anyone can build components for their own use case and promote them on the `Component Gallery `_.
-
-In return, you can benefit from the work of others and add new functionalities to your Apps with minimal effort.
-
-
-*************
-User Workflow
-*************
-
-#. Visit the `Component Gallery `_ and look for a Component close to something you want to do.
-
- .. raw:: html
-
-
-
-#. Check out the code for inspiration or simply install the component from PyPi and use it.
-
-----
-
-*************
-Success Story
-*************
-
-The default `Train and Demo Application `_ trains a PyTorch Lightning
-model and then starts a demo with `Gradio `_.
-
-.. code-block:: python
-
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
-
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = PyTorchLightningScript(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
-
- app = L.LightningApp(TrainDeploy())
-
-However, someone who wants to use this Aop (maybe you) found `Lightning HPO `_
-from browsing the `Component Gallery `_ and decided to give it a spin after checking the associated
-`Github Repository `_.
-
-Once ``lightning_hpo`` installed, they improved the default App by easily adding HPO support to their project.
-
-Here is the resulting App. It is almost the same code, but it's way more powerful now!
-
-This is the power of `lightning.ai `_ ecosystem 🔥⚡🔥
-
-.. code-block:: python
-
- import os.path as ops
- import lightning as L
- from quick_start.components import PyTorchLightningScript, ImageServeGradio
- import optuna
- from optuna.distributions import LogUniformDistribution
- from lightning_hpo import Optimizer, BaseObjective
-
-
- class HPOPyTorchLightningScript(PyTorchLightningScript, BaseObjective):
- @staticmethod
- def distributions():
- return {"model.lr": LogUniformDistribution(0.0001, 0.1)}
-
-
- class TrainDeploy(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.train_work = Optimizer(
- script_path=ops.join(ops.dirname(__file__), "./train_script.py"),
- script_args=["--trainer.max_epochs=5"],
- objective_cls=HPOPyTorchLightningScript,
- n_trials=4,
- )
-
- self.serve_work = ImageServeGradio(L.CloudCompute("cpu"))
-
- def run(self):
- # 1. Run the python script that trains the model
- self.train_work.run()
-
- # 2. when a checkpoint is available, deploy
- if self.train_work.best_model_path:
- self.serve_work.run(self.train_work.best_model_path)
-
- def configure_layout(self):
- tab_1 = {"name": "Model training", "content": self.train_work.hi_plot}
- tab_2 = {"name": "Interactive demo", "content": self.serve_work}
- return [tab_1, tab_2]
-
-
- app = L.LightningApp(TrainDeploy())
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Start from Ready-to-Run Template Apps
- :description: Jump-start your projects development
- :col_css: col-md-6
- :button_link: jumpstart_from_app_gallery.html
- :height: 180
-
-.. displayitem::
- :header: Level-up your skills with Lightning Apps
- :description: From Basic to Advanced Skills
- :col_css: col-md-6
- :button_link: ../levels/basic/index.html
- :height: 180
-
-.. raw:: html
-
-
-
-
diff --git a/docs/source-app/get_started/training_with_apps.rst b/docs/source-app/get_started/training_with_apps.rst
deleted file mode 100644
index 46cc851bdd290..0000000000000
--- a/docs/source-app/get_started/training_with_apps.rst
+++ /dev/null
@@ -1,125 +0,0 @@
-:orphan:
-
-################################
-Evolve a model into an ML system
-################################
-
-.. _convert_pl_to_app:
-
-**Required background:** Basic Python familiarity and complete the :ref:`build_model` guide.
-
-**Goal:** We'll walk you through the two key steps to build your first Lightning App from your existing PyTorch Lightning scripts.
-
-
-*******************
-Training and beyond
-*******************
-
-With `PyTorch Lightning `__, we abstracted distributed training and hardware, by organizing PyTorch code.
-With `Lightning Apps `__, we unified the local and cloud experience while abstracting infrastructure.
-
-By using `PyTorch Lightning `__ and `Lightning Apps `__
-together, a completely new world of possibilities emerges.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/pl_to_app_4.png
- :alt: From PyTorch Lightning to Lightning App
- :width: 100 %
-
-----
-
-******************************************
-1. Write an App to run the train.py script
-******************************************
-
-This article continues where the :ref:`build_model` guide finished.
-
-Create an additional file ``app.py`` in the ``pl_project`` folder as follows:
-
-.. code-block:: bash
-
- pl_project/
- app.py
- train.py
- requirements.txt
-
-Inside the ``app.py`` file, add the following code.
-
-.. literalinclude:: ../code_samples/convert_pl_to_app/app.py
-
-This App runs the PyTorch Lightning script contained in the ``train.py`` file using the powerful :class:`~lightning.app.components.python.tracer.TracerPythonScript` component. This is really worth checking out!
-
-----
-
-************************************************
-2. Run the train.py file locally or in the cloud
-************************************************
-
-First, go to the ``pl_folder`` folder from the local terminal and install the requirements.
-
-.. code-block:: bash
-
- cd pl_folder
- pip install -r requirements.txt
-
-To run your app, copy the following command to your local terminal:
-
-.. code-block:: bash
-
- lightning_app run app app.py
-
-Simply add ``--cloud`` to run this application in the cloud with a GPU machine 🤯
-
-.. code-block:: bash
-
- lightning_app run app app.py --cloud
-
-
-Congratulations! Now, you know how to run a `PyTorch Lightning `_ script with Lightning Apps.
-
-Lightning Apps can make your ML system way more powerful, keep reading to learn how.
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Level-up with Lightning Apps
- :description: From Basics to Advanced Skills
- :col_css: col-md-4
- :button_link: ../levels/basic/index.html
- :height: 180
-
-.. displayitem::
- :header: Add an Interactive Demo
- :description: Add a Gradio Demo once the training is finished
- :col_css: col-md-4
- :button_link: add_an_interactive_demo.html
- :height: 180
-
-.. displayitem::
- :header: Add Model Serving
- :description: Serve and load testing with MLServer and Locust
- :col_css: col-md-4
- :button_link: ../examples/model_server_app/model_server_app.html
- :height: 180
-
-.. displayitem::
- :header: Add DAG Orchestration
- :description: Organize your processing, training and metrics collection
- :col_css: col-md-4
- :button_link: ../examples/dag/dag.html
- :height: 180
-
-.. displayitem::
- :header: Add Team Collaboration
- :description: Create an app to run any PyTorch Lightning Script from Github
- :col_css: col-md-4
- :button_link: ../examples/github_repo_runner/github_repo_runner.html
- :height: 180
diff --git a/docs/source-app/get_started/what_app_can_do.rst b/docs/source-app/get_started/what_app_can_do.rst
deleted file mode 100644
index b4033dd40d594..0000000000000
--- a/docs/source-app/get_started/what_app_can_do.rst
+++ /dev/null
@@ -1,187 +0,0 @@
-:orphan:
-
-############################################
-Discover what Lightning Apps can do in 5 min
-############################################
-
-.. _what_app_can_do:
-
-Lightning Apps can be plenty things, and while a picture is worth a thousand words, videos showing you examples should be worth even more.
-
-
-*****************************
-Flashy - Auto ML App (Public)
-*****************************
-
-Train a model on any image or text dataset without writing any code. Flashy uses `React.js
`_ for its frontend.
-
-Find `Flashy
`_ on the App Gallery and the `Flashy codebase. `_ on GitHub.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/flashy.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/flashy.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-.. ----
-
-.. ***************************************
-.. NVIDIA Omniverse Sampling App (Private)
-.. ***************************************
-
-.. Use `Nvidia Sampling Omniverse `_ to generate synthetic samples from 3D meshes and train an object detector on that data.
-
-.. .. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/Omniverse-Sampling.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/Omniverse-Sampling.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-*********************
-Research App (Public)
-*********************
-
-Share your paper ``bundled`` with the arxiv link, poster, live jupyter notebook, interactive demo to try the model, and more!
-
-Find the `Research App `_ on the App Gallery and the `Research App codebase. `_ on GitHub.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/research_app.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/research_app.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-************************************************
-ScratchPad - Notebook Manager for Team (Public)
-************************************************
-
-Run multiple Jupyter Notebooks on cloud CPUs or machines with multiple GPUs.
-
-Find the `ScratchPad App `_ on the App Gallery and the `ScratchPad App codebase `_ on GitHub.
-
-.. note:: ScratchPad is `tested end-to-end `_ on every Lightning App commit with `pytest `_.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/notebook_apps.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/notebook_apps.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-***********************
-InVideo Search (Public)
-***********************
-
-This App lets you find anything you're looking for inside a video. The engine is powered by `Open AI CLIP `_.
-
-Find the `InVideo Search App `_ on the App Gallery and the `InVideo Search App codebase. `_ in GitHub.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/video_search_2.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/video_search_2.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-******************************
-AI-powered HackerNews (Public)
-******************************
-
-Save yourself time, and get Hacker News story recommendations, chosen for you specifically. This Lightning App was designed to illustrate a full end-to-end MLOPs workflow aimed at enterprise recommendation systems.
-
-Find the `AI-powered HackerNews App `_ on the App Gallery and the `AI-powered HackerNews App codebase. `_ on GitHub.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/hackernews_app.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/hackernews_app.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-----
-
-*********************************************************************
-Lightning Apps can turn ML into scalable systems in days — not months
-*********************************************************************
-
-Use the Lightning framework to develop any ML system: train and deploy a model, create an ETL pipeline,
-or spin up a research demo — using the intuitive principles we pioneered with PyTorch Lightning.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/apps_logos_2.png
- :alt: Apps with Logos
- :width: 100 %
-
-Anyone who knows Python can build a Lightning App, even without machine learning experience.
-
-Lightning Apps are:
-
-- cloud agnostic
-- fault-tolerant, distributed, cost optimized
-- production ready
-- local and cloud debuggable
-- highly reactive & interactive
-- connect multiple UIs together
-- built for team collaboration
-- framework agnostic, use your own stack
-- and much more
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_app_experience_cut.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_app_experience_cut.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-
-.. displayitem::
- :header: Build & Train a Model
- :description: Discover PyTorch Lightning and train your first Model.
- :col_css: col-md-4
- :button_link: build_model.html
- :height: 180
-
-.. displayitem::
- :header: Evolve a Model into an ML System
- :description: Develop an App to train a model in the cloud
- :col_css: col-md-4
- :button_link: training_with_apps.html
- :height: 180
-
-.. displayitem::
- :header: Start from an ML system template
- :description: Learn about Apps, from a template.
- :col_css: col-md-4
- :button_link: go_beyond_training.html
- :height: 180
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/app_tree.rst b/docs/source-app/glossary/app_tree.rst
deleted file mode 100644
index 37413f30cfe0f..0000000000000
--- a/docs/source-app/glossary/app_tree.rst
+++ /dev/null
@@ -1,113 +0,0 @@
-:orphan:
-
-.. _app_component_tree:
-
-###################
-App Component Tree
-###################
-
-**Audience:** Users who want to know how components can be composed with each other.
-
-**Level:** Basic
-
-----
-
-**************************************
-What is an Application Component Tree?
-**************************************
-
-Components can be nested to form component trees where the LightningFlows are its branches and LightningWorks are its leaves.
-
-This design enables users to organize and maintain their code with more ease, but more importantly, this helps creating an ecosystem with reusable components.
-
-Here's a basic application with four flows and two works (associated tree structure):
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/tree.gif
- :alt: Basic App Components
- :width: 100 %
-
-
-.. literalinclude:: ../code_samples/quickstart/app_comp.py
-
-A Lightning app runs all flows into a single process. Its flows coordinate the execution of the works each running in their own independent processes.
-
-----
-
-***********************************************
-How do I define my application component tree?
-***********************************************
-
-In order to define your application component tree, you need create a tree of components and attach them to your root flow.
-
-You can attach your components in the **__init__** method of a flow.
-
-.. code-block:: python
-
- import lightning as L
-
-
- class RootFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- # The `Work` component is attached here.
- self.work = Work()
-
- # The `NestedFlow` component is attached here.
- self.nested_flow = NestedFlow()
-
-Once done, simply add the root flow to a Lightning app as follows:
-
-.. code-block:: python
-
- app = L.LightningApp(RootFlow())
-
-----
-
-******************************************
-Is my application component tree static?
-******************************************
-
-No, Lightning supports dynamic flows and works.
-
-You can simply attach your components in the **run** method of a flow using the Python functions **hasattr**, **setattr**, and **getattr**.
-
-.. code-block:: python
-
- class RootFlow(L.LightningFlow):
- def run(self):
-
- if not hasattr(self, "work"):
- # The `Work` component is attached here.
- setattr(self, "work", Work())
- # Run the `Work` component.
- getattr(self, "work").run()
-
- if not hasattr(self, "nested_flow"):
- # The `NestedFlow` component is attached here.
- setattr(self, "nested_flow", NestedFlow())
- # Run the `NestedFlow` component.
- getattr(self, "wonested_flowrk").run()
-
-
-But it is usually more readable to use Lightning built-in :class:`~lightning.app.structures.Dict` or :class:`~lightning.app.structures.List` as follows:
-
-.. code-block:: python
-
- from lightning.app.structures import Dict
-
-
- class RootFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.dict = Dict()
-
- def run(self):
- if "work" not in self.dict:
- # The `Work` component is attached here.
- self.dict["work"] = Work()
- self.dict["work"].run()
-
- if "nested_flow" not in self.dict:
- # The `NestedFlow` component is attached here.
- self.dict["nested_flow"] = NestedFlow()
- self.dict["nested_flow"].run()
diff --git a/docs/source-app/glossary/build_config/build_config.rst b/docs/source-app/glossary/build_config/build_config.rst
deleted file mode 100644
index 43ba0b02e2bcf..0000000000000
--- a/docs/source-app/glossary/build_config/build_config.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-:orphan:
-
-.. _build_config:
-
-###################
-Build Configuration
-###################
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Basic
- :description: Learn how to manage Python dependencies for an individual LightningWork
- :col_css: col-md-6
- :button_link: build_config_basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Intermediate
- :description: Learn how to run custom build commands for a LightningWork
- :col_css: col-md-6
- :button_link: build_config_intermediate.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Advanced
- :description: Learn how to use a custom Docker image for a LightningWork
- :col_css: col-md-6
- :button_link: build_config_advanced.html
- :height: 150
- :tag: advanced
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/build_config/build_config_advanced.rst b/docs/source-app/glossary/build_config/build_config_advanced.rst
deleted file mode 100644
index bc6f5a2062d12..0000000000000
--- a/docs/source-app/glossary/build_config/build_config_advanced.rst
+++ /dev/null
@@ -1,63 +0,0 @@
-:orphan:
-
-##############################
-Build Configuration (Advanced)
-##############################
-
-**Audience:** Users who want full control over the docker image that is being installed in the cloud.
-
-**Level:** Advanced
-
-Advanced users who need full control over the environment a LightningWork runs in can specify a custom docker image that will be deployed in the cloud.
-
-
-----
-
-******************
-Use a docker image
-******************
-
-Create a :class:`~lightning.app.utilities.packaging.build_config.BuildConfig` and provide a **publicly accessible** link to where the image is hosted:
-
-.. code-block:: python
-
- from lightning.app import LightningWork, BuildConfig
-
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__()
-
- # Using a publicly hosted docker image:
- self.cloud_build_config = BuildConfig(
- # This is one of the base images Lightning uses by default
- image="ghcr.io/gridai/base-images:v1.8-gpu"
- )
-
- # Can also be combined with extra requirements
- self.cloud_build_config = BuildConfig(image="...", requirements=["torchmetrics"])
-
-
-.. warning::
- Many public hosters like DockerHub apply rate limits for public images. We recommend to pull images from your own registry.
- For example, you can set up a
- `docker registry on GitHub `_.
-
-
-.. note::
- - The build config only applies when running in the cloud and gets ignored otherwise. A local build config is currently not supported.
- - Images from private registries are currently not supported.
-
-.. note::
- Custom docker images must have python installed. We'll use `virtualenv` from this system python to create a virtual environment.
- We'll also configure the `virtualenv` to use the packages installed under system's python so your packages are not lost
-
-----
-
-
-*********************
-Provide a docker file
-*********************
-
-.. note::
- Not yet supported. Coming soon.
diff --git a/docs/source-app/glossary/build_config/build_config_basic.rst b/docs/source-app/glossary/build_config/build_config_basic.rst
deleted file mode 100644
index 538cf868c5642..0000000000000
--- a/docs/source-app/glossary/build_config/build_config_basic.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-:orphan:
-
-###########################
-Build Configuration (Basic)
-###########################
-
-**Audience:** Users who need to install Python packages for an individual LightningWork.
-
-**Level:** Basic
-
-----
-
-***********************************
-List dependencies in separate files
-***********************************
-
-If you are building an app with multiple LightningWorks that have different or even conflicting requirements, split your dependencies into individual files
-for more granular control.
-
-.. code-block:: bash
-
- ├── app.py
- ├── requirements.txt # Global requirements for the entire app
- └── works
- ├── serve
- │ ├── requirements.txt # Requirements specific to the 'serve' work
- │ └── serve.py # Source file for the LightningWork
- └── train
- ├── requirements.txt # Requirements specific to the 'train' work
- └── train.py # Source file for the LightningWork
-
-The requirements.txt file must be located in the same directory as the source file of the LightningWork.
-When the LightningWork starts up, it will pick up the requirements file if present and install all listed packages.
-
-.. note::
- This only applies when running in the cloud. The requirements.txt files get ignored when running locally.
-
-----
-
-***********************************
-Define the requirements in the code
-***********************************
-
-Instead of listing the requirements in a file, you can also pass them to the LightningWork at runtime using the
-:class:`~lightning.app.utilities.packaging.build_config.BuildConfig`:
-
-.. code-block:: python
- :emphasize-lines: 7
-
- from lightning.app import LightningWork, BuildConfig
-
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.cloud_build_config = BuildConfig(requirements=["torch>=1.8", "torchmetrics"])
-
-.. note::
- The build config only applies when running in the cloud and gets ignored otherwise. A local build config is currently not supported.
-
-.. warning::
- Custom base images are not supported with the default CPU cloud compute. For example:
-
- .. code-block:: py
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__(cloud_build_config=BuildConfig(image="my-custom-image")) # no cloud compute, for example default work
diff --git a/docs/source-app/glossary/build_config/build_config_intermediate.rst b/docs/source-app/glossary/build_config/build_config_intermediate.rst
deleted file mode 100644
index 600b8e85f36ef..0000000000000
--- a/docs/source-app/glossary/build_config/build_config_intermediate.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-:orphan:
-
-##################################
-Build Configuration (Intermediate)
-##################################
-
-**Audience:** Users who need to execute commands to configure the machine before a LightningWork runs on it.
-
-**Level:** Intermediate
-
-When a LightningWork machine starts up in the cloud, it uses a lightweight operating system with essential packages pre-installed.
-If you need to install additional system packages or run other configuration steps before your code executes on that machine, it is possible to do so by creating a custom
-:class:`~lightning.app.utilities.packaging.build_config.BuildConfig`:
-
-1. Subclass :class:`~lightning.app.utilities.packaging.build_config.BuildConfig`:
-
- .. code-block:: python
-
- from lightning.app import BuildConfig
-
-
- @dataclass
- class CustomBuildConfig(BuildConfig):
- def build_commands(self):
- return ["sudo apt-get install libsparsehash-dev"]
-
-
-2. Set the build config on the LightningWork:
-
- .. code-block:: python
-
- from lightning.app import LightningWork
-
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__()
-
- # Use the custom build config
- self.cloud_build_config = CustomBuildConfig()
-
- # Can also be combined with extra requirements
- self.cloud_build_config = CustomBuildConfig(requirements=["torchmetrics"])
-
-.. note::
- - When you need to execute commands or install tools that require more privileges than the current user has, you can use ``sudo`` without needing to provide a password, e.g., when installing system packages.
- - The build config only applies when running in the cloud and gets ignored otherwise. A local build config is currently not supported.
-
-.. warning::
- Custom base images are not supported with the default CPU cloud compute. For example:
-
- .. code-block:: py
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__(cloud_build_config=BuildConfig(image="my-custom-image")) # no cloud compute, for example default work
diff --git a/docs/source-app/glossary/command_lines/command_lines.rst b/docs/source-app/glossary/command_lines/command_lines.rst
deleted file mode 100644
index 15d77272c1692..0000000000000
--- a/docs/source-app/glossary/command_lines/command_lines.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-:orphan:
-
-############################
-Command-line Interface (CLI)
-############################
-
-**Audience:** Users looking to create a command line interface (CLI) for their application.
-
-----
-
-**************
-What is a CLI?
-**************
-
-A Command-line Interface (CLI) is an user interface (UI) in a terminal to interact with a specific program.
-
-.. note::
-
- The Lightning guideline to build CLI is `lightning_app ...` or ` ...`.
-
-As an example, Lightning provides a CLI to interact with your Lightning Apps and the `lightning.ai `_ platform as follows:
-
-.. code-block:: bash
-
- main
- ├── fork - Forks an App.
- ├── init - Initializes a Lightning App and/or Component.
- │ ├── app
- │ ├── component
- │ ├── pl-app - Creates an App from your PyTorch Lightning source files.
- │ └── react-ui - Creates a React UI to give a Lightning Component a React.js web UI
- ├── install - Installs a Lightning App and/or Component.
- │ ├── app
- │ └── component
- ├── list - Lists Lightning AI self-managed resources (apps)
- │ └── apps - Lists your Lightning AI Apps.
- ├── login - Logs in to your lightning.ai account.
- ├── logout - Logs out of your lightning.ai account.
- ├── run - Runs a Lightning App locally or on the cloud.
- │ └── app - Runs an App from a file.
- ├── show - Shows given resource.
- │ └── logs - Shows cloud application logs. By default prints logs for all currently available Components.
- ├── stop - Stops your App.
- └── tree - Shows the command tree of your CLI.
-
-Learn more about `Command-line interfaces here `_.
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a Command Line Interface
- :description: Learn how to develop a CLI for your App.
- :col_css: col-md-6
- :button_link: ../../workflows/build_command_line_interface/index_content.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/dag.rst b/docs/source-app/glossary/dag.rst
deleted file mode 100644
index 9578b1e5260a8..0000000000000
--- a/docs/source-app/glossary/dag.rst
+++ /dev/null
@@ -1,46 +0,0 @@
-######################
-Directed Acyclic Graph
-######################
-**Audience:** Users coming from MLOps to Lightning Apps, looking for more flexibility.
-
-----
-
-*****************************
-Is Lightning a DAG framework?
-*****************************
-No.
-
-A Lightning App enables developers to express complex, interactive applications that are impossible to create with DAGs.
-
-----
-
-*********************************
-Can I Build a DAG with Lightning?
-*********************************
-Yes!
-
-DAGs are one of the easiest Lightning Apps to build. For example, here's a :doc:`full app that defines a DAG <../examples/dag/dag>`.
-
-----
-
-********
-Examples
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Build a DAG
- :description: Learn how to create a DAG with Lightning
- :col_css: col-md-4
- :button_link: ../examples/dag/dag.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/debug_app.rst b/docs/source-app/glossary/debug_app.rst
deleted file mode 100644
index 2d5c0d19903b0..0000000000000
--- a/docs/source-app/glossary/debug_app.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-:orphan:
-
-.. include:: ../workflows/debug_locally.rst
diff --git a/docs/source-app/glossary/distributed_fe.rst b/docs/source-app/glossary/distributed_fe.rst
deleted file mode 100644
index 36d64b01436b6..0000000000000
--- a/docs/source-app/glossary/distributed_fe.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-#####################
-Distributed Front-End
-#####################
diff --git a/docs/source-app/glossary/distributed_hardware.rst b/docs/source-app/glossary/distributed_hardware.rst
deleted file mode 100644
index 0a64f5f5c0720..0000000000000
--- a/docs/source-app/glossary/distributed_hardware.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-####################
-Distributed Hardware
-####################
diff --git a/docs/source-app/glossary/environment_variables.rst b/docs/source-app/glossary/environment_variables.rst
deleted file mode 100644
index dcbcd8c6f4ab7..0000000000000
--- a/docs/source-app/glossary/environment_variables.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-.. _environment_variables:
-
-*********************
-Environment Variables
-*********************
-
-If your App is using configuration values you don't want to commit with your App source code, you can use environment variables.
-
-Lightning allows you to set environment variables when running the App from the CLI with the `lightning_app run app` command. You can use environment variables to pass any values to the App, and avoiding sticking those values in the source code.
-
-Set one or multiple variables using the **--env** option:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud --env FOO=BAR --env BAZ=FAZ
-
-Environment variables are available in all Flows and Works, and can be accessed as follows:
-
-.. code:: python
-
- import os
-
- print(os.environ["FOO"]) # BAR
- print(os.environ["BAZ"]) # FAZ
-
-.. note::
- Environment variables are not encrypted. For sensitive values, we recommend using :ref:`Encrypted Secrets `.
diff --git a/docs/source-app/glossary/event_loop.rst b/docs/source-app/glossary/event_loop.rst
deleted file mode 100644
index 30d1bd3b3acfd..0000000000000
--- a/docs/source-app/glossary/event_loop.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-##########
-Event loop
-##########
-
-Drawing inspiration from modern web frameworks like `React.js `_, the Lightning App runs all flows in an **event loop** (forever), which is triggered several times a second after collecting any works' state change.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_loop.gif
-
-When running a Lightning App in the cloud, the ``LightningWork`` run on different machines. LightningWork communicates any state changes to the **event loop** which re-executes the flow with the newly-collected works' state.
-
-.. _app_event_loop:
diff --git a/docs/source-app/glossary/fault_tolerance.rst b/docs/source-app/glossary/fault_tolerance.rst
deleted file mode 100644
index b0ee6dfd21102..0000000000000
--- a/docs/source-app/glossary/fault_tolerance.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:orphan:
-
-###############
-Fault tolerance
-###############
-
-.. note:: documentation under construction
diff --git a/docs/source-app/glossary/index.rst b/docs/source-app/glossary/index.rst
deleted file mode 100644
index 50e4c1be435d8..0000000000000
--- a/docs/source-app/glossary/index.rst
+++ /dev/null
@@ -1,155 +0,0 @@
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- ios_and_android
- app_tree
- build_config/build_config
- command_lines/command_lines
- dag
- event_loop
- environment_variables
- secrets
- front ends <../workflows/add_web_ui/glossary_front_end>
- Lightning app <../core_api/lightning_app/index>
- sharing_components
- scheduling
- storage/storage
- restful_api/restful_api
- add web ui <../workflows/add_web_ui/glossary_ui>
- use_local_lightning
-
-########
-Glossary
-########
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Android Lightning App
- :description: Use Lightning with android apps.
- :col_css: col-md-12
- :button_link: ios_and_android.html
- :height: 100
-
-.. displayitem::
- :header: App Components Tree
- :description: Learn how components can be nested to form component trees where the LightningFlows are its branches and LightningWorks are its leaves.
- :col_css: col-md-12
- :button_link: app_tree.html
- :height: 100
-
-.. displayitem::
- :header: Build Configuration
- :description: Prepare your requirements, add custom build commands or use docker image
- :col_css: col-md-12
- :button_link: build_config/build_config.html
- :height: 100
-
-.. displayitem::
- :header: Command Line Interface (CLI)
- :description: Learn about the Lightning CLI
- :col_css: col-md-12
- :button_link: command_lines/command_lines.html
- :height: 100
-
-.. displayitem::
- :header: DAG
- :description: Learn about directed acyclic graph, their properties and usage
- :col_css: col-md-12
- :button_link: dag.html
- :height: 100
-
-.. displayitem::
- :header: Event Loop
- :description: Learn how the Infinite Event Loop enables high distributed reactivity by triggering after collecting state changes.
- :col_css: col-md-12
- :button_link: event_loop.html
- :height: 100
-
-.. displayitem::
- :header: Environment Variables
- :description: Add secrets such as API keys or access tokens
- :col_css: col-md-12
- :button_link: environment_variables.html
- :height: 100
-
-.. displayitem::
- :header: Encrypted Secrets
- :description: Learn how to add passwords to your Lightning apps
- :col_css: col-md-12
- :button_link: secrets.html
- :height: 100
-
-.. displayitem::
- :header: Frontend
- :description: Customize your App View with any framework you want
- :col_css: col-md-12
- :button_link: ../workflows/add_web_ui/glossary_front_end.html
- :height: 100
-
-.. displayitem::
- :header: iOS Lightning App
- :description: Use Lightning with iOS apps.
- :col_css: col-md-12
- :button_link: ios_and_android.html
- :height: 100
-
-.. displayitem::
- :header: Lightning App
- :description: A Lightning app is a collection of connected components that form a workflow
- :col_css: col-md-12
- :button_link: ../core_api/lightning_app/index.html
- :height: 100
-
-.. displayitem::
- :header: Mounts
- :description: Mount Cloud Data
- :col_css: col-md-12
- :button_link: mount.html
- :height: 100
-
-.. displayitem::
- :header: Sharing Components
- :description: Let's create an ecosystem altogether
- :col_css: col-md-12
- :button_link: sharing_components.html
- :height: 100
-
-.. displayitem::
- :header: Scheduling
- :description: Orchestrate execution at specific times
- :col_css: col-md-12
- :button_link: scheduling.html
- :height: 100
-
-.. displayitem::
- :header: Storage
- :description: Easily share files even across multiple machines
- :col_css: col-md-12
- :button_link: storage/storage.html
- :height: 100
-
-.. displayitem::
- :header: REST API
- :description: Learn how to set up a RESTful API endpoint
- :col_css: col-md-12
- :button_link: restful_api/restful_api.html
- :height: 100
-
-.. displayitem::
- :header: UI
- :description: Combine multiple frameworks to create your own UI
- :col_css: col-md-12
- :button_link: ../workflows/add_web_ui/glossary_ui.html
- :height: 100
-
-.. displayitem::
- :header: Using a development branch of Lightning on the Cloud
- :description: Learn how to contribute to the Lightning framework in the cloud
- :col_css: col-md-12
- :button_link: use_local_lightning.html
- :height: 100
diff --git a/docs/source-app/glossary/ios_and_android.rst b/docs/source-app/glossary/ios_and_android.rst
deleted file mode 100644
index 90aeecbc0b141..0000000000000
--- a/docs/source-app/glossary/ios_and_android.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-
-###############################################
-Apple and Android mobile devices with Lightning
-###############################################
-
-Audience: Users who want to develop Lightning Apps for Apple or Android mobile devices.
-
-----
-
-***********************************************************
-Develop a Lightning App for Apple or Android mobile devices
-***********************************************************
-
-There are a couple of ways you can go about building Lightning Apps that work on Apple or Android mobile devices.
-
-Option 1
-^^^^^^^^
-
-You can develop a Lightning App that interacts with an iOS or Android app.
-The ML and backend services live on the Lightning App, but the iOS or Android code (obj-c/swift or android) lives on the mobile devices.
-
-Option 2
-^^^^^^^^
-
-You can build a mobile-first React Lightning App that works on both Apple and Android mobile devices.
-The `InVideo app
`_ is a good example of a Lightning App that does just that.
diff --git a/docs/source-app/glossary/lightning_app_overview/index.rst b/docs/source-app/glossary/lightning_app_overview/index.rst
deleted file mode 100644
index 09de273affc50..0000000000000
--- a/docs/source-app/glossary/lightning_app_overview/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-:orphan:
-
-###########################
-Lightning Apps Key concepts
-###########################
-
-**Audience:** Users who want to know how the 🤯 magic works under the hood.
-
-----
-
-.. note:: This page is under construction
diff --git a/docs/source-app/glossary/mount.rst b/docs/source-app/glossary/mount.rst
deleted file mode 100644
index a62d72b5b798d..0000000000000
--- a/docs/source-app/glossary/mount.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. include:: ../workflows/mount_cloud_object_store.rst
diff --git a/docs/source-app/glossary/restful_api/restful_api.rst b/docs/source-app/glossary/restful_api/restful_api.rst
deleted file mode 100644
index 6e04f60c75f1c..0000000000000
--- a/docs/source-app/glossary/restful_api/restful_api.rst
+++ /dev/null
@@ -1,53 +0,0 @@
-:orphan:
-
-###########
-RESTful API
-###########
-
-**Audience:** Users looking to create an API in their App to allow users to activate functionalities from external sources.
-
-----
-
-**********************
-What is a RESTful API?
-**********************
-
-A RESTful API is a set of external URL routes exposed by a server that enables clients to trigger some functionalities, such as getting or putting some data, uploading files, etc..
-
-This provides great flexibility for users as they can easily discover functionalities made available by the App Builders.
-
-The Lightning App framework supports the four primary HTTP methods: `GET`, `POST`, `PUT`, `DELETE`.
-
-These methods are guidelines to organize your RESTful Services and help users understand your functionalities.
-
-* **`GET`:** Reads data from the server.
-* **`POST`:** Creates new resources.
-* **`PUT`:** Updates/replaces existing resources.
-* **`DELETE`:** Deletes resources.
-
-Learn more about `HTTP Methods for RESTful Services here `_.
-
-The Lightning App framework uses the popular `FastAPI `_ and `Pydantic `_ frameworks under the hood. This means you can use all their features while building your App.
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a RESTful API
- :description: Learn how to develop an API for your App.
- :col_css: col-md-6
- :button_link: ../../workflows/build_rest_api/index_content.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/scheduling.rst b/docs/source-app/glossary/scheduling.rst
deleted file mode 100644
index 0e04dc35c897d..0000000000000
--- a/docs/source-app/glossary/scheduling.rst
+++ /dev/null
@@ -1,185 +0,0 @@
-:orphan:
-
-##########
-Scheduling
-##########
-
-The Lightning Scheduling system makes it easy to schedule your components execution with any arbitrary conditions.
-
-
-----
-
-************************
-Schedule your components
-************************
-
-The LightningFlow has a ``schedule`` method which can be used to schedule your components.
-
-.. code-block:: python
-
- from lightning.app import LightningWork, LightningFlow
- from lightning.app.storage import Path
-
-
- class MyFlow(LightningFlow):
-
- def run(self):
- if self.schedule("hourly"):
- # run some code once every hour.
-
- if self.schedule("daily"):
- # run some code once day.
-
- if self.schedule("daily") and anything_else:
- # run some code once day if the anything else is also True.
-
- if self.schedule("2 4 * * mon,fri"):
- # defined with cron syntax, run some code at 04:02 on every Monday and Friday.
-
-Learn more about the cron syntax `here `_
-
-----
-
-**************
-Best Practices
-**************
-
-In the example above, the line ``self.schedule("hourly")`` will return ``True`` for a **single** flow execution every hour. Mathematically, this is known as a dirac.
-
-1. Instantiate your component under the schedule method and run outside as follows:
-
-.. code-block:: python
-
- from lightning.app import LightningFlow
- from lightning.app.structures import List
-
- class ScheduledDAG(LightningFlow):
- def __init__(self):
- super().__init__()
- self.list = List()
-
- def run(self):
- if self.schedule("hourly"):
- # dynamically instantiate
- # don't forget to always attach
- # your components to the flow !!!
- self.list.append(MyDAGFlow(...))
-
- # run all dags, but the completed ones
- # are cached and don't re-execute.
- for dag in self.list:
- dag.run()
-
-
-2. Run a single work under the schedule with different arguments to have it re-run.
-
-.. code-block:: python
-
- from lightning.app import LightningFlow
- from time import time
-
- class ScheduledDAG(LightningFlow):
- def __init__(self):
- super().__init__()
- self.data_processor = DataProcessorWork(...)
-
- def run(self):
- ...
- if self.schedule("hourly"):
- self.data_processor.run(trigger_time=time())
-
-
-3. Capture the event in the state and execute your sequential works outside.
-
-.. code-block:: python
-
- from lightning.app import LightningFlow
- from time import time
-
- class ScheduledDAG(LightningFlow):
- def __init__(self):
- super().__init__()
- self.should_execute = False
- self.data_processor = DataProcessorWork(...)
- self.training_work = KerasTrainingWork(...)
-
- def run(self):
- ...
- if self.schedule("hourly"):
- self.should_execute = True
-
- # Runs in 10 min
- if self.should_execute:
- # Runs in 5 min
- self.data_processor.run(trigger_time=time())
- if self.data_processor.has_succeeded:
- # Runs in 5 min
- self.training_work.run(self.data_processor.data)
- if self.training_work.has_succeeded:
- self.should_execute = False
-
-----
-
-***********
-Limitations
-***********
-
-As stated above, the schedule acts as a dirac and is **True** for a single flow execution.
-Therefore, sequential works execution under the schedule won't work as they don't complete within a single flow execution.
-
-Here is an example of something which **WON'T** work:
-
-.. code-block:: python
-
- from lightning.app import LightningFlow
- from time import time
-
- class ScheduledDAG(LightningFlow):
- def __init__(self):
- super().__init__()
- self.data_processor = DataProcessorWork(...)
- self.training_work = KerasTrainingWork(...)
-
- def run(self):
- ...
- if self.schedule("hourly"):
- # This finishes 5 min later
- self.data_processor.run(trigger_time=time())
- if self.data_processor.has_succeeded:
- # This will never be reached as the
- # data processor will keep processing forever...
- self.training_work.run(self.data_processor.data)
-
-----
-
-**************************
-Frequently Asked Questions
-**************************
-
-- **Q: Can I use multiple nested scheduler?** No, as they might cancel themselves out, but you can capture the event of one to trigger the next one.
-
-- **Q: Can I use any arbitrary logic to schedule?** Yes, this design enables absolute flexibility, but you need to be careful to avoid bad practices.
-
-----
-
-********
-Examples
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Build a DAG
- :description: Learn how to schedule a DAG execution
- :col_css: col-md-4
- :button_link: ../examples/dag/dag.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/secrets.rst b/docs/source-app/glossary/secrets.rst
deleted file mode 100644
index 95a0d564c648d..0000000000000
--- a/docs/source-app/glossary/secrets.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-.. _secrets:
-
-#################
-Encrypted Secrets
-#################
-
-Encrypted Secrets allow you to pass private data to your apps, like API keys, access tokens, database passwords, or other credentials, in a secure way without exposing them in your code.
-Secrets provide you with a secure way to store this data in a way that is accessible to Apps so that they can authenticate third-party services/solutions.
-
-.. tip::
- For non-sensitive configuration values, we recommend using :ref:`plain-text Environment Variables `.
-
-************
-Add a secret
-************
-
-Add the secret to your profile on lightning.ai.
-Log in to your lightning.ai account > **Profile** > **Secrets** tab > click the **+New** button.
-Provide a name and value to your secret, for example, name could be "github_api_token".
-
-.. note::
- Secret names must start with a letter and can only contain letters, numbers, dashes, and periods. The Secret names must comply with `RFC1123 naming conventions `_. The Secret value has no restrictions.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning//encrypted_secrets_login.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning//encrypted_secrets_login.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-************
-Use a secret
-************
-
-1. Add an environment variable to your app to read the secret. For example, add an "api_token" environment variable:
-
-.. code:: python
-
- import os
-
- component.connect(api_token=os.environ["api_token"])
-
-2. Pass the secret to your app run with the following command:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud --secret =
-
-In this example, the command would be:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud --secret api_token=github_api_token
-
-
-The ``--secret`` option can be used for multiple Secrets, and alongside the ``--env`` option.
-
-Here's an example:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud --env FOO=bar --secret MY_APP_SECRET=my-secret --secret ANOTHER_SECRET=another-secret
-
-
-----
-
-******************
-How does this work
-******************
-
-When a Lightning App (App) **runs in the cloud**, a Secret can be exposed to the App using environment variables.
-The value of the Secret is encrypted in the Lightning.ai database, and is only decrypted and accessible to
-LightningFlow (Flow) or LightningWork (Work) processes in the cloud (when you use the ``--cloud`` option running your App).
diff --git a/docs/source-app/glossary/sharing_components.rst b/docs/source-app/glossary/sharing_components.rst
deleted file mode 100644
index 2426bb43d4469..0000000000000
--- a/docs/source-app/glossary/sharing_components.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-#####################
-Sharing my components
-#####################
-
-**Audience:** Users who want to know how to share component.
-
-**Level:** Basic
-
-----
-
-********************************************
-Why should I consider sharing my components?
-********************************************
-
-Lightning is community driven and its core objective is to make AI accessible to everyone.
-
-By creating components and sharing them with everyone else, the barrier to entry will go down.
-
-----
-
-************************************
-How should I organize my components?
-************************************
-
-By design, Lightning components are nested to form component trees where the ``LightningFlows`` are its branches and ``LightningWorks`` are its leaves.
-
-This design has two primary advantages:
-
-* This helps users organize and maintain their code with more ease.
-* This also helps create an ecosystem with **reusable** components.
-
-
-Now, imagine you have implemented a **KerasScriptRunner** component for training any `Keras `_ model with `Tensorboard UI `_ integrated.
-
-Here are the best practices steps before sharing the component:
-
-* **Testing**: Ensure your component is well tested by following the :doc:`../testing` guide.
-* **Documented**: Ensure your component has a docstring and comes with some usage explications.
-
-.. Note:: As a Lightning user, it helps to implement your components thinking someone else is going to use them.
-
-----
-
-*****************************************
-How should I proceed to share components?
-*****************************************
-
-Once your component is ready, create a *PiPy* package with your own library and then it can be reused by anyone else.
-
-Here is a `Component Template `_ from `William Falcon `_ to guide your component.
diff --git a/docs/source-app/glossary/storage/differences.rst b/docs/source-app/glossary/storage/differences.rst
deleted file mode 100644
index ed45edd069632..0000000000000
--- a/docs/source-app/glossary/storage/differences.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-:orphan:
-
-##################################
-Differences between Drive and Path
-##################################
-
-**Audience:** Users who want to share files between components.
-
-
-The Lightning Storage system makes it easy to share files between LightningWork so you can run your app both locally and in the cloud without changing the code.
-
-
-Lightning storage provides two solutions :class:`~lightning.app.storage.drive.Drive` and :class:`~lightning.app.storage.path.Path` to deal with files locally and in the cloud likewise.
-
-
-----
-
-*****************
-What is a Drive ?
-*****************
-
-The Drive object provides a central place for your components to share data.
-
-The drive acts as an isolate folder and any component can access it by knowing its name.
-
-Your components can put, list, get, delete files from and to the Drive (except LightningFlow's).
-
-----
-
-****************
-What is a Path ?
-****************
-
-The Path object is a reference to a specific file or directory from a LightningWork and can be used to transfer those files to another LightningWork (one way, from source to destination).
-
-A good mental representation of the Path Object usage is the `relay race `_.
-To make a transfer, the LightningWork Receiver asks (e.g when the path object is passed by the flow to the Receiver)
-for a copy of the files (baton) owned by their LightningWork Producer (e.g the work that created the files).
-
-----
-
-*********************************
-When should I use Drive vs Path ?
-*********************************
-
-The Drive should be used when you want to easily share data between components but the Path enables to create cleaner shareable
-component where you want to exposes some files to be transferred (like an HPO component sharing the best model weights) for anyone else to use.
-
-The Drive is more intuitive and easier to get on-boarded with, but in more advanced use cases, you might appreciate the Path Object
-which makes uni-directional files transfer simpler.
-
-----
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: The Drive Object.
- :description: Put, List and Get Files From a Shared Drive Disk.
- :col_css: col-md-4
- :button_link: drive.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: The Path Object.
- :description: Transfer Files From One Component to Another by Reference.
- :col_css: col-md-4
- :button_link: path.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/storage/drive.rst b/docs/source-app/glossary/storage/drive.rst
deleted file mode 100644
index dffdb979f305a..0000000000000
--- a/docs/source-app/glossary/storage/drive.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-:orphan:
-
-.. _drive_storage:
-
-#############
-Drive Storage
-#############
-
-**Audience:** Users who want to put, list, and get files from a shared disk space.
-
-----
-
-.. include:: ../../glossary/storage/drive_content_old.rst
diff --git a/docs/source-app/glossary/storage/drive_content.rst b/docs/source-app/glossary/storage/drive_content.rst
deleted file mode 100644
index ff1540e8ce6f6..0000000000000
--- a/docs/source-app/glossary/storage/drive_content.rst
+++ /dev/null
@@ -1,223 +0,0 @@
-:orphan:
-
-**************************
-What are Lightning Drives?
-**************************
-
-Lightning Drives are shared app storage that allow you to share files between :doc:`LightningWork (Work) <../../core_api/lightning_work/index>` components, so that you distributed components can share files when running on the cloud. Using drives, you can run your Lightning App both locally and in the cloud without changing the code.
-
-The Drive object provides a central place for your components to share data.
-
-The Drive acts as an isolated folder and any component can access it by knowing its name.
-
-We currently support two types of Drives: Lightning-managed (``lit://``) and S3 (``s3://``).
-
-+-----------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
-| Lightning-managed (``lit://``) | Allows read-write operations and are accessible through the Drive API from a Work. |
-| | |
-| | They allow your components to put, list, get, and delete files from and to the Drive (except LightningFlows). |
-+-----------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
-| S3 (``s3://``) | S3 is AWS S3 storage mounted at a filesystem mount point. S3 is read-only (for now) and its primary purpose is |
-| | to give you a permanent location to access your training data. |
-| | |
-| | They allow your components to list and get files located on the Drive. |
-+-----------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
-
-----
-
-**********************
-What Drives do for you
-**********************
-
-Think of every instance of the Drive object acting like a Google Drive or like Dropbox.
-
-By sharing the Drive between components through the LightningFlow,
-several components can have a shared place to read (S3 Drives) or read and write (Lightning-managed Drives) files from.
-
-S3 Drive Limitations
-^^^^^^^^^^^^^^^^^^^^
-
-These limitations only apply to S3 Drives:
-
-* There is no top level “shareable” S3 drive object. Each S3 Drive is owned by a particular Work. However, it’s possible to create a Drive with the same location across multiple Works.
-
-* S3 buckets cannot be mounted as Drives once a Work has been instantiated. The `Drive` object must be initialized passed to a Work at creation time.
-
-* Whenever a Drive is mounted to a Work, an indexing process will be done again for the provided S3 bucket. This may lead to performance issues with particularly large S3 buckets. For context, 1M files with 2-3 levels of nesting takes less than 1 second to index.
-
-----
-
-**************
-Create a Drive
-**************
-
-In order to create a Drive, you simply need to pass its name with the prefix ``lit://`` or ``s3://``.
-
-.. note:: We do not support mounting single objects for S3 buckets, so there must be a trailing `/` in the s3:// URL. For example: ``s3://foo/bar/``.
-
-.. code-block:: python
-
- from lightning.app.storage import Drive
-
- # The identifier of this Drive is ``drive_1``
- # Note: You need to add Lightning protocol ``lit://`` as a prefix.
-
- drive_1 = Drive("lit://drive_1")
-
- # The identifier of this Drive is ``drive_2``
- drive_2 = Drive("s3://drive_2/")
-
-Any component can create a drive object for ``lit://`` Drives.
-
-.. code-block:: python
-
- from lightning.app import LightningFlow, LightningWork
- from lightning.app.storage import Drive
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.drive_1 = Drive("lit://drive_1")
-
- def run(self):
- ...
-
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.drive_1 = Drive("lit://drive_1")
-
- def run(self):
- ...
-
-----
-
-*****************************
-Supported actions with Drives
-*****************************
-
-A Lightning-managed Drive supports put, list, get, and delete actions.
-
-An S3 Drive supports list and get actions (for now).
-
-.. code-block:: python
-
- from lightning.app.storage import Drive
-
- drive = Drive("lit://drive")
-
- drive.list(".") # Returns [] as empty
-
- # Created file.
- with open("a.txt", "w") as f:
- f.write("Hello World !")
-
- drive.put("a.txt")
-
- drive.list(".") # Returns ["a.txt"] as the file copied in the Drive during the put action.
-
- drive.get("a.txt") # Get the file into the current worker
-
- drive.delete("a.txt")
-
- drive.list(".") # Returns [] as empty
-
-----
-
-**********************************
-Component interactions with Drives
-**********************************
-
-Here is an illustrated code example on how to create drives within Works.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/drive_2.png
-
-.. code-block:: python
-
- from lightning.app import LightningFlow, LightningWork, LightningApp
- from lightning.app.storage import Drive
-
-
- class Work_A(LightningWork):
- def __init__(self):
- super().__init__()
- # The identifier of the Drive is ``drive_1``
- # Note: You need to add Lightning protocol ``lit://`` as a prefix.
- self.drive_1 = Drive("lit://drive_1")
-
- def run(self):
- # 1. Create a file.
- with open("a.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into the drive.
- self.drive_1.put("a.txt")
-
-
- class Work_B(LightningWork):
- def __init__(self):
- super().__init__()
-
- # Note: Work B has access 2 drives.
-
- # The identifier of this Drive is ``drive_1``
- self.drive_1 = Drive("lit://drive_1")
- # The identifier of this Drive is ``drive_2``
- self.drive_2 = Drive("lit://drive_2")
-
- def run(self):
- # 1. Create a file.
- with open("b.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into both drives.
- self.drive_1.put("b.txt")
- self.drive_2.put("b.txt")
-
-
- class Work_C(LightningWork):
- def __init__(self):
- super().__init__()
- self.drive_2 = Drive("lit://drive_2")
-
- def run(self):
- # 1. Create a file.
- with open("c.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into the drive.
- self.drive_2.put("c.txt")
-
-----
-
-*************************
-Transfer files with Drive
-*************************
-
-In the example below, the Drive is created by the Flow and passed to its Works.
-
-The ``Work_1`` put a file **a.txt** in the **Drive("lit://this_drive_id")** and the ``Work_2`` can list and get the **a.txt** file from it.
-
-.. literalinclude:: ../../../../examples/app/drive/app.py
-
-----
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Learn about the Path Object.
- :description: Transfer Files From One Component to Another by Reference.
- :col_css: col-md-4
- :button_link: path.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/storage/drive_content_old.rst b/docs/source-app/glossary/storage/drive_content_old.rst
deleted file mode 100644
index 3c37f883fd31d..0000000000000
--- a/docs/source-app/glossary/storage/drive_content_old.rst
+++ /dev/null
@@ -1,199 +0,0 @@
-:orphan:
-
-
-************
-About Drives
-************
-
-Lightning Drive storage makes it easy to share files between LightningWorks so you can run your Lightning App both locally and in the cloud without changing the code.
-
-The Drive object provides a central place for your components to share data.
-
-The Drive acts as an isolate folder and any component can access it by knowing its name.
-
-Your components can put, list, get, and delete files from and to the Drive (except LightningFlows).
-
-----
-
-***********************
-What Drive does for you
-***********************
-
-Think of every instance of the Drive object acting like a Google Drive or like Dropbox.
-
-By sharing the Drive between components through the LightningFlow,
-several components can have a shared place to read and write files from.
-
-----
-
-**************
-Create a Drive
-**************
-
-In order to create a Drive, you simply need to pass its name with the prefix ``lit://`` as follows:
-
-.. code-block:: python
-
- from lightning.app.storage import Drive
-
- # The identifier of this Drive is ``drive_1``
- # Note: You need to add Lightning protocol ``lit://`` as a prefix.
-
- drive_1 = Drive("lit://drive_1")
-
- # The identifier of this Drive is ``drive_2``
- drive_2 = Drive("lit://drive_2")
-
-Any components can create a drive object.
-
-.. code-block:: python
-
- from lightning.app import LightningFlow, LightningWork
- from lightning.app.storage import Drive
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.drive_1 = Drive("lit://drive_1")
-
- def run(self):
- ...
-
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.drive_1 = Drive("lit://drive_1")
-
- def run(self):
- ...
-
-----
-
-*****************************
-Supported actions with Drives
-*****************************
-
-A Drive supports put, list, get, and delete actions.
-
-.. code-block:: python
-
- from lightning.app.storage import Drive
-
- drive = Drive("lit://drive")
-
- drive.list(".") # Returns [] as empty
-
- # Created file.
- with open("a.txt", "w") as f:
- f.write("Hello World !")
-
- drive.put("a.txt")
-
- drive.list(".") # Returns ["a.txt"] as the file copied in the Drive during the put action.
-
- drive.get("a.txt") # Get the file into the current worker
-
- drive.delete("a.txt")
-
- drive.list(".") # Returns [] as empty
-
-----
-
-**********************************
-Component interactions with Drives
-**********************************
-
-Here is an illustrated code example on how to create drives within works.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/drive_2.png
-
-.. code-block:: python
-
- from lightning.app import LightningFlow, LightningWork, LightningApp
- from lightning.app.storage import Drive
-
-
- class Work_A(LightningWork):
- def __init__(self):
- super().__init__()
- # The identifier of the Drive is ``drive_1``
- # Note: You need to add Lightning protocol ``lit://`` as a prefix.
- self.drive_1 = Drive("lit://drive_1")
-
- def run(self):
- # 1. Create a file.
- with open("a.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into the drive.
- self.drive_1.put("a.txt")
-
-
- class Work_B(LightningWork):
- def __init__(self):
- super().__init__()
-
- # Note: Work B has access 2 drives.
-
- # The identifier of this Drive is ``drive_1``
- self.drive_1 = Drive("lit://drive_1")
- # The identifier of this Drive is ``drive_2``
- self.drive_2 = Drive("lit://drive_2")
-
- def run(self):
- # 1. Create a file.
- with open("b.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into both drives.
- self.drive_1.put("b.txt")
- self.drive_2.put("b.txt")
-
-
- class Work_C(LightningWork):
- def __init__(self):
- super().__init__()
- self.drive_2 = Drive("lit://drive_2")
-
- def run(self):
- # 1. Create a file.
- with open("c.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into the drive.
- self.drive_2.put("c.txt")
-
-----
-
-*****************************
-Transfer files with Drive
-*****************************
-
-In the example below, the Drive is created by the flow and passed to its LightningWork's.
-
-The ``Work_1`` put a file **a.txt** in the **Drive("lit://this_drive_id")** and the ``Work_2`` can list and get the **a.txt** file from it.
-
-.. literalinclude:: ../../../../examples/app/drive/app.py
-
-
-----
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Learn about the Path Object.
- :description: Transfer Files From One Component to Another by Reference.
- :col_css: col-md-4
- :button_link: path.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/storage/path.rst b/docs/source-app/glossary/storage/path.rst
deleted file mode 100644
index 4cf41c1453aa4..0000000000000
--- a/docs/source-app/glossary/storage/path.rst
+++ /dev/null
@@ -1,326 +0,0 @@
-:orphan:
-
-############
-Path Storage
-############
-
-**Audience:** Users who want to share files between components.
-
-
-The Lightning Storage system makes it easy to share files between LightningWork so you can run your app both locally and in the cloud without changing the code.
-
-----
-
-***********************
-What is a Path Object ?
-***********************
-
-The Path object is a reference to a specific file or directory from a LightningWork and can be used to transfer those files to another LightningWork (one way, from source to destination).
-
-A good mental representation of the Path Object usage is the `relay race `_.
-To make a transfer, the receiver asks (e.g when the path object is passed by the flow to the receiver)
-for a copy of the files (baton) owned by their producer (e.g the LightningWork which created the files).
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/path2.png
-
-*******************************************
-How does the Path Object works internally ?
-*******************************************
-
-To understand the Path Object internal, let's first answer this question: How do you locate a specific file or folder within a distributed system made of multiple machines ?
-
-You need to know on which machine the file or folder is located (e.g the LightningWork name uniquely identify its own machine in the cloud) and
-then you need the local path of the file or folder on that machine.
-
-In simple words, the Lightning Path augments :class:`pathlib.Path` object by tracking on which machine the file or folder is located.
-
-----
-
-**************************
-When to use Path storage ?
-**************************
-
-In the cloud, every :class:`~lightning.app.core.work.LightningWork` runs in a separate machine with its own filesystem.
-This means files in one Work cannot be directly accessed in another like you would be able to when running the app locally.
-But with Lightning Storage, this is easy: Simply declare which files need to be shared and Lightning will take care of the rest.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/path.mp4
- :width: 600
- :autoplay:
- :loop:
- :muted:
-
-
-----
-
-
-***********************************
-Tell Lightning where your files are
-***********************************
-
-Convert every filesystem path you want to share with other LightningWorks to by adding ``lit://`` in front of it.
-
-.. code-block:: python
-
- from lightning.app import LightningWork
- from lightning.app.storage import Path
-
-
- class SourceWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.checkpoint_dir = None
-
- def run(self):
- # Normally you would do:
- # self.checkpoint_dir = "outputs/checkpoints"
- # os.makedirs("outputs/checkpoints")
- # ...
-
- # In Lightning, do:
- self.checkpoint_dir = "lit://outputs/checkpoints"
- os.makedirs(self.checkpoint_dir)
- ...
-
-
-Under the hood, we convert this string to a :class:`~lightning.app.storage.path.Path` object, which is a drop-in replacement for :class:`pathlib.Path` meaning it will work with :mod:`os`, :mod:`os.path` and :mod:`pathlib` filesystem operations out of the box!
-
-
-----
-
-
-****************************
-Access files in another Work
-****************************
-
-Accessing files from another LightningWork is as easy as handing the path over by reference.
-For example, share a directory by passing it as an input to the run method of the destination work:
-
-.. code-block:: python
- :emphasize-lines: 12
-
- from lightning.app import LightningFlow
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.source = SourceWork()
- self.destination = DestinationWork()
-
- def run(self):
- self.source.run()
- # Pass the Path reference from one work to another
- self.destination.run(self.source.checkpoint_dir)
-
-
-When the destination Work starts, Lightning will automatically transfer the files to its filesystem (if they exist on the other end):
-
-.. code-block:: python
-
- class DestinationWork(LightningWork):
- def run(self, checkpoint_dir):
- # The directory is now accessible inside this Work
- files = os.listdir(checkpoint_dir)
- ...
-
-
-The automatic transfer only happens if the referenced files already exist in the originating LightningWork and it will overwrite any files that already exist locally.
-In all other cases, you can trigger the transfer manually.
-
-
-----
-
-
-******************
-Get files manually
-******************
-
-If you need to access files at a specific time or transfer them multiple times, use ``.get()`` method:
-
-.. code-block:: python
-
- def run(self, checkpoint_dir):
- ...
- # Make the directory available
- checkpoint_dir.get()
-
- # If the path already exists locally, you can force overwriting it
- checkpoint_dir.get(overwrite=True)
-
- files = os.listdir(checkpoint_dir)
- ...
-
-
-Multiple calls to the ``.get()`` method will always result in file transfers, regardless of whether the files have changed or not.
-If the path does not exist remotely, it will raise a ``FileNotFoundError``.
-If you need to handle this case, the Path also offers a method to check if files exist remotely.
-
-----
-
-
-********************************
-Check if a file or folder exists
-********************************
-
-You can check if a path exists locally or remotely in the source Work using the ``.exists_local()`` and ``.exists_remote()`` methods:
-
-.. code-block:: python
-
- def run(self, checkpoint_dir):
- if checkpoint_dir.exists_remote():
- # Get the file only if it exists in the source Work
- checkpoint_dir.get()
-
- # OR
-
- if checkpoint_dir.exists_local():
- # Do something with the file if it exists locally
- files = os.listdir(checkpoint_dir)
-
-
-----
-
-
-*************
-Persist files
-*************
-
-If a LightningWork finishes or stops due to an interruption (e.g., due to insufficient credits), the filesystem and all files in it get deleted (unless running locally).
-Lightning makes sure all Paths that are part of the state get stored and made accessible to the other Works that still need these files.
-
-.. code-block:: python
-
- from lightning.app.storage import Path
-
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- # The files in this path will be saved as an artifact when the Work finishes
- self.checkpoint_dir = "lit://outputs/checkpoints"
-
- # The files in this path WON'T be saved because it is not declared as a Lightning Path
- self.log_dir = "outputs/logs"
-
-
-----
-
-
-*********************************
-Example: Share a model checkpoint
-*********************************
-
-A common workflow in ML is to use a checkpoint created by another component.
-First, define a component that saves a checkpoint:
-
-.. code:: python
- :emphasize-lines: 14-18
-
- from lightning.app import LightningFlow, LightningWork
- from lightning.app.storage import Path
- import torch
- import os
-
-
- class ModelTraining(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.checkpoint_dir = "lit://outputs/checkpoints"
-
- def run(self):
- # create a directory
- os.makedirs(self.model_checkpoints_path, exist_ok=True)
- # make fake checkpoints
- checkpoint_1 = torch.tensor([0, 1, 2, 3, 4])
- checkpoint_2 = torch.tensor([0, 1, 2, 3, 4])
- torch.save(checkpoint_1, os.path.join(self.checkpoint_dir, "checkpoint_1.ckpt"))
- torch.save(checkpoint_2, os.path.join(self.checkpoint_dir, "checkpoint_2.ckpt"))
-
-
-Next, define a component that needs the checkpoints:
-
-.. code:: python
- :emphasize-lines: 4, 7
-
- class ModelDeploy(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__()
-
- def run(self, checkpoint_dir):
- ckpts = os.listdir(checkpoint_dir)
- checkpoint_1 = torch.load(ckpts[0])
- checkpoint_2 = torch.load(ckpts[1])
-
-Link both components via a parent component:
-
-.. code:: python
- :emphasize-lines: 7
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.train = ModelTraining()
-
- # pass the checkpoint path
- self.deploy = ModelDeploy()
-
- def run(self):
- self.train.run()
- self.deploy.run(checkpoint_dir=self.train.checkpoint_dir)
-
-
- app = L.LightningApp(Flow())
-
-
-----
-
-**************************
-Frequently Asked Questions
-**************************
-
-- **Q: Can files in a LightningWork be accessed inside the LightningFlow too?**
-
- No, LightningFlow is intentionally designed not to perform filesystem operations and computations and is intended to exclusively orchestrate Flow and Work.
-
-- **Q: Is it possible to reference any file using the Lightning lit:// path notation?**
-
- Yes, but only files for which the app has write permissions can be copied from Work to Work (apps don't run with root privileges).
-
-- **Q: Can I access the Lightning Storage in my UI (StreamLit, Web, ...)?**
-
- This is currently not supported but will be in the future.
-
-- **Q: Should I define my lit:// path in the __init__ or the run method?**
-
- You can declare a Lightning path anywhere you'd like. However, the ``.get()`` and ``.exists_*()`` methods only work inside of the run method of a LightningWork.
-
-- **Q:How often does Lightning synchronize the files between my Work?**
-
- Lightning does not synchronize the files between works. It only transfers the files once when the Work ``run`` method starts.
- But you can call ``Path.get()`` as many times as you wish to transfer the latest file into the current Work.
-
-- **Does Lightning provide me direct access to the shared cloud folder?**
-
- No, and this is on purpose. This restriction forces developers to build modular components that can be shared and integrated
- into apps easily. This would be much harder to achieve if file paths in these components would reference a global shared storage.
-
-----
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Learn about the Drive Object.
- :description: Put, List and Get Files From a Shared Drive Disk.
- :col_css: col-md-4
- :button_link: drive.html
- :height: 180
- :tag: Basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/storage/storage.rst b/docs/source-app/glossary/storage/storage.rst
deleted file mode 100644
index af115a813fc80..0000000000000
--- a/docs/source-app/glossary/storage/storage.rst
+++ /dev/null
@@ -1,77 +0,0 @@
-.. _storage:
-
-#######
-Storage
-#######
-
-**Audience:** Users who want to share files between components.
-
-
-The Lightning Storage system makes it easy to share files between LightningWork so you can run your app both locally and in the cloud without changing the code.
-
-
-Lightning storage provides two solutions :class:`~lightning.app.storage.drive.Drive` and :class:`~lightning.app.storage.path.Path` to deal with files locally and in the cloud likewise.
-
-
-----
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Learn about the differences between Drive vs Path.
- :description: Learn about their differences.
- :col_css: col-md-4
- :button_link: differences.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: The Drive Object.
- :description: Put, List and Get Files From a Shared Drive Disk.
- :col_css: col-md-4
- :button_link: drive.html
- :height: 180
- :tag: Basic
-
-.. displayitem::
- :header: The Path Object.
- :description: Transfer Files From One Component to Another by Reference.
- :col_css: col-md-4
- :button_link: path.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
-
-
-----
-
-********
-Examples
-********
-
-
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Build a File Server
- :description: Learn how to use Drive to upload / download files to your app.
- :col_css: col-md-4
- :button_link: ../../examples/file_server/file_server.html
- :height: 180
- :tag: Intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/glossary/use_local_lightning.rst b/docs/source-app/glossary/use_local_lightning.rst
deleted file mode 100644
index 1efc8e730e97a..0000000000000
--- a/docs/source-app/glossary/use_local_lightning.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-################################################################
-How to run an app on the cloud with a local version of lightning
-################################################################
-
-The lightning cloud uses the latest release by default. However, you might want to run your app with some local changes you've made to the lightning framework. To use your local version of lightning on the cloud, set the following environment variable:
-
-```bash
-git clone https://github.com/Lightning-AI/lightning.git
-cd lightning
-pip install -e .
-export PACKAGE_LIGHTNING=1 # <- this is the magic to use your version (not mainstream PyPI)!
-lightning_app run app app.py --cloud
-```
-
-By setting `PACKAGE_LIGHTNING=1`, lightning packages the lightning source code in your local directory in addition to your app source code and uploads them to the cloud.
diff --git a/docs/source-app/index.rst b/docs/source-app/index.rst
deleted file mode 100644
index fb4258ad28e3a..0000000000000
--- a/docs/source-app/index.rst
+++ /dev/null
@@ -1,153 +0,0 @@
-.. lightning documentation master file, created by
- sphinx-quickstart on Sat Sep 19 16:37:02 2020.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-#######################
-Welcome to ⚡ Lightning
-#######################
-Build models, ML components and full stack AI apps ⚡ *Lightning fast*.
-
-**Featured examples of what you can do with Lightning:**
-
-|
-
-.. raw:: html
-
-
-
-
-.. app_card::
- :title: Develop and Train
- :description: Train a model (32 GPUs)
- :width: 280
- :image: https://lightning-ai-docs.s3.amazonaws.com/develop_n_train_v1.jpg
- :target: levels/basic/real_lightning_component_implementations.html#ex-pytorch-lightning-trainer
- :preview: levels/basic/real_lightning_component_implementations.html#ex-pytorch-lightning-trainer
- :tags: Training
-
-.. app_card::
- :title: Serve and deploy
- :description: Develop a Model Server
- :width: 280
- :image: https://lightning-ai-docs.s3.amazonaws.com/serve_n_deploy_v1.jpg
- :target: examples/model_server_app/model_server_app.html
- :preview: examples/model_server_app/model_server_app.html
- :tags: Serving
-
-.. app_card::
- :title: Scale and build a product
- :description: Production-ready generative AI app
- :width: 280
- :app_id: HvUwbEG90E
- :image: https://lightning-ai-docs.s3.amazonaws.com/scale_n_build_v1.jpg
- :target: https://lightning.ai/app/HvUwbEG90E-Muse
- :tags: AI App
-
-.. raw:: html
-
-
-
-
-----
-
-********************************
-Build self-contained, components
-********************************
-Use Lightning, the hyper-minimalistic framework, to build machine learning components that can plug into existing ML workflows.
-A Lightning component organizes arbitrary code to run on the cloud, manage its own infrastructure, cloud costs, networking, and more.
-Focus on component logic and not engineering.
-
-Use components on their own, or compose them into full-stack AI apps with our next-generation Lightning orchestrator.
-
-.. raw:: html
-
-
-
-
-
-|
-
-|
-
-**Run an example component on the cloud**:
-
-.. include:: ./levels/basic/hero_components.rst
-
-|
-
-Components run the same on the cloud and locally on your choice of hardware.
-
-.. lit_tabs::
- :code_files: landing_app_run.bash
- :highlights: 5
- :height: 150px
- :code_only: True
-
-Explore pre-built community components in `our gallery `_.
-
-|
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Get started
- :description: Learn to build Lightning components step-by-step.
- :col_css: col-md-12
- :button_link: levels/basic/index.html
- :height: 160
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
-
-.. raw:: html
-
-
-
-.. toctree::
- :maxdepth: 1
- :caption: Home
-
- self
- Install
-
-.. toctree::
- :maxdepth: 1
- :caption: Get started in steps
-
- Basic
- Intermediate
- Advanced
-
-.. toctree::
- :maxdepth: 1
- :caption: Core API Reference
-
- LightningApp
- LightningFlow
- LightningWork
-
-.. toctree::
- :maxdepth: 1
- :caption: Addons API Reference
-
- api_reference/components
- api_reference/frontend
- api_reference/runners
- api_reference/storage
-
-.. toctree::
- :maxdepth: 1
- :caption: More
-
- Examples
- Glossary
- How-to
diff --git a/docs/source-app/install/install_beginner.rst b/docs/source-app/install/install_beginner.rst
deleted file mode 100644
index f690ef74e7d2f..0000000000000
--- a/docs/source-app/install/install_beginner.rst
+++ /dev/null
@@ -1,117 +0,0 @@
-:orphan:
-
-.. _install_beginner:
-
-#############################
-What is a virtual environment
-#############################
-A virtual environment keeps the packages you install isolated from the rest of your system.
-This allows you to work on multiple projects that have different and potentially conflicting requirements, and it
-keeps your system Python installation clean.
-
-.. raw:: html
-
- VIDEO
-
-----
-
-We will describe two choices here, pick one:
-
-
-1. :ref:`Python virtualenv `.
-2. :ref:`Conda virtual environment `.
-
-----
-
-.. _python-virtualenv:
-
-********************
-1. Python Virtualenv
-********************
-
-First, make sure that you have Python 3.8+ installed on your system.
-
-.. code-block:: bash
-
- python3 --version
-
-If you can't run the command above or it returns a version older than 3.8,
-`install the latest version of Python `_.
-After installing it, make sure you can run the above command without errors.
-
-----
-
-Creating a Virtual Environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When starting with a new Python project, you typically want to create a new Python virtual environment.
-Navigate to the location of your project and run the following command:
-
-.. code-block:: bash
-
- python3 -m venv lightning
-
-The name of the environment here is *lightning* but you can choose any other name you like.
-By running the above command, Python will create a new folder *lightning* in the current working directory.
-
-----
-
-Activating the Virtual Environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Before you can install packages into the environment, you need to activate it:
-
-.. code-block:: bash
-
- source lightning/bin/activate
-
-You need to do this step every time you want to work on your project / open the terminal.
-With your virtual environment activated, you are now ready to
-:doc:`install Lightning ` and get started with Apps!
-
-----
-
-.. _conda:
-
-********
-2. Conda
-********
-
-To get started, you first need to download and install the `Miniconda package manager `_.
-To check that the installation was successful, open an new terminal and run:
-
-.. code:: bash
-
- conda
-
-It should return a list of commands.
-
-----
-
-Creating a Conda Environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When starting with a new Python project, you typically want to create a new Conda virtual environment.
-Navigate to the location of your project and run the following command:
-
-.. code-block:: bash
-
- conda create --yes --name lightning python=3.8
-
-The name of the environment here is *lightning* but you can choose any other name you like.
-Note how we can also specify the Python version here.
-
-----
-
-Activating the Conda Environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Before you can install packages into the environment, you need to activate it:
-
-.. code-block:: bash
-
- conda activate lightning
-
-You need to do this step every time you want to work on your project / open the terminal.
-With your virtual environment activated, you are now ready to
-:doc:`install Lightning ` and get started with Apps!
diff --git a/docs/source-app/install/installation.rst b/docs/source-app/install/installation.rst
deleted file mode 100644
index 294e26853007d..0000000000000
--- a/docs/source-app/install/installation.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-
-.. _install:
-
-
-############
-Installation
-############
-
-**Prerequisites**: Use Python 3.8.x or later (3.8.x, 3.9.x, 3.10.x). We also recommend you install in a virtual environment (learn how).
-
-.. lit_tabs::
- :descriptions: Pip; Macs, Apple Silicon (M1/M2/M3); Windows
- :code_files: pip.bash; mac.bash; windows.bash
- :tab_rows: 4
- :height: 180px
-
-----
-
-************
-Troubleshoot
-************
-If you encounter issues during installation join our community discord and share the output of the following command:
-
-.. code:: bash
-
- pip list | grep lightning
-
-.. join_slack::
- :align: left
diff --git a/docs/source-app/install/mac.bash b/docs/source-app/install/mac.bash
deleted file mode 100644
index 22825bb246530..0000000000000
--- a/docs/source-app/install/mac.bash
+++ /dev/null
@@ -1,5 +0,0 @@
-# needed for M1/M2/M3
-export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1
-export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1
-
-pip install lightning
diff --git a/docs/source-app/install/pip.bash b/docs/source-app/install/pip.bash
deleted file mode 100644
index f6d38b76b8107..0000000000000
--- a/docs/source-app/install/pip.bash
+++ /dev/null
@@ -1 +0,0 @@
-pip install lightning
diff --git a/docs/source-app/install/windows.bash b/docs/source-app/install/windows.bash
deleted file mode 100644
index 150b04e5b4d5d..0000000000000
--- a/docs/source-app/install/windows.bash
+++ /dev/null
@@ -1,4 +0,0 @@
-# install pip
-# install git
-# setup an alias for Python: python=python3
-# Add the root folder of Lightning to the Environment Variables to PATH
diff --git a/docs/source-app/intro.rst b/docs/source-app/intro.rst
deleted file mode 100644
index c975ee7090bbe..0000000000000
--- a/docs/source-app/intro.rst
+++ /dev/null
@@ -1,88 +0,0 @@
-:orphan:
-
-.. _what:
-
-###################
-What is Lightning?
-###################
-
-Lightning is a free, modular, distributed, and open-source framework for building
-AI applications where the components you want to use interact together.
-
-Lightning apps can be built for **any AI use case**, ranging from AI research to
-production-ready pipelines (and everything in between!).
-
-By abstracting the engineering boilerplate, Lightning allows researchers, data scientists, and software engineers to
-build highly-scalable, production-ready AI apps using the tools and technologies of their choice,
-regardless of their level of engineering expertise.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/Lightning.gif
- :alt: What is Lightning gif.
- :width: 100 %
-
-----
-
-.. _why:
-
-***************
-Why Lightning?
-***************
-
-
-Easy to learn
-^^^^^^^^^^^^^
-
-Lightning was built for creating AI apps, not for dev-ops. It offers an intuitive, pythonic
-and highly composable interface that allows you to focus on solving the problems that are important to you.
-
-----
-
-Quick to deliver
-^^^^^^^^^^^^^^^^
-
-Lightning speeds the development process by offering testable templates you can build from,
-accelerating the process of moving from idea to prototype and finally to market.
-
-----
-
-Easy to scale
-^^^^^^^^^^^^^
-
-Lightning provides a mirrored experience locally and in the cloud. The `lightning.ai `_.
-cloud platform abstracts the infrastructure, so you can run your apps at any scale.
-
-----
-
-Easy to collaborate
-^^^^^^^^^^^^^^^^^^^
-
-Lightning was built for collaboration.
-By following the best MLOps practices provided through our documentation and example use cases,
-you can deploy state-of-the-art ML applications that are ready to be used by teams of all sizes.
-
-----
-
-*****************************
-What's Novel With Lightning?
-*****************************
-
-
-Cloud Infra Made Simple and Pythonic
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Lightning is for building reactive, scalable, cost effective, easy-to-maintain and reliable ML products in the cloud without worrying about infrastructure. Lightning provides several engineering novelties to enable this:
-
-#. **Reactivity**: Lightning allows you to run stateful components distributed across different machines, so you can design async, dynamic and reactive workflows in python, without having to define DAGs.
-
-#. **Scalable & Cost-Effective**: Lightning provides a granular and simple way to run components preemptively or on-demand and on any desired resource such as CPU or GPU. It also enables you to easily transfer artifacts from one machine to another.
-
-#. **Reliability**:
-
- #. **Checkpointing**: Lightning apps can be paused and resumed from generated state and artifact-based checkpoints.
- #. **Resilience**: Lightning has a strong fault-tolerance foundation. Your application can be written and tested to be resilient for cloud hazards at the component level.
- #. **Testing Tools**: Lightning provides you with tools and best practices you can use to develop and test your application. All of our built-in templates have unit integration and end-to-end tests.
-
-#. **Easy to maintain**:
-
- #. **Easy Debugging**: Lightning apps can be debugged locally and in the cloud with **breakpoints** in any components.
- #. **Non-Invasive**: Lightning is the glue that connects all parts of your workflow, but this is done in a non-invasive way by formalizing API contracts between components. In other words, your application can run someone else's code with little assumption.
diff --git a/docs/source-app/landing_app.py b/docs/source-app/landing_app.py
deleted file mode 100644
index fa9429a1b53cd..0000000000000
--- a/docs/source-app/landing_app.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-# run on a cloud machine ("cpu", "gpu", ...)
-component = YourComponent(cloud_compute=CloudCompute("cpu"))
-app = LightningApp(component)
diff --git a/docs/source-app/landing_app_run.bash b/docs/source-app/landing_app_run.bash
deleted file mode 100644
index e4bed41dadddd..0000000000000
--- a/docs/source-app/landing_app_run.bash
+++ /dev/null
@@ -1,5 +0,0 @@
-# install lightning
-pip install lightning
-
-# run the app on the --cloud (--setup installs deps automatically)
-lightning_app run app app.py --setup --cloud
diff --git a/docs/source-app/levels/advanced/index.rst b/docs/source-app/levels/advanced/index.rst
deleted file mode 100644
index 4ba7d09d68ba7..0000000000000
--- a/docs/source-app/levels/advanced/index.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-.. _advanced_level:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- start_dynamic_components
- level_16
- level_17
- level_18
- level_19
- level_20
-
-###############
-Advanced skills
-###############
-Learn to build nested components with advanced functionality.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 9: Start dynamic components
- :description: Learn to start works dynamically
- :button_link: start_dynamic_components.html
- :col_css: col-md-6
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Level 10: Check component status
- :description: Learn to use work status to coordinate complex apps.
- :button_link: level_16.html
- :col_css: col-md-6
- :height: 150
- :tag: advanced
-
-.. displayitem::
- :header: Level: Nest flows
- :description: Learn to nest flows into other flows.
- :button_link: level_14.html
- :col_css: col-md-6
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Level: Develop reactive apps.
- :description: Learn to develop reactive Lightning Apps. Lightning shines with reactive workflows.
- :button_link: level_14.html
- :col_css: col-md-6
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Level: Enable CLI commands for your app
- :description: Speak to your app from a CLI over the network
- :button_link: level_17.html
- :col_css: col-md-6
- :height: 150
- :tag: advanced
-
-.. displayitem::
- :header: Level 11: Connect two components over the network
- :description: Connect two LightningWorks over the network.
- :button_link: level_14.html
- :col_css: col-md-6
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Level 13: Rerun components
- :description: Learn to reuse components by passing different variables.
- :button_link: level_17.html
- :col_css: col-md-6
- :height: 150
- :tag: advanced
-
-.. displayitem::
- :header: Level 14: Handle Lightning App exceptions
- :description: Learn to handle Lightning App exceptions.
- :button_link: level_19.html
- :col_css: col-md-6
- :height: 150
- :tag: advanced
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/advanced/level_16.rst b/docs/source-app/levels/advanced/level_16.rst
deleted file mode 100644
index 58c1e1fc7e0ad..0000000000000
--- a/docs/source-app/levels/advanced/level_16.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-###########################
-Level 16: Check Work status
-###########################
-**Audience:** Users who want to stop/start Lightning Work based on a status.
-
-**Prereqs:** Level 16+
-
-----
-
-.. include:: ../../core_api/lightning_work/status_content.rst
diff --git a/docs/source-app/levels/advanced/level_17.rst b/docs/source-app/levels/advanced/level_17.rst
deleted file mode 100644
index 6650860eaa177..0000000000000
--- a/docs/source-app/levels/advanced/level_17.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-##########################
-Level 17: Rerun components
-##########################
-**Audience:** Users who want Work.run() to activate multiple times in an app.
-
-**Prereqs:** Level 16+ and read the :doc:`Event Loop guide <../../glossary/event_loop>`.
-
-----
-
-.. include:: ../../workflows/run_work_once_content.rst
diff --git a/docs/source-app/levels/advanced/level_18.rst b/docs/source-app/levels/advanced/level_18.rst
deleted file mode 100644
index 87fba3eb8bcc9..0000000000000
--- a/docs/source-app/levels/advanced/level_18.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-##############################################
-Level 18: Share objects between LightningWorks
-##############################################
-**Audience:** Users moving DataFrames or outputs, between Lightning Works (usually data engineers).
-
-**Prereqs:** Level 16+ and know about the Pandas library and read the :doc:`Access app state guide <../../workflows/access_app_state>`.
-
-----
-
-.. include:: ../../core_api/lightning_work/payload_content.rst
diff --git a/docs/source-app/levels/advanced/level_19.rst b/docs/source-app/levels/advanced/level_19.rst
deleted file mode 100644
index 99a859e1ad2ca..0000000000000
--- a/docs/source-app/levels/advanced/level_19.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-#########################################
-Level 19: Handle Lightning App exceptions
-#########################################
-
-**Audience:** Users who want to make Lightning Apps more robust to potential issues.
-
-**Prereqs:** Level 16+
-
-----
-
-.. include:: ../../core_api/lightning_work/handling_app_exception_content.rst
diff --git a/docs/source-app/levels/advanced/level_20.rst b/docs/source-app/levels/advanced/level_20.rst
deleted file mode 100644
index 1d045e85fc692..0000000000000
--- a/docs/source-app/levels/advanced/level_20.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-#######################################
-Level 20: Enable dynamic LightningWorks
-#######################################
-
-**Audience:** Users who want to create/run/stop multiple LightningWorks not defined at app instantiation.
-
-**Prereqs:** Level 16+
-
-----
-
-.. include:: ../../core_api/lightning_app/dynamic_work_content.rst
diff --git a/docs/source-app/levels/advanced/start_dynamic_components.rst b/docs/source-app/levels/advanced/start_dynamic_components.rst
deleted file mode 100644
index 2e91bc2f632f0..0000000000000
--- a/docs/source-app/levels/advanced/start_dynamic_components.rst
+++ /dev/null
@@ -1,38 +0,0 @@
-###############################
-Level: Start dynamic components
-###############################
-**Audience:** Users who want to run a Lightning Component in parallel (asynchronously).
-
-**Prereqs:** You must have finished the :doc:`Basic levels <../basic/index>`.
-
-----
-
-.. include:: ../../workflows/run_work_in_parallel_content.rst
-
-----
-
-**********************************************
-Next steps: Share variables between components
-**********************************************
-Now that you know how to run components in parallel, we'll learn to share variables
-across components to simplify complex workflows.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 6: Share variables between components
- :description: Learn to connect components
- :col_css: col-md-12
- :button_link: share_variables_between_lightning_components.html
- :height: 150
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/basic/build_a_dag.rst b/docs/source-app/levels/basic/build_a_dag.rst
deleted file mode 100644
index e430306447797..0000000000000
--- a/docs/source-app/levels/basic/build_a_dag.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-:orphan:
-
-###########################
-Example: Deploy a model API
-###########################
-
-**Prereqs:** You have an app already running locally.
-
-----
-
-****************************
-What is the Lightning Cloud?
-****************************
-The Lightning Cloud is the platform that we've created to interface with the cloud providers. Today
-the Lightning Cloud supports AWS.
-
-.. note:: Support for GCP and Azure is coming soon!
-
-To use the Lightning Cloud, you buy credits that are used to pay the cloud providers. If you want to run
-on your own AWS credentials, please contact us (support@lightning.ai) so we can get your clusters set up for you.
diff --git a/docs/source-app/levels/basic/build_a_lightning_component.rst b/docs/source-app/levels/basic/build_a_lightning_component.rst
deleted file mode 100644
index 00b777e150813..0000000000000
--- a/docs/source-app/levels/basic/build_a_lightning_component.rst
+++ /dev/null
@@ -1,154 +0,0 @@
-##############################################
-Level 1: Package code in a lightning component
-##############################################
-
-**Prereqs:** You know *basic* Python.
-
-**Goal:** In this guide you'll learn to develop `a Lightning component `_.
-
-
-*********************************
-Why you need Lightning components
-*********************************
-A Lightning component is a self-contained, modular machine-learning component
-that you can plug into your existing ML workflows. A Lightning component organizes arbitrary code so it can run on the cloud, manages
-its own infrastructure, cloud costs, networking and more. Connect components using your current workflow management tools or
-our :doc:`next-generation reactive orchestrator <../intermediate/index>`.
-
-Components run on the cloud or your laptop without code changes 🤯🤯.
-
-.. raw:: html
-
-
-
-
-
-|
-
-Organizing your code into Lightning components offers these benefits:
-
-.. collapse:: Build systems not scripts
-
- |
-
- The Lightning structure forces best practices so you don't have to be an expert production engineer.
- Although it feels like you're writing a script, you are actually building a production-ready system.
-
-.. collapse:: Cost control
-
- |
-
- The component run-time has been optimized for cost management to support the largest machine-learning workloads.
- Lower your cloud bill with machines that shut down or spin up faster.
-
-.. collapse:: For beginners: Code like an expert
-
- |
-
- Lightning embeds the best practices of building production-ready full stack AI apps into your
- coding experience. You can write code like you normally do, and the Lightning structure
- ensures your code is implicitly production ready... even if you're just doing research.
-
-.. collapse:: For experts: Scale with full control
-
- |
-
- if you know what you are doing, Lightning gives you full control to manage your own
- scaling logic, fault-tolerance and even pre-provisioning, all from Python.
-
-.. collapse:: Integrate into your current workflow tools
-
- |
-
- Lightning components are self-contained pieces of functionality. Add them to your current workflow
- tools to quickly fill in gaps in your ML workflow such as monitoring drift, training LLMs and more.
- You can (optionally) use the Lightning App to integrate components into a cohesive workflow.
-
-.. collapse:: Packaged code
-
- |
-
- Lightning apps bundles components into an app that runs in any environment. The same code will run on your laptop,
- or any cloud or private clusters. You don't have to think about the cluster or know anything about the cloud.
-
-.. collapse:: Rapid iteration
-
- |
-
- Iterate through ideas in hours not months because you don't have to learn a million other concepts that the components
- handle for you such as kubernetes, cost management, auto-scaling and more.
-
-.. collapse:: Modularity
-
- |
-
- Components are modular and inter-operable by design. Leverage our vibrant community of components so you don't
- have to build each piece of the system yourself.
-
-----
-
-*****************
-Install Lightning
-*****************
-First, install Lightning.
-
-.. lit_tabs::
- :descriptions: Pip; Macs, Apple Silicon (M1/M2/M3); Windows
- :code_files: /install/pip.bash; /install/mac.bash; /install/windows.bash
- :tab_rows: 4
- :height: 180px
-
-----
-
-**************************
-Build your first component
-**************************
-A Lightning component organizes arbitrary code so it can run on the cloud, manages its own infrastructure, cloud costs, networking and more
-
-**Run one of these components!**
-
-.. include:: ./hero_components.rst
-
-----
-
-************
-Key features
-************
-You now know enough to build a self-contained component that runs any Python code on the cloud that can be connected to form a
-powerful Lightning app. Here are a few key features available to super-charge your work:
-
-.. lit_tabs::
- :titles: 15+ accelerators; Auto-stop idle machines; Auto-timeout submitted work; Use spot machines (~70% discount); Work with massive datasets; Mount cloud storage; Use a custom container
- :code_files: ./key_features/accelerators.py; ./key_features/idle_machine.py; ./key_features/auto_timeout.py; ./key_features/spot.py; ./key_features/massive_dataset.py; ./key_features/mount_data.py; ./key_features/custom_container.py;
- :highlights: 11;11;11;11;11;2,7,10, 11; 11
- :enable_run: true
- :tab_rows: 3
- :height: 430px
-
-----
-
-********************************************
-Next: Explore real component implementations
-********************************************
-In this section we introduced components. Let's explore
-real component implementations in-depth.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 2: Explore real component implementations
- :description: Go deep into real component implementations.
- :col_css: col-md-12
- :button_link: real_lightning_component_implementations.html
- :height: 150
- :tag: beginner
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/basic/create_a_model_demo.rst b/docs/source-app/levels/basic/create_a_model_demo.rst
deleted file mode 100644
index 72750717efde4..0000000000000
--- a/docs/source-app/levels/basic/create_a_model_demo.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-:orphan:
-
-############################
-Example: Create a model demo
-############################
-
-**Prereqs:** You have an app already running locally.
-
-----
-
-****************************
-What is the Lightning Cloud?
-****************************
-The Lightning Cloud is the platform that we've created to interface with the cloud providers. Today
-the Lightning Cloud supports AWS.
-
-.. note:: Support for GCP and Azure is coming soon!
-
-To use the Lightning Cloud, you buy credits that are used to pay the cloud providers. If you want to run
-on your own AWS credentials, please contact us (support@lightning.ai) so we can get your clusters set up for you.
diff --git a/docs/source-app/levels/basic/deploy_ai_model_api.rst b/docs/source-app/levels/basic/deploy_ai_model_api.rst
deleted file mode 100644
index e430306447797..0000000000000
--- a/docs/source-app/levels/basic/deploy_ai_model_api.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-:orphan:
-
-###########################
-Example: Deploy a model API
-###########################
-
-**Prereqs:** You have an app already running locally.
-
-----
-
-****************************
-What is the Lightning Cloud?
-****************************
-The Lightning Cloud is the platform that we've created to interface with the cloud providers. Today
-the Lightning Cloud supports AWS.
-
-.. note:: Support for GCP and Azure is coming soon!
-
-To use the Lightning Cloud, you buy credits that are used to pay the cloud providers. If you want to run
-on your own AWS credentials, please contact us (support@lightning.ai) so we can get your clusters set up for you.
diff --git a/docs/source-app/levels/basic/hello_components/code_run_cloud.bash b/docs/source-app/levels/basic/hello_components/code_run_cloud.bash
deleted file mode 100644
index f81431222724c..0000000000000
--- a/docs/source-app/levels/basic/hello_components/code_run_cloud.bash
+++ /dev/null
@@ -1 +0,0 @@
-lightning_app run app app.py --cloud
diff --git a/docs/source-app/levels/basic/hello_components/code_run_cloud_setup.bash b/docs/source-app/levels/basic/hello_components/code_run_cloud_setup.bash
deleted file mode 100644
index 09435ff5d7caa..0000000000000
--- a/docs/source-app/levels/basic/hello_components/code_run_cloud_setup.bash
+++ /dev/null
@@ -1 +0,0 @@
-lightning_app run app app.py --setup --cloud
diff --git a/docs/source-app/levels/basic/hello_components/code_run_local.bash b/docs/source-app/levels/basic/hello_components/code_run_local.bash
deleted file mode 100644
index 1f8c994a84b1d..0000000000000
--- a/docs/source-app/levels/basic/hello_components/code_run_local.bash
+++ /dev/null
@@ -1 +0,0 @@
-lightning_app run app app.py
diff --git a/docs/source-app/levels/basic/hello_components/code_run_local_setup.bash b/docs/source-app/levels/basic/hello_components/code_run_local_setup.bash
deleted file mode 100644
index 45b0529de3736..0000000000000
--- a/docs/source-app/levels/basic/hello_components/code_run_local_setup.bash
+++ /dev/null
@@ -1 +0,0 @@
-lightning_app run app app.py --setup
diff --git a/docs/source-app/levels/basic/hello_components/deploy_model.py b/docs/source-app/levels/basic/hello_components/deploy_model.py
deleted file mode 100644
index 7911f6a3158ac..0000000000000
--- a/docs/source-app/levels/basic/hello_components/deploy_model.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# !pip install torchvision
-from lightning.app import LightningApp, CloudCompute
-from lightning.app.components.serve import PythonServer, Image, Number
-import base64, io, torchvision, torch
-from PIL import Image as PILImage
-
-
-class PyTorchServer(PythonServer):
- def setup(self):
- self._model = torchvision.models.resnet18(pretrained=True)
- self._device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- self._model.to(self._device)
-
- def predict(self, request):
- image = base64.b64decode(request.image.encode("utf-8"))
- image = PILImage.open(io.BytesIO(image))
- transforms = torchvision.transforms.Compose([
- torchvision.transforms.Resize(224),
- torchvision.transforms.ToTensor(),
- torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
- ])
- image = transforms(image)
- image = image.to(self._device)
- prediction = self._model(image.unsqueeze(0))
- return {"prediction": prediction.argmax().item()}
-
-
-component = PyTorchServer(
- input_type=Image, output_type=Number, cloud_compute=CloudCompute('gpu')
-)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/hello_world.py b/docs/source-app/levels/basic/hello_components/hello_world.py
deleted file mode 100644
index a716fecb2fb2c..0000000000000
--- a/docs/source-app/levels/basic/hello_components/hello_world.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-
-component = YourComponent()
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/hello_world_gpu.py b/docs/source-app/levels/basic/hello_components/hello_world_gpu.py
deleted file mode 100644
index 67e5d92fb3666..0000000000000
--- a/docs/source-app/levels/basic/hello_components/hello_world_gpu.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-# run on a cloud machine ("cpu", "gpu", ...)
-compute = CloudCompute("gpu")
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/multi_node.py b/docs/source-app/levels/basic/hello_components/multi_node.py
deleted file mode 100644
index 03ce0fbec2341..0000000000000
--- a/docs/source-app/levels/basic/hello_components/multi_node.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# !pip install torch
-from lightning.app import LightningWork, LightningApp, CloudCompute
-from lightning.app.components import MultiNode
-
-
-class MultiNodeComponent(LightningWork):
- def run(
- self,
- main_address: str,
- main_port: int,
- node_rank: int,
- world_size: int,
- ):
- print(f"ADD YOUR DISTRIBUTED CODE: {main_address=} {main_port=} {node_rank=} {world_size=}")
- print("supports ANY ML library")
-
-
-
-
-
-
-
-
-
-
-# gpu-multi-fast has 4 GPUs x 8 nodes = 32 GPUs
-component = MultiNodeComponent(cloud_compute=CloudCompute("gpu-multi-fast"))
-component = MultiNode(component, nodes=8)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/pl_multinode.py b/docs/source-app/levels/basic/hello_components/pl_multinode.py
deleted file mode 100644
index 09480da44eee6..0000000000000
--- a/docs/source-app/levels/basic/hello_components/pl_multinode.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# app.py
-from lightning import Trainer
-from lightning.app import LightningWork, LightningApp, CloudCompute
-from lightning.app.components import LightningTrainerMultiNode
-from lightning.pytorch.demos.boring_classes import BoringModel
-
-
-class LightningTrainerDistributed(LightningWork):
- def run(self):
- model = BoringModel()
- trainer = Trainer(max_epochs=10, strategy="ddp")
- trainer.fit(model)
-
-# 8 GPUs: (2 nodes of 4 x v100)
-component = LightningTrainerMultiNode(
- LightningTrainerDistributed,
- num_nodes=4,
- cloud_compute=CloudCompute("gpu-fast-multi"), # 4 x v100
-)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/pt_multinode.py b/docs/source-app/levels/basic/hello_components/pt_multinode.py
deleted file mode 100644
index 569a7c99b4d0c..0000000000000
--- a/docs/source-app/levels/basic/hello_components/pt_multinode.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# app.py
-# ! pip install torch
-from lightning.app import LightningWork, LightningApp, CloudCompute
-from lightning.app.components import MultiNode
-import torch
-from torch.nn.parallel.distributed import DistributedDataParallel
-
-
-def distributed_train(local_rank: int, main_address: str, main_port: int, num_nodes: int, node_rank: int, nprocs: int):
- # 1. SET UP DISTRIBUTED ENVIRONMENT
- global_rank = local_rank + node_rank * nprocs
- world_size = num_nodes * nprocs
-
- if torch.distributed.is_available() and not torch.distributed.is_initialized():
- torch.distributed.init_process_group(
- "nccl" if torch.cuda.is_available() else "gloo",
- rank=global_rank,
- world_size=world_size,
- init_method=f"tcp://{main_address}:{main_port}",
- )
-
- # 2. PREPARE DISTRIBUTED MODEL
- model = torch.nn.Linear(32, 2)
- device = torch.device(f"cuda:{local_rank}") if torch.cuda.is_available() else torch.device("cpu")
- model = DistributedDataParallel(model, device_ids=[local_rank] if torch.cuda.is_available() else None).to(device)
-
- # 3. SETUP LOSS AND OPTIMIZER
- criterion = torch.nn.MSELoss()
- optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
-
- # 4.TRAIN THE MODEL FOR 50 STEPS
- for step in range(50):
- model.zero_grad()
- x = torch.randn(64, 32).to(device)
- output = model(x)
- loss = criterion(output, torch.ones_like(output))
- print(f"global_rank: {global_rank} step: {step} loss: {loss}")
- loss.backward()
- optimizer.step()
-
- # 5. VERIFY ALL COPIES OF THE MODEL HAVE THE SAME WEIGTHS AT END OF TRAINING
- weight = model.module.weight.clone()
- torch.distributed.all_reduce(weight)
- assert torch.equal(model.module.weight, weight / world_size)
-
- print("Multi Node Distributed Training Done!")
-
-class PyTorchDistributed(LightningWork):
- def run(self, main_address: str, main_port: int, num_nodes: int, node_rank: int):
- nprocs = torch.cuda.device_count() if torch.cuda.is_available() else 1
- torch.multiprocessing.spawn(
- distributed_train,
- args=(main_address, main_port, num_nodes, node_rank, nprocs),
- nprocs=nprocs
- )
-
-# 32 GPUs: (8 nodes x 4 v 100)
-compute = CloudCompute("gpu-fast-multi") # 4xV100
-component = MultiNode(PyTorchDistributed, num_nodes=8, cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/run_ptl_script.py b/docs/source-app/levels/basic/hello_components/run_ptl_script.py
deleted file mode 100644
index 46501310e2aa7..0000000000000
--- a/docs/source-app/levels/basic/hello_components/run_ptl_script.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# app.py
-# !curl https://raw.githubusercontent.com/Lightning-AI/lightning/master/examples/app/multi_node/pl_boring_script.py -o pl_boring_script.py
-from lightning.app import LightningApp, CloudCompute
-from lightning.app.components.training import LightningTrainerScript
-
-# run script that trains PyTorch with the Lightning Trainer
-model_script = 'pl_boring_script.py'
-component = LightningTrainerScript(
- model_script,
- num_nodes=1,
- cloud_compute=CloudCompute("gpu")
-)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/hello_components/streamlit_demo.py b/docs/source-app/levels/basic/hello_components/streamlit_demo.py
deleted file mode 100644
index 41ad3988d908d..0000000000000
--- a/docs/source-app/levels/basic/hello_components/streamlit_demo.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# app.py
-# !pip install streamlit omegaconf scipy
-# !pip install torch
-from lightning.app import LightningApp
-import torch
-from io import BytesIO
-from functools import partial
-from scipy.io.wavfile import write
-import streamlit as st
-
-
-class StreamlitApp(app.components.ServeStreamlit):
- def build_model(self):
- sample_rate = 48000
- model, _ = torch.hub.load('snakers4/silero-models', model='silero_tts',speaker="v3_en")
- return partial(model.apply_tts, sample_rate=sample_rate, speaker="en_0"), sample_rate
-
- def render(self):
- st.title("Text To Speech")
- text = st.text_input("Text:", "Lightning Apps are the best!")
-
- if text:
- model, sample_rate = self.model
- audio_numpy = model(text).numpy()
- audio = BytesIO()
- write(audio, sample_rate, audio_numpy)
- audio.seek(0)
- st.audio(audio)
-
-app = LightningApp(StreamlitApp())
diff --git a/docs/source-app/levels/basic/hello_components/train_ptl.py b/docs/source-app/levels/basic/hello_components/train_ptl.py
deleted file mode 100644
index 3fa9684f9213b..0000000000000
--- a/docs/source-app/levels/basic/hello_components/train_ptl.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# A hello world component
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-
-# run on a cloud machine
-compute = CloudCompute("cpu")
-worker = YourComponent(cloud_compute=compute)
-app = LightningApp(worker)
diff --git a/docs/source-app/levels/basic/hello_components/train_pytorch.py b/docs/source-app/levels/basic/hello_components/train_pytorch.py
deleted file mode 100644
index fa54c577bd19b..0000000000000
--- a/docs/source-app/levels/basic/hello_components/train_pytorch.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# app.py
-# ! pip install torch
-from lightning.app import LightningWork, LightningApp, CloudCompute
-import torch
-
-class PyTorchComponent(LightningWork):
- def run(self):
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- model = torch.nn.Sequential(torch.nn.Linear(1, 1),
- torch.nn.ReLU(),
- torch.nn.Linear(1, 1))
- model.to(device)
- criterion = torch.nn.MSELoss()
- optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
-
- for step in range(10000):
- model.zero_grad()
- x = torch.tensor([0.8]).to(device)
- target = torch.tensor([1.0]).to(device)
- output = model(x)
- loss = criterion(output, target)
- print(f'step: {step}. loss {loss}')
- loss.backward()
- optimizer.step()
-
-compute = CloudCompute('gpu')
-componet = PyTorchComponent(cloud_compute=compute)
-app = LightningApp(componet)
diff --git a/docs/source-app/levels/basic/hello_components/xgboost.py b/docs/source-app/levels/basic/hello_components/xgboost.py
deleted file mode 100644
index 68cc2c181e050..0000000000000
--- a/docs/source-app/levels/basic/hello_components/xgboost.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# app.py
-# !pip install scikit-learn xgboost
-from lightning.app import LightningWork, LightningApp
-from sklearn import datasets
-from sklearn.model_selection import train_test_split
-from xgboost import XGBClassifier
-
-class XGBoostComponent(LightningWork):
- def run(self):
- iris = datasets.load_iris()
- X, y = iris.data, iris.target
-
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
-
- bst = XGBClassifier(verbosity=3)
- bst.fit(X_train, y_train)
- preds = bst.predict(X_test)
- print(f'preds: {preds}')
-
-
-app = LightningApp(XGBoostComponent())
diff --git a/docs/source-app/levels/basic/hello_components/xgboost_gpu.py b/docs/source-app/levels/basic/hello_components/xgboost_gpu.py
deleted file mode 100644
index f8058a3169e34..0000000000000
--- a/docs/source-app/levels/basic/hello_components/xgboost_gpu.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# app.py
-# !pip install sklearn xgboost
-# !conda install py-xgboost-gpu
-from lightning.app import LightningWork, LightningApp, CloudCompute
-from sklearn import datasets
-from sklearn.model_selection import train_test_split
-from xgboost import XGBClassifier
-
-class XGBoostComponent(LightningWork):
- def run(self):
- iris = datasets.load_iris()
- X, y = iris.data, iris.target
-
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
-
- bst = XGBClassifier(tree_method='gpu_hist', gpu_id=0, verbosity=3)
- bst.fit(X_train, y_train)
- preds = bst.predict(X_test)
- print(f'preds: {preds}')
-
-compute = CloudCompute('gpu')
-app = LightningApp(XGBoostComponent(cloud_compute=compute))
diff --git a/docs/source-app/levels/basic/hero_components.rst b/docs/source-app/levels/basic/hero_components.rst
deleted file mode 100644
index da1d9d076a794..0000000000000
--- a/docs/source-app/levels/basic/hero_components.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-.. lit_tabs::
- :titles: Hello world; Hello GPU world; PyTorch & ⚡⚡⚡ Trainer (1+ cloud GPUs); Train PyTorch (cloud GPU); Train PyTorch (32 cloud GPUs); Deploy a model on cloud GPUs; Run a model script; XGBoost; Streamlit demo
- :code_files: /levels/basic/hello_components/hello_world.py; /levels/basic/hello_components/hello_world_gpu.py; /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/train_pytorch.py; /levels/basic/hello_components/pt_multinode.py; /levels/basic/hello_components/deploy_model.py; /levels/basic/hello_components/run_ptl_script.py; /levels/basic/hello_components/xgboost.py; /levels/basic/hello_components/streamlit_demo.py
- :highlights: 7; 10, 11; 9-11, 16, 17; 4, 8, 12, 18-19, 26; 5, 10, 22, 27, 31, 41, 57-59; 3, 11-12, 25, 29; 7, 10; 15, 21; 9, 15, 24
- :works: [{"name":"root.work","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"default","preemptible":false,"shmSize":0},"networkConfig":[{"name":"dzodf","port":61304}]}}];[{"name":"root.work","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"qnlgd","port":61516}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu","preemptible":false,"shmSize":0}}}];[{"name":"root.ws.0","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"ajfrc","port":61553}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.1","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"ttyqc","port":61554}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.2","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"svyej","port":61555}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.3","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"parme","port":61556}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}}];[{"name":"root.work","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"cutdu","port":61584}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu","preemptible":false,"shmSize":0}}}];[{"name":"root.ws.0","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"whhby","port":61613}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.1","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"yhjtf","port":61614}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.2","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"rqwkt","port":61615}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.3","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"pjdsj","port":61616}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.4","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"efdor","port":61617}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.5","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"pxmso","port":61618}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.6","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"feevy","port":61619}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}},{"name":"root.ws.7","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"tbmse","port":61620}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu-fast-multi","preemptible":false,"shmSize":0}}}];[{"name":"root.work","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"umqqg","port":7777}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"gpu","preemptible":false,"shmSize":0}}}];[];[{"name":"root.work","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"tggba","port":61729}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"default","preemptible":false,"shmSize":0}}}];[{"name":"root.work","spec":{"buildSpec":{"commands":[],"pythonDependencies":{"packageManager":"PACKAGE_MANAGER_PIP","packages":""}},"drives":[],"networkConfig":[{"name":"hpyaz","port":61763}],"userRequestedComputeConfig":{"count":1,"diskSize":0,"name":"default","preemptible":false,"shmSize":0}}}]
- :enable_run: true
- :tab_rows: 3
- :height: 620px
diff --git a/docs/source-app/levels/basic/index.rst b/docs/source-app/levels/basic/index.rst
deleted file mode 100644
index 2912b69b6b7ff..0000000000000
--- a/docs/source-app/levels/basic/index.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-.. _level_basic:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- build_a_lightning_component
- real_lightning_component_implementations
- save_money_on_cloud_costs
-
-############
-Basic skills
-############
-Learn to package your code into Lightning components which can plug into your existing ML workflows.
-
-A Lightning component organizes arbitrary code so it can run on the cloud, manages
-its own infrastructure, cloud costs, networking and more.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 1: Package code in a Lightning component
- :description: Learn to package your code into Lightning components which can plug into your existing ML workflows.
- :button_link: build_a_lightning_component.html
- :col_css: col-md-6
- :height: 170
- :tag: 10 minutes
-
-.. displayitem::
- :header: Level 2: Explore real component implementations
- :description: Go deep into real component implementations.
- :button_link: real_lightning_component_implementations.html
- :col_css: col-md-6
- :height: 170
- :tag: 10 minutes
-
-.. displayitem::
- :header: Level 3: Save money on cloud costs
- :description: Explore key Lightning features that save you cloud costs and improve performance.
- :button_link: save_money_on_cloud_costs.html
- :col_css: col-md-6
- :height: 150
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/basic/key_features/accelerators.py b/docs/source-app/levels/basic/key_features/accelerators.py
deleted file mode 100644
index fd27325fda74e..0000000000000
--- a/docs/source-app/levels/basic/key_features/accelerators.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-# custom accelerators
-compute = CloudCompute('gpu')
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
-
-# OTHER ACCELERATORS:
-# compute = CloudCompute('default') # 1 CPU
-# compute = CloudCompute('cpu-medium') # 8 CPUs
-# compute = CloudCompute('gpu') # 1 T4 GPU
-# compute = CloudCompute('gpu-fast-multi') # 4 V100 GPU
-# compute = CloudCompute('p4d.24xlarge') # AWS instance name (8 A100 GPU)
-# compute = ...
diff --git a/docs/source-app/levels/basic/key_features/auto_timeout.py b/docs/source-app/levels/basic/key_features/auto_timeout.py
deleted file mode 100644
index 73d6281b602d5..0000000000000
--- a/docs/source-app/levels/basic/key_features/auto_timeout.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-# if the machine hasn't started after 60 seconds, cancel the work
-compute = CloudCompute('gpu', wait_timeout=60)
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/key_features/custom_container.py b/docs/source-app/levels/basic/key_features/custom_container.py
deleted file mode 100644
index 5e574d43d90a8..0000000000000
--- a/docs/source-app/levels/basic/key_features/custom_container.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-# custom image (from any provider)
-config= BuildConfig(image="gcr.io/google-samples/hello-app:1.0")
-component = YourComponent(cloud_build_config=config)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/key_features/idle_machine.py b/docs/source-app/levels/basic/key_features/idle_machine.py
deleted file mode 100644
index 89ab43355c8e6..0000000000000
--- a/docs/source-app/levels/basic/key_features/idle_machine.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-# stop the machine when idle for 10 seconds
-compute = CloudCompute('gpu', idle_timeout=10)
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/key_features/massive_dataset.py b/docs/source-app/levels/basic/key_features/massive_dataset.py
deleted file mode 100644
index 2c12b9cb4e8f7..0000000000000
--- a/docs/source-app/levels/basic/key_features/massive_dataset.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-
-# use 100 GB of space on that machine (max size: 64 TB)
-compute = CloudCompute('gpu', disk_size=100)
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/key_features/mount_data.py b/docs/source-app/levels/basic/key_features/mount_data.py
deleted file mode 100644
index e6096d7e925d2..0000000000000
--- a/docs/source-app/levels/basic/key_features/mount_data.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from lightning.app import LightningWork, LightningApp, CloudCompute
-import os
-
-
-class YourComponent(LightningWork):
- def run(self):
- os.listdir('/foo')
-
-# mount the files on the s3 bucket under this path
-mount = Mount(source="s3://lightning-example-public/", mount_path="/foo")
-compute = CloudCompute(mounts=mount)
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/key_features/spot.py b/docs/source-app/levels/basic/key_features/spot.py
deleted file mode 100644
index b1a0291eeacee..0000000000000
--- a/docs/source-app/levels/basic/key_features/spot.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningApp, CloudCompute
-
-
-class YourComponent(LightningWork):
- def run(self):
- print('RUN ANY PYTHON CODE HERE')
-
-# spot machines can be turned off without notice, use for non-critical, resumable work
-# request a spot machine, after 60 seconds of waiting switch to full-price
-compute = CloudCompute('gpu', wait_timeout=60, spot=True)
-component = YourComponent(cloud_compute=compute)
-app = LightningApp(component)
diff --git a/docs/source-app/levels/basic/real_lightning_component_implementations.rst b/docs/source-app/levels/basic/real_lightning_component_implementations.rst
deleted file mode 100644
index 391bda9ada605..0000000000000
--- a/docs/source-app/levels/basic/real_lightning_component_implementations.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-###############################################
-Level 2: Explore real component implementations
-###############################################
-**Audience:** Users who want to deeply understand what is possible with Lightning components.
-
-**Prereqs:** You must have finished :doc:`level 1 <../basic/build_a_lightning_component>`.
-
-----
-
-***********************
-Real component examples
-***********************
-Use this guide to understand what is happening in each type of component.
-These are a few prototypical components. Since each component organizes
-Python, you can build virtually infinite components for any use-case
-you can think of.
-
-----
-
-*******************************
-Ex: PyTorch + Lightning Trainer
-*******************************
-This example shows how to train PyTorch with the Lightning trainer on your machine
-or cloud GPUs without code changes.
-
-.. lit_tabs::
- :descriptions: import Lightning; We're using a demo LightningModule; Move your training code here (usually your main.py); Pass your component to the multi-node executor (it works on CPU or single GPUs also); Select the number of machines (nodes). Here we choose 4.; Choose from over 15+ machine types. This one has 4 v100 GPUs.; Initialize the App object that executes the component logic.
- :code_files: /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/pl_multinode.py; /levels/basic/hello_components/pl_multinode.py;
- :highlights: 2; 4; 9-11; 14-17; 16; 17; 19
- :enable_run: true
- :tab_rows: 5
- :height: 420px
-
-----
-
-*********************************
-Ex: Deploy a PyTorch API endpoint
-*********************************
-This example shows how to deploy PyTorch and create an API
-
-.. lit_tabs::
- :descriptions: Shortcut to list dependencies without a requirements.txt file.; Import one of our serving components (high-performance ones are available on the enterprise tiers); Define the setup function to load your favorite pretrained models and do any kind of pre-processing.; Define the predict function which is called when the endpoint is hit.; Initialize the server and define the type of cloud machine to use.
- :code_files: /levels/basic/hello_components/deploy_model.py; /levels/basic/hello_components/deploy_model.py; /levels/basic/hello_components/deploy_model.py; /levels/basic/hello_components/deploy_model.py; /levels/basic/hello_components/deploy_model.py;
- :highlights: 1; 3; 10-12; 15-25; 28-30
- :enable_run: true
- :tab_rows: 4
- :height: 620px
-
-----
-
-*************************
-Next: Save on cloud costs
-*************************
-Let's review key lightning features to help you run components more efficiently on the cloud
-so you can save on cloud costs.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 3: Save money on cloud costs
- :description: Explore key Lightning features that save you cloud costs and improve performance.
- :button_link: save_money_on_cloud_costs.html
- :col_css: col-md-12
- :height: 150
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/basic/run_jupyter_notebook_on_the_cloud.rst b/docs/source-app/levels/basic/run_jupyter_notebook_on_the_cloud.rst
deleted file mode 100644
index 9bc5cdd5207ef..0000000000000
--- a/docs/source-app/levels/basic/run_jupyter_notebook_on_the_cloud.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-:orphan:
-
-#############################################
-Example: Develop a Jupyter Notebook component
-#############################################
-
-**Prereqs:** You have an app already running locally.
-
-----
-
-****************************
-What is the Lightning Cloud?
-****************************
-The Lightning Cloud is the platform that we've created to interface with the cloud providers. Today
-the Lightning Cloud supports AWS.
-
-.. note:: Support for GCP and Azure is coming soon!
-
-To use the Lightning Cloud, you buy credits that are used to pay the cloud providers. If you want to run
-on your own AWS credentials, please contact us (support@lightning.ai) so we can get your clusters set up for you.
diff --git a/docs/source-app/levels/basic/save_money_on_cloud_costs.rst b/docs/source-app/levels/basic/save_money_on_cloud_costs.rst
deleted file mode 100644
index b2ff007ca8bba..0000000000000
--- a/docs/source-app/levels/basic/save_money_on_cloud_costs.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-##################################
-Level 3: Save money on cloud costs
-##################################
-**Audience:** Users who want to use the AWS cloud efficiently.
-
-**Prereqs:** You must have finished :doc:`level 1 <../basic/build_a_lightning_component>`.
-
-----
-
-***********************************
-Save money with these optimizations
-***********************************
-A Lightning component gives you fine-grain control over the cloud lifecycle of that component.
-
-Here are a few features that will enable you save a lot on your cloud costs:
-
-.. lit_tabs::
- :titles: 15+ accelerators; Auto-stop idle machines; Auto-timeout submitted work; Use spot machines (~70% discount); Work with massive datasets; Mount cloud storage; Use a custom container
- :code_files: ./key_features/accelerators.py; ./key_features/idle_machine.py; ./key_features/auto_timeout.py; ./key_features/spot.py; ./key_features/massive_dataset.py; ./key_features/mount_data.py; ./key_features/custom_container.py;
- :highlights: 11;11;11;11;11;1,7, 10, 11; 11
- :enable_run: true
- :tab_rows: 3
- :height: 430px
-
-----
-
-******************************
-Next: Coordinate 2+ components
-******************************
-Now that you know how to organize arbitrary code inside a Lightning component,
-learn to coordinate 2 or more components into workflows which we call Lightning apps.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Intermediate skills
- :description: Learn to coordinate 2+ components into workflows which we call Lightning apps.
- :button_link: ../intermediate/index.html
- :col_css: col-md-12
- :height: 170
- :tag: 15 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/basic/scripts/toy_app_1_component.py b/docs/source-app/levels/basic/scripts/toy_app_1_component.py
deleted file mode 100644
index e09df3ecb0d9c..0000000000000
--- a/docs/source-app/levels/basic/scripts/toy_app_1_component.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-class Component(LightningWork):
- def run(self, x):
- print(x)
-
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component = Component()
-
- def run(self):
- self.component.run('i love Lightning')
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/basic/scripts/toy_app_1_component_pdb.py b/docs/source-app/levels/basic/scripts/toy_app_1_component_pdb.py
deleted file mode 100644
index 6348f9c4ed46e..0000000000000
--- a/docs/source-app/levels/basic/scripts/toy_app_1_component_pdb.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.pdb import set_trace
-
-class Component(LightningWork):
- def run(self, x):
- print(x)
- set_trace()
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component = Component()
-
- def run(self):
- self.component.run('i love Lightning')
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/basic/train_pytorch_on_the_cloud.rst b/docs/source-app/levels/basic/train_pytorch_on_the_cloud.rst
deleted file mode 100644
index 1860094624f15..0000000000000
--- a/docs/source-app/levels/basic/train_pytorch_on_the_cloud.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-:orphan:
-
-###################################
-Example: Train PyTorch on the cloud
-###################################
-
-**Prereqs:** You have an app already running locally.
-
-----
-
-****************************
-What is the Lightning Cloud?
-****************************
-The Lightning Cloud is the platform that we've created to interface with the cloud providers. Today
-the Lightning Cloud supports AWS.
-
-.. note:: Support for GCP and Azure is coming soon!
-
-To use the Lightning Cloud, you buy credits that are used to pay the cloud providers. If you want to run
-on your own AWS credentials, please contact us (support@lightning.ai) so we can get your clusters set up for you.
diff --git a/docs/source-app/levels/expert/index.rst b/docs/source-app/levels/expert/index.rst
deleted file mode 100644
index c98199b9432e7..0000000000000
--- a/docs/source-app/levels/expert/index.rst
+++ /dev/null
@@ -1,82 +0,0 @@
-:orphan:
-
-.. _expert_level:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
-#############
-Expert skills
-#############
-
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Level : Use custom containers
- :description: Learn to use a custom cloud container.
- :button_link: build_a_machine_learning_workflow.html
- :col_css: col-md-6
- :height: 150
- :tag: basic
-
-
-.. raw:: html
-
-
-
-
-----
-
-*********************
-Intermediate Examples
-*********************
-As you work through the intermediate levels, try these examples:
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example: Develop a Github Repo Script Runner
- :description: Develop a workflow to execute Github Repos
- :button_link: ../../examples/github_repo_runner/github_repo_runner.html
- :col_css: col-md-6
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Example: Develop a file server
- :description: Create a simple Lightning App (App) that allows users to upload files and list the uploaded files.
- :button_link: ../../examples/file_server/file_server.html
- :col_css: col-md-6
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Example: Develop a Jupyter Notebook component
- :description: Develop a LightningWork that runs a notebook on the cloud.
- :button_link: run_jupyter_notebook_on_the_cloud.html
- :col_css: col-md-6
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Example: Create a model demo
- :description: Demo POCs and MVPs which can be shared with a public web user interface.
- :button_link: create_a_model_demo.html
- :col_css: col-md-6
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/connect_lightning_components.rst b/docs/source-app/levels/intermediate/connect_lightning_components.rst
deleted file mode 100644
index 31f6cc1516441..0000000000000
--- a/docs/source-app/levels/intermediate/connect_lightning_components.rst
+++ /dev/null
@@ -1,116 +0,0 @@
-####################################################
-Level 4: Connect components into a full stack AI app
-####################################################
-
-**Audience:** Users who want to build apps with multiple components.
-
-**Prereqs:** You know how to :doc:`build a component <../basic/build_a_lightning_component>`.
-
-----
-
-****************************
-What is a full stack AI app?
-****************************
-In the ML world, workflows coordinate multiple pieces of code working together. In Lightning,
-when we coordinate 2 or more :doc:`Lightning components <../basic/build_a_lightning_component>` working together,
-we instead call it a Lightning App. The difference will become more obvious when we introduce reactive
-workflows in the advanced section.
-
-For the time being, we'll go over how to coordinate 2 components together in a traditional workflow setting
-and explain how it works.
-
-.. note:: If you've used workflow tools for Python, this page describes conventional DAGs.
- In :doc:`level 6 `, we introduce reactive workflows that generalize beyond DAGs
- so you can build complex systems without much effort.
-
-----
-
-***********
-The toy app
-***********
-
-In this app, we define two components that run across 2 separate machines. One to train a model on a GPU machine and one to analyze the model
-on a separate CPU machine. We save money by stopping the GPU machine when the work is done.
-
-.. lit_tabs::
- :titles: Import Lightning; Define Component 1; Define Component 2; Orchestrator; Connect component 1; Connect component 2; Implement run; Train; Analyze; Define app placeholder
- :descriptions: First, import Lightning; This component trains a model on a GPU machine; This component analyzes a model on a CPU machine; Define the LightningFlow that orchestrates components; Component 1 will run on a CPU machine; Component 2 will run on an accelerated GPU machine; Describe the workflow in the run method; Training runs first and completes; Analyze runs after training completes; This allows the app to be runnable
- :code_files: ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py
- :highlights: 2; 5-7; 9-11; 13; 16; 17; 19; 20; 21; 23
- :enable_run: true
- :tab_rows: 4
- :height: 460px
-
-|
-
-Now run the app:
-
-.. lit_tabs::
- :titles: Run on Lightning cloud; Your own hardware
- :descriptions: Run to see these 2 components execute on separate machines 🤯; Run it locally without code changes 🤯🤯;
- :code_files: ./level_2_scripts/code_run_cloud.bash; ./level_2_scripts/code_run_local.bash
- :tab_rows: 7
- :height: 195px
-
-|
-
-Now you can develop distributed cloud apps on your laptop 🤯🤯🤯🤯!
-
-----
-
-*************
-Now you know:
-*************
-
-Without going out of your way, you're now doing the following: (Hint: Click **visualize** to see an animation describing the code).
-
-.. lit_tabs::
- :titles: Orchestration; Distributed cloud computing; Multi-machine communication; Multi-machine communication; Multi-cloud;
- :descriptions: Define orchestration in Python with full control-flow; The two pieces of independent Python code ran on separate machines 🤯🤯; The text "CPU machine 1" was sent from the flow machine to the machine running the TrainComponent; The text "GPU machine 2" was sent from the flow machine to the machine running the AnalyzeComponent; The full Lightning app can move across clusters and clouds
- :code_files: ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py; ./level_2_scripts/hello_app.py;
- :tab_rows: 4
- :highlights: 19-21; 16-17; 20; 21
- :images: | | | |
- :height: 470px
-
-----
-
-*********************
-Maintain full control
-*********************
-Although we've abstracted the infrastructure, you still have full control when you need it:
-
-.. lit_tabs::
- :titles: Scheduler; Crontab syntax; Auto-scaling; Organized Python; Full terraform control;
- :descriptions: Although you can use Python timers, we have a scheduler short-hand; You can also use full cron syntax; Code your own auto-scaling syntax (Lightning plays well with Kubernetes); *Remember* components organize ANY Python code which can even call external non-python scripts such as optimized C++ model servers ;Experts have the option to use terraform to configure Lightning clusters
- :code_files: ./level_2_scripts/hello_app_scheduler.py; ./level_2_scripts/hello_app_cron.py; ./level_2_scripts/hello_app_auto_scale.py; ./level_2_scripts/organized_app_python.py;
- :tab_rows: 4
- :highlights: 24; 24; 21, 24, 27, 28; 9, 16, 17
- :height: 700px
-
-----
-
-*************************
-Next: Review how to debug
-*************************
-The next levels does a 2 minute review to make sure you know how to debug a Lightning app.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 5: Debug a Lightning App
- :description: Learn to debug a lightning app.
- :button_link: debug_a_lightning_app.html
- :col_css: col-md-12
- :height: 170
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/debug_a_lightning_app.rst b/docs/source-app/levels/intermediate/debug_a_lightning_app.rst
deleted file mode 100644
index 8fdc7c67ae973..0000000000000
--- a/docs/source-app/levels/intermediate/debug_a_lightning_app.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-##############################
-Level 5: Debug A Lightning app
-##############################
-**Audience:** Users who want to debug a distributed app locally.
-
-**Prereqs:** You must have finished the :doc:`Basic levels <../basic/index>`.
-
-----
-
-******************
-Enable breakpoints
-******************
-To enable a breakpoint, use :func:`~lightning.app.pdb.set_trace()` (note direct python pdb support is work in progress and open to contributions).
-
-.. lit_tabs::
- :descriptions: Toy app; Add a breakpoint. When the program runs, it will stop at this line.
- :code_files: ./debug_app_scripts/toy_app_1_component.py; ./debug_app_scripts/toy_app_1_component_pdb.py
- :highlights: ; 7
- :enable_run: true
- :tab_rows: 3
- :height: 350px
-
-----
-
-*********************************
-Next: Run a component in parallel
-*********************************
-Learn to run components in parallel to enable more powerful workflows.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 6: Run a Lightning component in parallel
- :description: Learn when and how to run Components in parallel (asynchronous).
- :button_link: run_lightning_work_in_parallel.html
- :col_css: col-md-12
- :height: 150
- :tag: 15 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/debug_app_scripts/debug_app.py b/docs/source-app/levels/intermediate/debug_app_scripts/debug_app.py
deleted file mode 100644
index cc2bbe16be35e..0000000000000
--- a/docs/source-app/levels/intermediate/debug_app_scripts/debug_app.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.runners import MultiProcessRuntime
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent()
- self.analyze = AnalyzeComponent()
-
- def run(self):
- self.train.run("GPU machine 1")
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
-MultiProcessRuntime(app).dispatch()
diff --git a/docs/source-app/levels/intermediate/debug_app_scripts/toy_app.py b/docs/source-app/levels/intermediate/debug_app_scripts/toy_app.py
deleted file mode 100644
index 5f32ac07e2431..0000000000000
--- a/docs/source-app/levels/intermediate/debug_app_scripts/toy_app.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent()
- self.analyze = AnalyzeComponent()
-
- def run(self):
- self.train.run("CPU machine 1")
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/debug_app_scripts/toy_app_1_component.py b/docs/source-app/levels/intermediate/debug_app_scripts/toy_app_1_component.py
deleted file mode 100644
index e09df3ecb0d9c..0000000000000
--- a/docs/source-app/levels/intermediate/debug_app_scripts/toy_app_1_component.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-class Component(LightningWork):
- def run(self, x):
- print(x)
-
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component = Component()
-
- def run(self):
- self.component.run('i love Lightning')
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/debug_app_scripts/toy_app_1_component_pdb.py b/docs/source-app/levels/intermediate/debug_app_scripts/toy_app_1_component_pdb.py
deleted file mode 100644
index 6348f9c4ed46e..0000000000000
--- a/docs/source-app/levels/intermediate/debug_app_scripts/toy_app_1_component_pdb.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.pdb import set_trace
-
-class Component(LightningWork):
- def run(self, x):
- print(x)
- set_trace()
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component = Component()
-
- def run(self):
- self.component.run('i love Lightning')
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/embed_web_ui_into_lightningwork.rst b/docs/source-app/levels/intermediate/embed_web_ui_into_lightningwork.rst
deleted file mode 100644
index 354a82023f3a3..0000000000000
--- a/docs/source-app/levels/intermediate/embed_web_ui_into_lightningwork.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-######################################
-Level 9: Embed graphical UIs into work
-######################################
-**Audience:** Users who need to embed a Graphical UI in their Lightning Apps.
-
-**Prereqs:** You have finished :doc:`Level 8 `.
-
-----
-
-.. include:: ../../workflows/add_web_ui/index_content.rst
-
-----
-
-*******************************************
-Next steps: Practice adapting app templates
-*******************************************
-One of the most exciting powers of Lightning is the ability
-to start an app from a template, replace or add components
-and build a powerful system.
-
-----
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 10: Practice adapting app templates
- :description: Practice starting apps from templates and evolving them by replacing or adding components.
- :button_link: start_from_lightning_app_templates.html
- :col_css: col-md-12
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/index.rst b/docs/source-app/levels/intermediate/index.rst
deleted file mode 100644
index ed5c3912acb0c..0000000000000
--- a/docs/source-app/levels/intermediate/index.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-.. _intermediate_level:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- connect_lightning_components
- debug_a_lightning_app
- run_lightning_work_in_parallel
- share_variables_between_lightning_components
- share_files_between_components
- embed_web_ui_into_lightningwork
- start_from_lightning_app_templates
-
-###################
-Intermediate skills
-###################
-Learn to coordinate 2+ components into workflows which we call Lightning apps.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 4: Coordinate 2+ components in a workflow
- :description: Learn to coordinate 2_ components in a workflow which we call a Lightning app.
- :button_link: connect_lightning_components.html
- :col_css: col-md-6
- :height: 170
- :tag: 15 minutes
-
-.. displayitem::
- :header: Level 5: Debug a Lightning App
- :description: Learn to debug a lightning app.
- :button_link: debug_a_lightning_app.html
- :col_css: col-md-6
- :height: 170
- :tag: 2 minutes
-
-.. displayitem::
- :header: Level 6: Run a Lightning component in parallel
- :description: Learn when and how to run Components in parallel (asynchronous).
- :button_link: run_lightning_work_in_parallel.html
- :col_css: col-md-6
- :height: 150
- :tag: 10 minutes
-
-.. displayitem::
- :header: Level 7: Share variables between components
- :description: Share variables between Lightning components.
- :button_link: share_variables_between_lightning_components.html
- :col_css: col-md-6
- :height: 150
- :tag: 15 minutes
-
-.. displayitem::
- :header: Level 8: Share files between components
- :description: Learn how Drives share files between components
- :button_link: share_files_between_components.html
- :col_css: col-md-6
- :height: 150
- :tag: 20 minutes
-
-.. displayitem::
- :header: Level 9: Render a web UI with other components
- :description: Learn how to embed graphical UIs like react, vue, streamlit and notebook UIs into a lightning workflow.
- :button_link: embed_web_ui_into_lightningwork.html
- :col_css: col-md-6
- :height: 150
- :tag: 15 minutes
-
-.. displayitem::
- :header: Level 10: Practice adapting app templates
- :description: Practice starting apps from templates and evolving them by replacing or adding components.
- :button_link: start_from_lightning_app_templates.html
- :col_css: col-md-6
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/level_12.rst b/docs/source-app/levels/intermediate/level_12.rst
deleted file mode 100644
index 60c23909040a6..0000000000000
--- a/docs/source-app/levels/intermediate/level_12.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-:orphan:
-
-######################
-Level 12: Flow vs Work
-######################
-**Audience:** Users who need to do non trivial workloads in their apps.
-
-**Prereqs:** Level 8+
-
-----
-
-.. include:: ../../workflows/build_lightning_component/from_scratch_component_content.rst
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/code_run_cloud.bash b/docs/source-app/levels/intermediate/level_2_scripts/code_run_cloud.bash
deleted file mode 100644
index 6594fe0ecac33..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/code_run_cloud.bash
+++ /dev/null
@@ -1 +0,0 @@
-lightning run app app.py --cloud
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/code_run_local.bash b/docs/source-app/levels/intermediate/level_2_scripts/code_run_local.bash
deleted file mode 100644
index 8a00b45e132ca..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/code_run_local.bash
+++ /dev/null
@@ -1 +0,0 @@
-lightning run app app.py
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/hello_app.py b/docs/source-app/levels/intermediate/level_2_scripts/hello_app.py
deleted file mode 100644
index 22edaa42add12..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/hello_app.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('gpu'))
-
- def run(self):
- self.train.run("CPU machine 1")
- self.analyze.run("GPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/hello_app_auto_scale.py b/docs/source-app/levels/intermediate/level_2_scripts/hello_app_auto_scale.py
deleted file mode 100644
index 5ea0903caa3c8..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/hello_app_auto_scale.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('gpu'))
-
- def run(self):
- # run() starts the machine
- self.train.run("GPU machine 1")
-
- # stop() stops the machine
- self.train.stop()
-
- # run analysis ONLY when machine 1 stopped
- if self.train.status.STOPPED:
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/hello_app_cron.py b/docs/source-app/levels/intermediate/level_2_scripts/hello_app_cron.py
deleted file mode 100644
index a5c1cdb8a7bee..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/hello_app_cron.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('gpu'))
-
- def run(self):
- # run training once
- self.train.run("GPU machine 1")
-
- # run analysis once, then every hour again...
- if self.schedule("5 4 * * *"):
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/hello_app_scheduler.py b/docs/source-app/levels/intermediate/level_2_scripts/hello_app_scheduler.py
deleted file mode 100644
index a7ca0f415dfcf..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/hello_app_scheduler.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('gpu'))
-
- def run(self):
- # run training once
- self.train.run("GPU machine 1")
-
- # run analysis once, then every hour again...
- if self.schedule("hourly"):
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/level_2_scripts/organized_app_python.py b/docs/source-app/levels/intermediate/level_2_scripts/organized_app_python.py
deleted file mode 100644
index e5e21d856f74d..0000000000000
--- a/docs/source-app/levels/intermediate/level_2_scripts/organized_app_python.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# app.py
-import subprocess
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class ExternalModelServer(LightningWork):
- def run(self, x):
- # compile
- process = subprocess.Popen('g++ model_server.cpp -o model_server')
- process.wait()
- process = subprocess.Popen('./model_server')
- process.wait()
-
-class LocustLoadTester(LightningWork):
- def run(self, x):
- cmd = f'locust --master-host {self.host} --master-port {self.port}'
- process = subprocess.Popen(cmd)
- process.wait()
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.serve = ExternalModelServer(
- cloud_compute=CloudCompute('cpu'), parallel=True
- )
- self.load_test = LocustLoadTester(cloud_compute=CloudCompute('cpu'))
-
- def run(self):
- # start the server (on a CPU machine 1)
- self.serve.run()
-
- # load testing when the server is up (on a separate cpu machine 2)
- if self.serve.state.RUNNING:
- self.load_test.run()
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/level_9.rst b/docs/source-app/levels/intermediate/level_9.rst
deleted file mode 100644
index 344c321bac79b..0000000000000
--- a/docs/source-app/levels/intermediate/level_9.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-:orphan:
-
-###################
-Level 9: Event loop
-###################
-**Audience:** Users who want to build reactive Lightning Apps and move beyond DAGs.
-
-**Prereqs:** Level 8+
-
-----
-
-Drawing inspiration from modern web frameworks like `React.js `_, the Lightning App runs all flows in an **event loop** (forever), which is triggered several times a second after collecting any works' state change.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/lightning_loop.gif
-
-When running a Lightning App in the cloud, the ``LightningWork`` run on different machines. LightningWork communicates any state changes to the **event loop** which re-executes the flow with the newly-collected works' state.
diff --git a/docs/source-app/levels/intermediate/run_lightning_work_in_parallel.rst b/docs/source-app/levels/intermediate/run_lightning_work_in_parallel.rst
deleted file mode 100644
index 7a561893b2a35..0000000000000
--- a/docs/source-app/levels/intermediate/run_lightning_work_in_parallel.rst
+++ /dev/null
@@ -1,38 +0,0 @@
-##############################################
-Level 6: Run a Lightning component in parallel
-##############################################
-**Audience:** Users who want to run a Lightning Component in parallel (asynchronously).
-
-**Prereqs:** You must have finished :doc:`Level 5 `.
-
-----
-
-.. include:: ../../workflows/run_work_in_parallel_content.rst
-
-----
-
-**********************************************
-Next steps: Share variables between components
-**********************************************
-Now that you know how to run components in parallel, we'll learn to share variables
-across components to simplify complex workflows.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 7: Share variables between components
- :description: Learn to connect components
- :col_css: col-md-12
- :button_link: share_variables_between_lightning_components.html
- :height: 150
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/scripts/.storage/a b/docs/source-app/levels/intermediate/scripts/.storage/a
deleted file mode 100644
index 1c6c4ccab0cee..0000000000000
Binary files a/docs/source-app/levels/intermediate/scripts/.storage/a and /dev/null differ
diff --git a/docs/source-app/levels/intermediate/scripts/.storage/embeddings b/docs/source-app/levels/intermediate/scripts/.storage/embeddings
deleted file mode 100644
index af3ee639fa570..0000000000000
Binary files a/docs/source-app/levels/intermediate/scripts/.storage/embeddings and /dev/null differ
diff --git a/docs/source-app/levels/intermediate/scripts/a b/docs/source-app/levels/intermediate/scripts/a
deleted file mode 100644
index 1c6c4ccab0cee..0000000000000
Binary files a/docs/source-app/levels/intermediate/scripts/a and /dev/null differ
diff --git a/docs/source-app/levels/intermediate/scripts/comms_1.py b/docs/source-app/levels/intermediate/scripts/comms_1.py
deleted file mode 100644
index d62d047c545f6..0000000000000
--- a/docs/source-app/levels/intermediate/scripts/comms_1.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-class Component(LightningWork):
- def run(self, x):
- print(f'MACHINE 1: this string came from machine 0: "{x}"')
- print('MACHINE 1: this string is on machine 1')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component = Component()
-
- def run(self):
- x = 'hello from machine 0'
- self.component.run(x)
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/scripts/debug_app.py b/docs/source-app/levels/intermediate/scripts/debug_app.py
deleted file mode 100644
index cc2bbe16be35e..0000000000000
--- a/docs/source-app/levels/intermediate/scripts/debug_app.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.runners import MultiProcessRuntime
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent()
- self.analyze = AnalyzeComponent()
-
- def run(self):
- self.train.run("GPU machine 1")
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
-MultiProcessRuntime(app).dispatch()
diff --git a/docs/source-app/levels/intermediate/scripts/embeddings b/docs/source-app/levels/intermediate/scripts/embeddings
deleted file mode 100644
index af3ee639fa570..0000000000000
Binary files a/docs/source-app/levels/intermediate/scripts/embeddings and /dev/null differ
diff --git a/docs/source-app/levels/intermediate/scripts/toy_app.py b/docs/source-app/levels/intermediate/scripts/toy_app.py
deleted file mode 100644
index 5f32ac07e2431..0000000000000
--- a/docs/source-app/levels/intermediate/scripts/toy_app.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-
-
-class TrainComponent(LightningWork):
- def run(self, x):
- print(f'train a model on {x}')
-
-class AnalyzeComponent(LightningWork):
- def run(self, x):
- print(f'analyze model on {x}')
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent()
- self.analyze = AnalyzeComponent()
-
- def run(self):
- self.train.run("CPU machine 1")
- self.analyze.run("CPU machine 2")
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/scripts/toy_payload.py b/docs/source-app/levels/intermediate/scripts/toy_payload.py
deleted file mode 100644
index 473b450a5f56f..0000000000000
--- a/docs/source-app/levels/intermediate/scripts/toy_payload.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-
-
-class EmbeddingProcessor(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.embeddings = None
-
- def run(self):
- print('PROCESSOR: Generating embeddings...')
- fake_embeddings = [[1, 2, 3], [2, 3, 4]]
- self.embeddings = storage.Payload(fake_embeddings)
-
-class EmbeddingServer(LightningWork):
- def run(self, payload):
- print('SERVER: Using embeddings from processor', payload)
- embeddings = payload.value
- print('serving embeddings sent from EmbeddingProcessor: ', embeddings)
-
-class WorkflowOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.processor = EmbeddingProcessor()
- self.server = EmbeddingServer()
-
- def run(self):
- self.processor.run()
- self.server.run(self.processor.embeddings)
-
-app = LightningApp(WorkflowOrchestrator())
diff --git a/docs/source-app/levels/intermediate/scripts/two_comms_non_ml.py b/docs/source-app/levels/intermediate/scripts/two_comms_non_ml.py
deleted file mode 100644
index 1a9e32589c110..0000000000000
--- a/docs/source-app/levels/intermediate/scripts/two_comms_non_ml.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-import time
-
-class A(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.msg_changed = False
- self.new_msg = ''
-
- def run(self):
- # pretend to train and save a checkpoint every 10 steps
- for step in (range(1000)):
- time.sleep(1.0)
- if step % 10 == 0:
- self.msg_changed = True
- self.new_msg = f'A is at step: {step}'
- print(self.new_msg)
-
-class B(LightningWork):
- def run(self, msg):
- print(f'B: message from A: {msg}')
-
-class Example(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.a = A(parallel=True)
- self.b = B(parallel=True)
-
- def run(self):
- self.a.run()
- if self.a.msg_changed:
- self.a.msg_changed = False
- self.b.run(self.a.new_msg)
-
-app = LightningApp(Example())
diff --git a/docs/source-app/levels/intermediate/scripts/two_work_comms.py b/docs/source-app/levels/intermediate/scripts/two_work_comms.py
deleted file mode 100644
index df37a468253fe..0000000000000
--- a/docs/source-app/levels/intermediate/scripts/two_work_comms.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp
-import time
-
-class TrainComponent(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.last_checkpoint_path = None
-
- def run(self):
- # pretend to train and save a checkpoint every 10 steps
- for step in (range(1000)):
- time.sleep(1.0)
- fake_loss = round(1/(step + 0.00001), 4)
- print(f'{step=}: {fake_loss=} ')
- if step % 10 == 0:
- self.last_checkpoint_path = f'/some/path/{step=}_{fake_loss=}'
- print(f'TRAIN COMPONENT: saved new checkpoint: {self.last_checkpoint_path}')
-
-class ModelDeploymentComponent(LightningWork):
- def run(self, new_checkpoint):
- print(f'DEPLOY COMPONENT: load new model from checkpoint: {new_checkpoint}')
-
-class ContinuousDeployment(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(parallel=True)
- self.model_deployment = ModelDeploymentComponent(parallel=True)
-
- def run(self):
- self.train.run()
- if self.train.last_checkpoint_path:
- self.model_deployment.run(self.train.last_checkpoint_path)
-
-app = LightningApp(ContinuousDeployment())
diff --git a/docs/source-app/levels/intermediate/share_files_between_components.rst b/docs/source-app/levels/intermediate/share_files_between_components.rst
deleted file mode 100644
index 89c170f350a37..0000000000000
--- a/docs/source-app/levels/intermediate/share_files_between_components.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-#######################################
-Level 8: Share files between components
-#######################################
-**Audience:** Users who are moving large files such as artifacts or datasets.
-
-**Prereqs:** Level 6+
-
-----
-
-*************************************************
-Next steps: Render a web UI with other components
-*************************************************
-Now that we know the key ways of sharing files and variables,
-we'll apply it to embed web UIs alongside components.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 9: Render a web UI with other components
- :description: Learn how to embed graphical UIs like react, vue, streamlit and notebook UIs into a lightning workflow.
- :button_link: embed_web_ui_into_lightningwork.html
- :col_css: col-md-12
- :height: 150
- :tag: 15 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/share_variables_between_lightning_components.rst b/docs/source-app/levels/intermediate/share_variables_between_lightning_components.rst
deleted file mode 100644
index 81bbfbf718638..0000000000000
--- a/docs/source-app/levels/intermediate/share_variables_between_lightning_components.rst
+++ /dev/null
@@ -1,162 +0,0 @@
-###########################################
-Level 7: Share variables between components
-###########################################
-**Audience:** Users who want to share variables and files across Lightning components.
-
-**Prereqs:** You must have finished `intermediate level 5+ `_.
-
-----
-
-****************************************
-Send a variable from Flow to a Component
-****************************************
-When a variable is defined on the LightningFlow (orchestrator), and
-then it's passed into functions for the work components, under the hood
-Lightning sends the variables across the machines for you automatically.
-
-.. lit_tabs::
- :descriptions: Remember this component may live on its own machine; The flow may be on a separate machine as well; This variable is on the flow machine; When passed to the work component, it is actually sent across the network under the hood.; When it prints here, it prints on the work component machine (not the flow machine); The second string was directly created on machine 1
- :code_files: ./scripts/comms_1.py; ./scripts/comms_1.py; ./scripts/comms_1.py; ./scripts/comms_1.py; ./scripts/comms_1.py; ./scripts/comms_1.py
- :highlights: 4-7; 9-16; 15; 16; 6; 7;
- :enable_run: true
- :tab_rows: 3
- :height: 380px
-
-|
-
-.. collapse:: CLI output
-
- .. code-block::
-
- $ lightning run app app.py --open-ui=false
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- MACHINE 1: this string came from machine 0: "hello from machine 0"
- MACHINE 1: this string is on machine 1
-
-|
-
-In this example, we learned that we can send variables to components like in regular Python.
-On a local machine, it will behave like Python. When the workflow is distributed on the cloud,
-it makes network calls under the hood, but still functions like Python to you.
-
-----
-
-**************************************
-Send a variable between two components
-**************************************
-A majority of workflows (especially in ML), require components to respond to a change in a component
-likely running on a separate machine or even cluster.
-
-Example Continuous deployment: Every time a model saves a checkpoint, we redeploy a model:
-
-.. lit_tabs::
- :descriptions: Define a component that simulates training; Define a component that simulates deployment; Training will happen in parallel over a long period; The deployment server also runs in parallel forever; Start training in parallel (could take months); Whenever the model has a checkpoint deploy; When the checkpoint is updated, model re-deploys
- :code_files: ./scripts/two_work_comms.py; ./scripts/two_work_comms.py; ./scripts/two_work_comms.py; ./scripts/two_work_comms.py; ./scripts/two_work_comms.py; ./scripts/two_work_comms.py; ./scripts/two_work_comms.py
- :highlights: 5-18; 20-22; 27; 28; 31; 32, 33; 33
- :enable_run: true
- :tab_rows: 3
- :height: 690px
-
-|
-
-.. collapse:: CLI output:
-
- .. code::
-
- $ lightning run app app.py --open-ui=false
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- step=0: fake_loss=100000.0
- TRAIN COMPONENT: saved new checkpoint: /some/path/step=0_fake_loss=100000.0
- step=1: fake_loss=1.0
- DEPLOY COMPONENT: load new model from checkpoint: /some/path/step=0_fake_loss=100000.0
- step=2: fake_loss=0.5
- step=3: fake_loss=0.3333
- step=4: fake_loss=0.25
- step=5: fake_loss=0.2
- step=6: fake_loss=0.1667
- step=7: fake_loss=0.1429
- step=8: fake_loss=0.125
- step=9: fake_loss=0.1111
- step=10: fake_loss=0.1
- TRAIN COMPONENT: saved new checkpoint: /some/path/step=10_fake_loss=0.1
- DEPLOY COMPONENT: load new model from checkpoint: /some/path/step=10_fake_loss=0.1
- step=11: fake_loss=0.0909
- step=12: fake_loss=0.0833
- step=13: fake_loss=0.0769
- step=14: fake_loss=0.0714
- step=15: fake_loss=0.0667
- step=16: fake_loss=0.0625
- step=17: fake_loss=0.0588
- step=18: fake_loss=0.0556
- step=19: fake_loss=0.0526
- step=20: fake_loss=0.05
- TRAIN COMPONENT: saved new checkpoint: /some/path/step=20_fake_loss=0.05
- DEPLOY COMPONENT: load new model from checkpoint: /some/path/step=20_fake_loss=0.05
- step=21: fake_loss=0.0476
-
-----
-
-********************************************
-Send a large variable between two components
-********************************************
-For large variables such as arrays, tensors, embeddings and so on, use Payload to enable
-transferring them across components.
-
-.. lit_tabs::
- :descriptions: Let's define a component to simulate generating embeddings (from a DB, feature store, etc...); This component simulates a server that will use the embeddings.; Run the component to generate the embeddings; Simulate embeddings as an array. Here you would query a DB, load from a feature store or disk or even use a neural network to extract the embedding.; Allow the embeddings to be transferred efficiently by wrapping them in the Payload object.; Pass the variable to the EmbeddingServer (just the pointer).; The data gets transferred once you use the .value attribute in the other component.
- :code_files: ./scripts/toy_payload.py; ./scripts/toy_payload.py; ./scripts/toy_payload.py; ./scripts/toy_payload.py; ./scripts/toy_payload.py; ./scripts/toy_payload.py; ./scripts/toy_payload.py;
- :highlights: 5-13; 15-19; 28; 12; 13; 29; 18
- :enable_run: true
- :tab_rows: 3
- :height: 600px
-
-|
-
-.. collapse:: CLI output
-
- .. code::
-
- $ lightning run app app.py --open-ui=false
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- PROCESSOR: Generating embeddings...
- SERVER: Using embeddings from processor
- serving embeddings sent from EmbeddingProcessor: [[1, 2, 3], [2, 3, 4]]
-
-|
-
-The payload object keeps the data on the machine and passes a pointer
-to the data around the app until the data is needed by a component.
-
-----
-
-******************************************
-Next steps: Share files between components
-******************************************
-Now that you know how to run components in parallel, we'll learn to share variables
-across components to simplify complex workflows.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Level 8: Share files between components
- :description: Learn to share files between components.
- :col_css: col-md-12
- :button_link: share_files_between_components.html
- :height: 150
- :tag: 10 minutes
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/levels/intermediate/start_from_lightning_app_templates.rst b/docs/source-app/levels/intermediate/start_from_lightning_app_templates.rst
deleted file mode 100644
index 2b5ae06e48022..0000000000000
--- a/docs/source-app/levels/intermediate/start_from_lightning_app_templates.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-############################################
-Level 10: Start from lightning app templates
-############################################
-**Audience:** All users who want to move quickly with Lightning
-
-**Prereqs:** You have finished :doc:`Level 9 `.
-
-----
-
-****************************************************
-Next step: Learn to build powerful nested components
-****************************************************
-Now that you can build powerful apps, learn to build nested components
-that can do things like start dynamic works and connect to each other
-via networking or CLI commands.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Advanced skills
- :description: Learn to build nested components with advanced functionality.
- :button_link: ../advanced/index.html
- :col_css: col-md-12
- :height: 170
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/make.bat b/docs/source-app/make.bat
deleted file mode 100644
index 9b565142aecbf..0000000000000
--- a/docs/source-app/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=.
-set BUILDDIR=../build
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.http://sphinx-doc.org/
- exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff --git a/docs/source-app/moving_to_the_cloud.rst b/docs/source-app/moving_to_the_cloud.rst
deleted file mode 100644
index a4d57e9fc026c..0000000000000
--- a/docs/source-app/moving_to_the_cloud.rst
+++ /dev/null
@@ -1,122 +0,0 @@
-:orphan:
-
-.. _moving_to_the_cloud:
-
-####################
-Moving to the Cloud
-####################
-
-.. warning:: This is in progress and not yet fully supported.
-
-In the :ref:`quick_start` guide, you learned how to implement a simple app
-that trains an image classifier and serve it once trained.
-
-In this tutorial, you'll learn how to extend that application so that it works seamlessly
-both locally and in the cloud.
-
-----
-
-********************************
-Step 1: Distributed Application
-********************************
-
-
-Distributed Storage
-^^^^^^^^^^^^^^^^^^^
-
-When running your application in a fully-distributed setting, the data available on one machine won't necessarily be available on another.
-
-To solve this problem, Lightning introduces the :class:`~lightning.app.storage.path.Path` object.
-This ensures that your code can run both locally and in the cloud.
-
-The :class:`~lightning.app.storage.path.Path` object keeps track of the work which creates
-the path. This enables Lightning to transfer the files correctly in a distributed setting.
-
-Instead of passing a string representing a file or directory, Lightning simply wraps
-them into a :class:`~lightning.app.storage.path.Path` object and makes them an attribute of your LightningWork.
-
-Without doing this conscientiously for every single path, your application will fail in the cloud.
-
-In the example below, a file written by **SourceFileWork** is being transferred by the flow
-to the **DestinationFileAndServeWork** work. The Path object is the reference to the file.
-
-.. literalinclude:: ../../examples/app/boring/app.py
- :emphasize-lines: 5, 22, 28, 48
-
-In the ``scripts/serve.py`` file, we are creating a **FastApi Service** running on port ``1111``
-that returns the content of the file received from **SourceFileWork** when
-a post request is sent to ``/file``.
-
-.. literalinclude:: ../../examples/app/boring/scripts/serve.py
- :emphasize-lines: 21, 23-26
-
-----
-
-Distributed Frontend
-^^^^^^^^^^^^^^^^^^^^
-
-In the above example, the **FastAPI Service** was running on one machine,
-and the frontend UI in another.
-
-In order to assemble them, you need to do two things:
-
-* Provide **port** argument to your work's ``__init__`` method to expose a single service.
-
-Here's how to expose the port:
-
-.. literalinclude:: ../../examples/app/boring/app.py
- :emphasize-lines: 8
- :lines: 33-44
-
-
-And here's how to expose your services within the ``configure_layout`` flow hook:
-
-.. literalinclude:: ../../examples/app/boring/app.py
- :emphasize-lines: 5
- :lines: 53-57
-
-In this example, we're appending ``/file`` to our **FastApi Service** url.
-This means that our ``Boring Tab`` triggers the ``get_file_content`` from the **FastAPI Service**
-and embeds its content as an `IFrame `_.
-
-.. literalinclude:: ../../examples/app/boring/scripts/serve.py
- :lines: 23-26
-
-
-Here's a visualization of the application described above:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/storage_ui.gif
- :alt: Storage API Animation
- :width: 100 %
-
-----
-
-*****************************
-Step 2: Scalable Application
-*****************************
-
-The benefit of defining long-running code inside a
-:class:`~lightning.app.core.work.LightningWork`
-component is that you can run it on different hardware
-by providing :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute` to
-the ``__init__`` method of your :class:`~lightning.app.core.work.LightningWork`.
-
-By adapting the :ref:`quick_start` example as follows, you can easily run your component on multiple GPUs:
-
-
-Without doing much, you’re now running a script on its own cluster of machines! 🤯
-
-----
-
-*****************************
-Step 3: Resilient Application
-*****************************
-
-We designed Lightning with a strong emphasis on supporting failure cases.
-The framework shines when the developer embraces our fault-tolerance best practices,
-enabling them to create ML applications with a high degree of complexity as well as a strong support
-for unhappy cases.
-
-An entire section would be dedicated to this concept.
-
-TODO
diff --git a/docs/source-app/quickstart.rst b/docs/source-app/quickstart.rst
deleted file mode 100644
index 99872c5f8ae46..0000000000000
--- a/docs/source-app/quickstart.rst
+++ /dev/null
@@ -1,125 +0,0 @@
-:orphan:
-
-.. _quick_start:
-
-############
-Quick Start
-############
-
-In this guide, we'll run an application that trains
-an image classification model with the `MNIST Dataset `_,
-and uses `Gradio `_ to serve it.
-
-----
-
-**********************
-Step 1 - Installation
-**********************
-
-First, you'll need to install Lightning. You can find the complete guide here.
-
-Then, you'll need to install the `Lightning Quick Start package `_.
-
-.. code-block:: bash
-
- lightning install app lightning/quick-start
-
-And download the training script used by the App:
-
-
-----
-
-**********************
-Step 2 - Run the app
-**********************
-
-To run your app, copy the following command to your local terminal:
-
-.. code-block:: bash
-
- lightning run app app.py
-
-And that's it!
-
-.. admonition:: You should see the app logs in your terminal.
- :class: dropdown
-
- .. code-block:: console
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
-
- Global seed set to 42
-
- GPU available: True (mps), used: False
- TPU available: False, using: 0 TPU cores
-
- | Name | Type | Params | In sizes | Out sizes
- ------------------------------------------------------------------
- 0 | model | Net | 1.2 M | [1, 1, 28, 28] | [1, 10]
- 1 | val_acc | Accuracy | 0 | ? | ?
- ------------------------------------------------------------------
- 1.2 M Trainable params
- 0 Non-trainable params
- 1.2 M Total params
- Epoch 4: 100%|█████████████████████████| 16/16 [00:00<00:00, 32.31it/s, loss=0.0826, v_num=0]
- `Trainer.fit` stopped: `max_epochs=5` reached.
-
- Running on local URL: http://127.0.0.1:62782/
- ...
-
-
-The app will open your browser and show an interactive demo:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/qiuck-start-tensorboard-tab.png
- :alt: Quick Start UI - Model Training Tab
- :width: 100 %
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/quick-start-gradio-tab.png
- :alt: Quick Start UI - Interactive Demo Tab
- :width: 100 %
-
-----
-
-This app behind the scenes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This application has one flow component which coordinates two works executing their own python script.
-Once the training is finished, the trained model weights are passed to the serve component.
-
-
-Here is how the components of a Lightning app are structured:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/quick_start_components.gif
- :alt: Quick Start Application
- :width: 100 %
-
-Here is the application timeline:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/timeline.gif
- :alt: Quick Start Timeline Application
- :width: 100 %
-
-----
-
-**************************************
-Steps 3 - Build your app in the cloud
-**************************************
-
-Simply add ``--cloud`` to run this application in the cloud 🤯
-
-.. code-block:: bash
-
- lightning run app app.py --cloud
-
-Congratulations! You've now run your first application with Lightning.
-
-----
-
-***********
-Next Steps
-***********
-
-To learn how to build and modify apps, go to the :ref:`basics`.
-
-To learn how to create UIs for your apps, read :ref:`ui_and_frontends`.
diff --git a/docs/source-app/testing.rst b/docs/source-app/testing.rst
deleted file mode 100644
index 752291541000e..0000000000000
--- a/docs/source-app/testing.rst
+++ /dev/null
@@ -1,155 +0,0 @@
-:orphan:
-
-.. _testing:
-
-#######################
-Productionize your Apps
-#######################
-
-.. TODO: Cleanup
-
-At the core of our system is an integration testing framework that will allow for a first-class experience creating integration tests for Lightning Apps. This document will explain how we can create a lightning app test, how we can execute it, and where to find more information.
-
-----
-
-***********
-Philosophy
-***********
-
-Testing a Lightning app is unique. It is a superset of an application that converges machine learning, API development, and UI development. With that in mind, there are several philosophies (or "best practices") that you should adhere to:
-
-
-#. **Control your app state** - With integration and end to end tests, you have the capabilities of controlling your app's state through dependency injection. Use it!
-#. **Integration focuses on the work, End to End focuses on the app** - When writing tests, think of the depth and breath of what you are writing. Write many integration tests since they are relatively cheap, while keeping the end to end tests for holistic app testing.
-#. **Don't overthink it** - What needs to be tested? What is the order of risk? These are the questions you should build with before writing your first line of code. Writing tests for the sake of writing tests is an exercise in futility. Write meaningful, impactful tests.
-#. **Test Isolation** - Write your tests in an isolated manner. No two tests should ever depend on each other.
-#. **Use your framework** - Testing apps should be framework agnostic.
-#. **Have fun!** - At the heart of testing is experimentation. Like any experiment, tests begin with a hypothesis of workability, but you can extend that to be more inclusive. Ask the question, write the test to answer your question, and make sure you have fun while doing it.
-
-----
-
-****************************************
-Anatomy of a Lightning integration test
-****************************************
-
-The following is a PyTest example of an integration test using the ``lightning.app.testing`` module.
-
-.. code-block:: python
-
- import os
-
- from lightning.app import _PROJECT_ROOT
- from lightning.app.testing import application_testing, LightningTestApp
- from lightning.app.utilities.enum import AppStage
-
-
- class TestLightningAppInt(TestLightningApp):
- def run_once(self) -> bool:
- if self.root.counter > 1:
- print("V0 App End")
- self.stage = AppStage.STOPPING
- return True
- return super().run_once()
-
-
- def test_v0_app_example():
- command_line = [
- os.path.join(_PROJECT_ROOT, "examples/app_v0/app.py"),
- "--blocking",
- "False",
- "--multiprocess",
- "--open-ui",
- "False",
- ]
- result = application_testing(TestLightningAppInt, command_line)
- assert "V0 App End" in str(result.stdout_bytes)
- assert result.exit_code == 0
-
-----
-
-Setting up the app
-^^^^^^^^^^^^^^^^^^
-
-Lightning apps are unique in that they represent a full stack model for your machine learning application. To be clear, the integration tests are *NOT* going to touch the UI flow. Instead we inject your application with helper methods that, when executed, can assist in validating your application.
-
-To get started, you simply need to import the following:
-
-.. code-block:: python
-
- from lightning.app.testing import application_testing, LightningTestApp
-
-We will discuss ``application_testing`` in a bit, but first let's review the structure of ``LightningTestApp``.
-
-----
-
-LightningTestApp
-^^^^^^^^^^^^^^^^^
-
-The :class:`lightning.app.testing.testing.LightningTestApp` class is available to use for provisioning and setting up your testing needs. Note that you do not need this class to move forward with testing. Any application that inherits ``LightningApp`` should suffice as long as you override the correct methods. Reviewing the TestLightnigApp we see some overrides that are already there. Please revuew the class for more information.
-
-.. code-block:: python
-
- class TestLightningAppInt(LightningTestApp):
- def run_once(self) -> bool:
- if self.root.counter > 1:
- print("V0 App End")
- self.stage = AppStage.STOPPING
- return True
- return super().run_once()
-
-We create a test class overriding the ``run_once`` function. This function helps control the flow of your application and is ran first. In this example we are calling ``self.root.counter`` and checking if the job has executed once. If so, we want to print out ``V0 App End`` and set the ``self.stage`` to ``AppStage.STOPPING``. This is how we control the flow through state. Your situation might be different, so experiment and see what you can do!
-
-Besides ``run_once`` there are a few other overrides available:
-
-
-* ``on_before_run_once`` - This runs before your ``run_once`` function kicks off. You can set up your application pre-conditions here.
-* ``on_after_run_once`` - Similar to ``on_before_run_once`` but after the ``run_once`` method is called.
-
-These methods will skew your tests, so use them when needed.
-
-----
-
-The Test
-^^^^^^^^
-
-We provide ``application_testing`` as a helper function to get your application up and running for testing. It uses ``click``\ 's invocation tooling underneath.
-
-.. code-block::
-
- command_line = [
- os.path.join(_PROJECT_ROOT, "examples/app_v0/app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ]
-
-First in the list for ``command_line`` is the location of your script. It is an external file. In this example we have ``_PROJECT_ROOT`` but this is *not* a helper constant for you to utilize. You will need to provide the location yourself.
-
-Next there are a couple of options you can leverage:
-
-* ``blocking`` - Blocking is an app status that says "Do not run until I click run in the UI". For our integration test, since we are not using the UI, we are setting this to "False".
-* ``open-ui`` - We set this to false since this is the routine that opens a browser for your local execution.
-
-Once you have your commandline ready, you will then be able to kick off the test and gather results:
-
-.. code-block:: python
-
- result = application_testing(TestLightningAppInt, command_line)
-
-As mentioned earlier, ``application_testing`` is a helper method that allows you to inject your TestLightningApp class (with overrides) and the commandline flags. Once the process is done it returns the results back for parsing.
-
-.. code-block:: python
-
- assert "V0 App End" in str(result.stdout_bytes)
- assert result.exit_code == 0
-
-Since we injected "V0 App End" to the end of our test flow. The state was changed to ``AppStatus.STOPPING`` which means the process is done. Finally, we check the result's exit code to make sure that we did not throw an error during execution.
-
-----
-
-************
-End to End
-************
-
-TODO
diff --git a/docs/source-app/ui_and_frontends.rst b/docs/source-app/ui_and_frontends.rst
deleted file mode 100644
index 3ffa5f983e7d8..0000000000000
--- a/docs/source-app/ui_and_frontends.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-:orphan:
-
-.. _ui_and_frontends:
-
-################
-UI and Frontends
-################
-
-
-The Lightning framework allows you to create customized, interactive UIs with the framework of your choice.
-
-You can easily embed other tools and services (like a GitHub repo, a `FastAPI Service `_, an `arXiv `_ paper or a `Dask Cluster `_ Admin page), or create a complete UI from scratch.
-
-
-To get started, you can use built-in templates for the following frameworks:
-
-* `React.js `_
-* `StreamLit `_
-
-
-
-To keep learning about Lightning, check out :ref:`moving_to_the_cloud`.
-This section covers best practices to seamlessly make your Lightning code work both locally and in the cloud.
diff --git a/docs/source-app/workflows/access_app_state.rst b/docs/source-app/workflows/access_app_state.rst
deleted file mode 100644
index 8f99534bd239b..0000000000000
--- a/docs/source-app/workflows/access_app_state.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-.. _access_app_state:
-
-################
-Access App State
-################
-
-**Audience:** Users who want to know how the App State can be accessed.
-
-**Level:** Basic
-
-**********************
-What is the App State?
-**********************
-
-In Lightning, each component is stateful and their state is composed of all attributes defined within their **__init__** method.
-
-The **App State** is the collection of all the components' states forming the App.
-
-************************************
-What is special about the App State?
-************************************
-
-The **App State** is always up-to-date, even running an App in the cloud on multiple machines.
-This means that every time an attribute is modified in a Work, that information is automatically
-broadcasted to the Flow. With this mechanism, any Component can **react** to any other
-Component's **state changes** through the Flow and complex systems can be easily implemented.
-Lightning requires a state based driven mindset when implementing the Flow.
-
-***************************************
-When do I need to access the App State?
-***************************************
-
-As a user, you are interacting with your component attributes, so most likely,
-you won't need to access the Component's state directly, but it can be helpful to
-understand how the state works under the hood.
-
-For example, here we define a **Flow** component and **Work** component, where the Work increments a counter indefinitely and the Flow prints its state which contains the Work.
-
-You can easily check the state of your entire App as follows:
-
-.. literalinclude:: ../code_samples/quickstart/app_01.py
-
-Run the App with:
-
-.. code-block:: bash
-
- lightning run app docs/quickstart/app_01.py
-
-And here's the output you get when running the App using **Lightning CLI**:
-
-.. code-block:: console
-
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- State: {'works': {'w': {'vars': {'counter': 1}}}}
- State: {'works': {'w': {'vars': {'counter': 2}}}}
- State: {'works': {'w': {'vars': {'counter': 3}}}}
- State: {'works': {'w': {'vars': {'counter': 3}}}}
- State: {'works': {'w': {'vars': {'counter': 4}}}}
- ...
diff --git a/docs/source-app/workflows/add_components.rst b/docs/source-app/workflows/add_components.rst
deleted file mode 100644
index 3edff4c1c0c7d..0000000000000
--- a/docs/source-app/workflows/add_components.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-:orphan:
-
-###########################
-Add a component to your app
-###########################
-**Audience:** Users looking to expand the functionality of their Lightning apps.
-
-----
-
-*******************
-Install a component
-*******************
-
-Any Lightning component can be installed with:
-
-.. code:: python
-
- lightning install component org/the-component-name
-
-`Browse all community-built components here `_.
-
-.. note:: Components are being populated daily
-
-----
-
-**********************
-Contribute a component
-**********************
-One of the first principles of the Lightning community is to code something *once* for the benefit or everyone!
-
-To contribute a component, :doc:`follow this guide `.
diff --git a/docs/source-app/workflows/add_server/any_server.rst b/docs/source-app/workflows/add_server/any_server.rst
deleted file mode 100644
index 948ae22a2abc0..0000000000000
--- a/docs/source-app/workflows/add_server/any_server.rst
+++ /dev/null
@@ -1,187 +0,0 @@
-#########################
-Enable any server (basic)
-#########################
-**Audience:** Users who want to enable an arbitrary server/UI.
-
-**Prereqs:** Basic python knowledge.
-
-----
-
-*****************
-What is a server?
-*****************
-A server is a program that enables other programs or users to connect to it. As long as your server can listen on a port,
-you can enable it with a Lightning App.
-
-----
-
-***************************
-Add a server to a component
-***************************
-Any server that listens on a port, can be enabled via a work. For example, here's a plain python server:
-
-.. code:: python
- :emphasize-lines: 11-12
-
- import socketserver
- from http import HTTPStatus, server
-
-
- class PlainServer(server.SimpleHTTPRequestHandler):
- def do_GET(self):
- self.send_response(HTTPStatus.OK)
- self.end_headers()
- # Data must be passed as bytes to the `self.wfile.write` call
- html = b" Hello lit world "
- self.wfile.write(html)
-
-
- httpd = socketserver.TCPServer(("localhost", "3000"), PlainServer)
- httpd.serve_forever()
-
-To enable the server inside the component, start the server in the run method and use the ``self.host`` and ``self.port`` properties:
-
-.. code:: python
- :emphasize-lines: 14-15
-
- import lightning as L
- import socketserver
- from http import HTTPStatus, server
-
-
- class PlainServer(server.SimpleHTTPRequestHandler):
- def do_GET(self):
- self.send_response(HTTPStatus.OK)
- self.end_headers()
- # Data must be passed as bytes to the `self.wfile.write` call
- html = b" Hello lit world "
- self.wfile.write(html)
-
-
- class LitServer(L.LightningWork):
- def run(self):
- httpd = socketserver.TCPServer((self.host, self.port), PlainServer)
- httpd.serve_forever()
-
-----
-
-**************************************
-Route the server in the root component
-**************************************
-The final step, is to tell the Root component in which tab to render this component's output:
-In this case, we render the ``LitServer`` output in the ``home`` tab of the application.
-
-.. code:: python
- :emphasize-lines: 20, 23, 28
-
- import lightning as L
- import socketserver
- from http import HTTPStatus, server
-
-
- class PlainServer(server.SimpleHTTPRequestHandler):
- def do_GET(self):
- self.send_response(HTTPStatus.OK)
- self.end_headers()
- # Data must be passed as bytes to the `self.wfile.write` call
- html = b"
Hello lit world "
- self.wfile.write(html)
-
-
- class LitServer(L.LightningWork):
- def run(self):
- httpd = socketserver.TCPServer((self.host, self.port), PlainServer)
- httpd.serve_forever()
-
-
- class Root(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_server = LitServer(parallel=True)
-
- def run(self):
- self.lit_server.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_server}
- return tab1
-
-
- app = L.LightningApp(Root())
-
-We use the ``parallel=True`` argument of ``LightningWork`` to run the server in parallel
-while the rest of the Lightning App runs everything else.
-
-----
-
-***********
-Run the app
-***********
-Start the app to see your new UI!
-
-.. code:: bash
-
- lightning_app run app app.py
-
-To run the app on the cloud, use the ``--cloud`` argument.
-
-.. code:: bash
-
- lightning_app run app app.py --cloud
-
-----
-
-*****************************************
-Interact with a component from the server
-*****************************************
-
-.. TODO:: how do we do this?
-
-
-----
-
-*****************************************
-Interact with the server from a component
-*****************************************
-
-.. TODO:: how do we do this?
-
-----
-
-********
-Examples
-********
-Here are a few example apps that expose a server via a component:
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example: Tensorboard
- :description: TODO
- :col_css: col-md-4
- :button_link: example_app.html
- :height: 150
-
-.. displayitem::
- :header: Example: Streamlit
- :description: TODO
- :col_css: col-md-4
- :button_link: example_app.html
- :height: 150
-
-.. displayitem::
- :header: Example: React
- :description: TODO
- :col_css: col-md-4
- :button_link: example_app.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_server/flask_basic.rst b/docs/source-app/workflows/add_server/flask_basic.rst
deleted file mode 100644
index 38ca282346248..0000000000000
--- a/docs/source-app/workflows/add_server/flask_basic.rst
+++ /dev/null
@@ -1,155 +0,0 @@
-###############################
-Add a web UI with Flask (basic)
-###############################
-**Audience:** Users who want to enable a flask app within a component.
-
-**Prereqs:** Basic python knowledge.
-
-----
-
-**************
-What is Flask?
-**************
-Flask is a web framework, that lets you develop web applications in Python easily.
-
-----
-
-************************
-Add Flask to a component
-************************
-First, define your flask app as you normally would without Lightning:
-
-.. code:: python
- :emphasize-lines: 9
-
- from flask import Flask
-
- flask_app = Flask(__name__)
-
-
- @flask_app.route("/")
- def hello():
- return "Hello, World!"
-
-
- flask_app.run(host="0.0.0.0", port=80)
-
-To enable the server inside the component, start the Flask server in the run method and use the ``self.host`` and ``self.port`` properties:
-
-.. code:: python
- :emphasize-lines: 12
-
- import lightning as L
- from flask import Flask
-
-
- class LitFlask(L.LightningWork):
- def run(self):
- flask_app = Flask(__name__)
-
- @flask_app.route("/")
- def hello():
- return "Hello, World!"
-
- flask_app.run(host=self.host, port=self.port)
-
-----
-
-**************************************
-Route the server in the root component
-**************************************
-The final step, is to tell the Root component in which tab to render this component's output:
-In this case, we render the ``LitFlask`` output in the ``home`` tab of the application.
-
-.. code:: python
- :emphasize-lines: 17, 23
-
- import lightning as L
- from flask import Flask
-
-
- class LitFlask(L.LightningWork):
- def run(self):
- flask_app = Flask(__name__)
-
- @flask_app.route("/")
- def hello():
- return "Hello, World!"
-
- flask_app.run(host=self.host, port=self.port)
-
-
- class Root(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_flask = LitFlask(parallel=True)
-
- def run(self):
- self.lit_flask.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_flask}
- return tab1
-
-
- app = L.LightningApp(Root())
-
-We use the ``parallel=True`` argument of ``LightningWork`` to run the server in the background
-while the rest of the Lightning App runs everything else.
-
-----
-
-***********
-Run the app
-***********
-Start the app to see your new UI!
-
-.. code:: bash
-
- lightning run app app.py
-
-To run the app on the cloud, use the ``--cloud`` argument.
-
-.. code:: bash
-
- lightning run app app.py --cloud
-
-----
-
-********
-Examples
-********
-Here are a few example apps that expose a Flask server via a component:
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: TODO
- :col_css: col-md-4
- :button_link: example_app.html
- :height: 150
-
-.. displayitem::
- :header: Example 2
- :description: TODO
- :col_css: col-md-4
- :button_link: example_app.html
- :height: 150
-
-.. displayitem::
- :header: Example 3
- :description: TODO
- :col_css: col-md-4
- :button_link: example_app.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_server/index.rst b/docs/source-app/workflows/add_server/index.rst
deleted file mode 100644
index 1429b08679b2a..0000000000000
--- a/docs/source-app/workflows/add_server/index.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-###################################
-Run a server within a Lightning App
-###################################
-Any type of server can run inside a Lightning App.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/add_server/index_content.rst b/docs/source-app/workflows/add_server/index_content.rst
deleted file mode 100644
index d3623698cfb19..0000000000000
--- a/docs/source-app/workflows/add_server/index_content.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- any_server
- flask_basic
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Any server
- :description: Learn how to enable any server inside a Lightning App.
- :col_css: col-md-6
- :button_link: any_server.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Flask
- :description: Learn how to add a Flask server inside a Lightning App.
- :col_css: col-md-6
- :button_link: flask_basic.html
- :height: 150
- :tag: basic
-
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_link.rst b/docs/source-app/workflows/add_web_link.rst
deleted file mode 100644
index 01ffdf62ef3e3..0000000000000
--- a/docs/source-app/workflows/add_web_link.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-##############
-Add a web link
-##############
-**Audience:** Users who want to link to other pages from their app.
-
-----
-
-**************
-Add a url link
-**************
-In this example we'll replicate |urls_link|.
-
-To add a url link to an app, simply specify it in the ``configure_layout`` method
-and connect the UIs. Create a file named **app.py** with this code:
-
-.. |urls_link| raw:: html
-
- the app running here
-
-.. code:: python
- :emphasize-lines: 7,11
-
- import lightning as L
-
- class LitApp(L.LightningFlow):
- def configure_layout(self):
- tab_1 = {
- "name": "Logger",
- "content": "https://bit.ly/tb-aasae"
- }
- tab_2 = {
- "name": "Paper",
- "content": "https://arxiv.org/pdf/2107.12329.pdf"
- }
- return tab_1, tab_2
-
- app = L.LightningApp(LitApp())
-
-----
-
-***********
-Run the app
-***********
-Run the app locally to see it!
-
-.. code:: python
-
- lightning run app app.py
-
-Now run it on the cloud as well:
-
-.. code:: python
-
- lightning run app app.py --cloud
diff --git a/docs/source-app/workflows/add_web_ui/angular_js_intermediate.rst b/docs/source-app/workflows/add_web_ui/angular_js_intermediate.rst
deleted file mode 100644
index 095dee362bf48..0000000000000
--- a/docs/source-app/workflows/add_web_ui/angular_js_intermediate.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-:orphan:
-
-###########################################
-Add a web UI with Angular.js (intermediate)
-###########################################
-coming...
diff --git a/docs/source-app/workflows/add_web_ui/dash/basic.rst b/docs/source-app/workflows/add_web_ui/dash/basic.rst
deleted file mode 100644
index 4316fc13a1992..0000000000000
--- a/docs/source-app/workflows/add_web_ui/dash/basic.rst
+++ /dev/null
@@ -1,221 +0,0 @@
-##############################
-Add a web UI with Dash (basic)
-##############################
-**Audience:** Users who want to add a web UI with Dash by Plotly.
-
-**Prereqs:** Basic python knowledge.
-
-----
-
-*************
-What is Dash?
-*************
-`Dash `_ is the original low-code framework for rapidly building data apps in Python, R, Julia, and F# (experimental).
-
-Install Dash with:
-
-.. code:: bash
-
- pip install dash
-
-----
-
-************************
-Create the dash demo app
-************************
-
-To explain how to use Dash with Lightning, let's build a simple app with Dash.
-
-
-..
- To explain how to use Dash with Lightning, let's replicate the |dash_link|.
-
- .. |dash_link| raw:: html
-
- example running here
-
-In the next few sections we'll build an app step-by-step.
-First **create a file named app.py** with the app content:
-
-.. code:: bash
-
- import lightning as L
- import dash
- import plotly.express as px
-
- class LitDash(L.LightningWork):
- def run(self):
- dash_app = dash.Dash(__name__)
- X = [1, 2, 3, 4, 5, 6]
- Y = [2, 4, 8, 16, 32, 64]
- fig = px.line(x=X, y=Y)
-
- dash_app.layout = dash.html.Div(children=[
- dash.html.H1(children='⚡ Hello Dash + Lightning⚡'),
- dash.html.Div(children='The Dash framework running inside a ⚡ Lightning App'),
- dash.dcc.Graph(id='example-graph', figure=fig)
- ])
-
- dash_app.run_server(host=self.host, port=self.port)
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_dash = LitDash(parallel=True)
-
- def run(self):
- self.lit_dash.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_dash}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-
-add 'dash' to a requirements.txt file:
-
-.. code:: bash
-
- echo "dash" >> requirements.txt
-
-this is a best practice to make apps reproducible.
-
-----
-
-***********
-Run the app
-***********
-Run the app locally to see it!
-
-.. code:: python
-
- lightning run app app.py
-
-Now run it on the cloud as well:
-
-.. code:: python
-
- lightning run app app.py --cloud
-
-----
-
-************************
-Step-by-step walkthrough
-************************
-In this section, we explain each part of this code in detail.
-
-----
-
-0. Define a Dash app
-^^^^^^^^^^^^^^^^^^^^
-First, find the dash app you want to integrate. In this example, that app looks like:
-
-.. code:: python
-
- import dash
- import plotly.express as px
-
- dash_app = dash.Dash(__name__)
- X = [1, 2, 3, 4, 5, 6]
- Y = [2, 4, 8, 16, 32, 64]
- fig = px.line(x=X, y=Y)
-
- dash_app.layout = dash.html.Div(children=[
- dash.html.H1(children='⚡ Hello Dash + Lightning⚡'),
- dash.html.Div(children='The Dash framework running inside a ⚡ Lightning App'),
- dash.dcc.Graph(id='example-graph', figure=fig)
- ])
-
- dash_app.run_server(host='0.0.0.0', port=80)
-
-This dash app plots a simple line curve along with some HTMlapp.
-`Visit the Dash documentation for the full API `_.
-
-----
-
-1. Add Dash to a component
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-Add the dash app to the run method of a ``LightningWork`` component and run the server on that component's **host** and **port**:
-
-.. code:: python
- :emphasize-lines: 6, 18
-
- import lightning as L
- import dash
- import plotly.express as px
-
- class LitDash(L.LightningWork):
- def run(self):
- dash_app = dash.Dash(__name__)
- X = [1, 2, 3, 4, 5, 6]
- Y = [2, 4, 8, 16, 32, 64]
- fig = px.line(x=X, y=Y)
-
- dash_app.layout = dash.html.Div(children=[
- dash.html.H1(children='⚡ Hello Dash + Lightning⚡'),
- dash.html.Div(children='The Dash framework running inside a ⚡ Lightning App'),
- dash.dcc.Graph(id='example-graph', figure=fig)
- ])
-
- dash_app.run_server(host=self.host, port=self.port)
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_dash = LitDash(parallel=True)
-
- def run(self):
- self.lit_dash.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_dash}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-----
-
-2. Route the UI in the root component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The final step, is to tell the Root component in which tab to render this component's UI.
-In this case, we render the ``LitDash`` UI in the ``home`` tab of the application.
-
-.. code:: python
- :emphasize-lines: 23, 29
-
- import lightning as L
- import dash
- import plotly.express as px
-
- class LitDash(L.LightningWork):
- def run(self):
- dash_app = dash.Dash(__name__)
- X = [1, 2, 3, 4, 5, 6]
- Y = [2, 4, 8, 16, 32, 64]
- fig = px.line(x=X, y=Y)
-
- dash_app.layout = dash.html.Div(children=[
- dash.html.H1(children='⚡ Hello Dash + Lightning⚡'),
- dash.html.Div(children='The Dash framework running inside a ⚡ Lightning App'),
- dash.dcc.Graph(id='example-graph', figure=fig)
- ])
-
- dash_app.run_server(host=self.host, port=self.port)
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_dash = LitDash(parallel=True)
-
- def run(self):
- self.lit_dash.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_dash}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-We use the ``parallel=True`` argument of ``LightningWork`` to run the server in the background
-while the rest of the Lightning App runs everything else.
diff --git a/docs/source-app/workflows/add_web_ui/dash/index.rst b/docs/source-app/workflows/add_web_ui/dash/index.rst
deleted file mode 100644
index 5abb444c8e9a9..0000000000000
--- a/docs/source-app/workflows/add_web_ui/dash/index.rst
+++ /dev/null
@@ -1,84 +0,0 @@
-:orphan:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- basic
- intermediate
-
-######################
-Add a web UI with Dash
-######################
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1: Connect Dash
- :description: Learn how to connect a Dash app.
- :col_css: col-md-6
- :button_link: basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: 2: Enable two-way communication
- :description: Enable two-way communication between the dash app and a Lightning App.
- :col_css: col-md-6
- :button_link: intermediate.html
- :height: 150
- :tag: [docs coming soon]
-
-.. raw:: html
-
-
-
-
-----
-
-********
-Examples
-********
-Here are a few example apps that use a Dash web UI.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/dash/intermediate.rst b/docs/source-app/workflows/add_web_ui/dash/intermediate.rst
deleted file mode 100644
index 5c5da1a19403e..0000000000000
--- a/docs/source-app/workflows/add_web_ui/dash/intermediate.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-#####################################
-Add a web UI with Dash (intermediate)
-#####################################
-**Audience:** Users who want to communicate between the Lightning App and Dash.
-
-**Prereqs:** Must have read the :doc:`dash basic ` guide.
-
-----
-
-*******************************
-Interact with the App from Dash
-*******************************
-
-In the example below, every time you change the select year on the dashboard, this is directly communicated to the flow
-and another work process the associated data frame with the provided year.
-
-.. literalinclude:: intermediate_plot.py
-
-Here is how the app looks like once running:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/dash_plot.gif
-
-----
-
-***********************************
-Interact with Dash from a component
-***********************************
-
-In the example below, when you click the toggle, the state of the work appears.
-
-Install the following libraries if you want to run the app.
-
-```bash
-pip install dash_daq dash_renderjson
-```
-
-.. literalinclude:: intermediate_state.py
-
-
-Here is how the app looks like once running:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/dash_state.gif
diff --git a/docs/source-app/workflows/add_web_ui/dash/intermediate_plot.py b/docs/source-app/workflows/add_web_ui/dash/intermediate_plot.py
deleted file mode 100644
index 95a3edb8f99d4..0000000000000
--- a/docs/source-app/workflows/add_web_ui/dash/intermediate_plot.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from typing import Optional
-
-import pandas as pd
-import plotly.express as px
-from dash import Dash, dcc, html, Input, Output
-
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.storage import Payload
-
-
-class LitDash(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.df = None
- self.selected_year = None
-
- def run(self):
- df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")
- self.df = Payload(df)
-
- dash_app = Dash(__name__)
-
- dash_app.layout = html.Div(
- [
- dcc.Graph(id="graph-with-slider"),
- dcc.Slider(
- df["year"].min(),
- df["year"].max(),
- step=None,
- value=df["year"].min(),
- marks={str(year): str(year) for year in df["year"].unique()},
- id="year-slider",
- ),
- ]
- )
-
- @dash_app.callback(Output("graph-with-slider", "figure"), Input("year-slider", "value"))
- def update_figure(selected_year):
- self.selected_year = selected_year
- filtered_df = df[df.year == selected_year]
-
- fig = px.scatter(
- filtered_df,
- x="gdpPercap",
- y="lifeExp",
- size="pop",
- color="continent",
- hover_name="country",
- log_x=True,
- size_max=55,
- )
-
- fig.update_layout(transition_duration=500)
-
- return fig
-
- dash_app.run_server(host=self.host, port=self.port)
-
-
-class Processor(LightningWork):
- def run(self, df: Payload, selected_year: Optional[str]):
- if selected_year:
- df = df.value
- filtered_df = df[df.year == selected_year]
- print(f"[PROCESSOR|selected_year={selected_year}]")
- print(filtered_df)
-
-
-class LitApp(LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_dash = LitDash()
- self.processor = Processor(parallel=True)
-
- def run(self):
- self.lit_dash.run()
-
- # Launch some processing based on the Dash Dashboard.
- self.processor.run(self.lit_dash.df, self.lit_dash.selected_year)
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_dash}
- return tab1
-
-
-app = LightningApp(LitApp())
diff --git a/docs/source-app/workflows/add_web_ui/dash/intermediate_state.py b/docs/source-app/workflows/add_web_ui/dash/intermediate_state.py
deleted file mode 100644
index d30bdc8c02c25..0000000000000
--- a/docs/source-app/workflows/add_web_ui/dash/intermediate_state.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import dash
-import dash_daq as daq
-import dash_renderjson
-from dash import html, Input, Output
-
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.utilities.state import AppState
-
-
-class LitDash(LightningWork):
- def run(self):
- dash_app = dash.Dash(__name__)
-
- dash_app.layout = html.Div([daq.ToggleSwitch(id="my-toggle-switch", value=False), html.Div(id="output")])
-
- @dash_app.callback(Output("output", "children"), [Input("my-toggle-switch", "value")])
- def display_output(value):
- if value:
- state = AppState()
- state._request_state()
- return dash_renderjson.DashRenderjson(id="input", data=state._state, max_depth=-1, invert_theme=True)
-
- dash_app.run_server(host=self.host, port=self.port)
-
-
-class LitApp(LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_dash = LitDash(parallel=True)
-
- def run(self):
- self.lit_dash.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_dash}
- return tab1
-
-
-app = LightningApp(LitApp())
diff --git a/docs/source-app/workflows/add_web_ui/example_app.rst b/docs/source-app/workflows/add_web_ui/example_app.rst
deleted file mode 100644
index e5d2cbbd3f8d4..0000000000000
--- a/docs/source-app/workflows/add_web_ui/example_app.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:orphan:
-
-###########
-Example App
-###########
-
-This is an example app that needs to be built for this part of the docs.
diff --git a/docs/source-app/workflows/add_web_ui/glossary_front_end.rst b/docs/source-app/workflows/add_web_ui/glossary_front_end.rst
deleted file mode 100644
index ce51ef12aa77b..0000000000000
--- a/docs/source-app/workflows/add_web_ui/glossary_front_end.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-########
-Frontend
-########
-Web pages visible to users are also known as **front-ends**. Lightning Apps can have multiple
-types of Frontends.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/add_web_ui/glossary_ui.rst b/docs/source-app/workflows/add_web_ui/glossary_ui.rst
deleted file mode 100644
index bc9e4f529e3b6..0000000000000
--- a/docs/source-app/workflows/add_web_ui/glossary_ui.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-###################
-UI (User Interface)
-###################
-We use (UI) as short for a **web page** with interactions. Lightning Apps can have multiple
-types of UIs.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/add_web_ui/gradio/basic.rst b/docs/source-app/workflows/add_web_ui/gradio/basic.rst
deleted file mode 100644
index 4f5ab87b8b8d4..0000000000000
--- a/docs/source-app/workflows/add_web_ui/gradio/basic.rst
+++ /dev/null
@@ -1,217 +0,0 @@
-################################
-Add a web UI with Gradio (basic)
-################################
-**Audience:** Users who want to add a web UI written with Python.
-
-**Prereqs:** Basic python knowledge.
-
-----
-
-***************
-What is Gradio?
-***************
-Gradio is a Python library that automatically generates a web interface to demo a machine learning model.
-
-----
-
-*****************
-Install gradio
-*****************
-First, install gradio.
-
-.. code:: bash
-
- pip install gradio
-
-----
-
-**************************
-Create the gradio demo app
-**************************
-To explain how to use Gradio with Lightning, let's replicate the |gradio_link|.
-
-.. |gradio_link| raw:: html
-
- example running here
-
-In the next few sections we'll build an app step-by-step.
-First **create a file named app.py** with the app content:
-
-.. code:: python
-
- import lightning as L
- from lightning.app.components import ServeGradio
- import gradio as gr
-
- class LitGradio(ServeGradio):
-
- inputs = gr.inputs.Textbox(default='lightning', label='name input')
- outputs = gr.outputs.Textbox(label='output')
- examples = [["hello lightning"]]
-
- def predict(self, input_text):
- return self.model(input_text)
-
- def build_model(self):
- fake_model = lambda x: f"hello {x}"
- return fake_model
-
- class RootFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_gradio = LitGradio()
-
- def run(self):
- self.lit_gradio.run()
-
- def configure_layout(self):
- return [{"name": "home", "content": self.lit_gradio}]
-
- app = L.LightningApp(RootFlow())
-
-add "gradio" to a requirements.txt file:
-
-.. code:: bash
-
- echo 'gradio' >> requirements.txt
-
-this is a best practice to make apps reproducible.
-
-----
-
-***********
-Run the app
-***********
-Run the app locally to see it!
-
-.. code:: python
-
- lightning run app app.py
-
-Now run it on the cloud as well:
-
-.. code:: python
-
- lightning run app app.py --cloud
-
-----
-
-************************
-Step-by-step walkthrough
-************************
-In this section, we explain each part of this code in detail.
-
-----
-
-Create a Gradio component
-^^^^^^^^^^^^^^^^^^^^^^^^^
-To create a Gradio component, simply take any Gradio app and subclass it from ``ServeGradio``.
-If you haven't created a Gradio demo, you have to implement the following elements:
-
-1. Input which is text.
-2. Output which is text.
-3. A build_model function.
-4. A predict function.
-
-|
-
-Here's an example:
-
-.. code:: python
- :emphasize-lines: 4
-
- from lightning.app.components import ServeGradio
- import gradio as gr
-
- class LitGradio(ServeGradio):
-
- inputs = gr.inputs.Textbox(default='lightning', label='name input')
- outputs = gr.outputs.Textbox(label='output')
-
- def predict(self, input_text):
- return self.model(input_text)
-
- def build_model(self):
- fake_model = lambda x: f"hello {x}"
- return fake_model
-
-This fake model simply concatenates 2 strings.
-
-----
-
-Route the UI in the root component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Now, tell the Root component in which tab to render this component's UI.
-In this case, we render the ``LitGradio`` UI in the ``home`` tab of the application.
-
-.. code:: python
- :emphasize-lines: 21, 27
-
- import lightning as L
- from lightning.app.components import ServeGradio
- import gradio as gr
-
- class LitGradio(ServeGradio):
-
- inputs = gr.inputs.Textbox(default='lightning', label='name input')
- outputs = gr.outputs.Textbox(label='output')
- examples = [["hello lightning"]]
-
- def predict(self, input_text):
- return self.model(input_text)
-
- def build_model(self):
- fake_model = lambda x: f"hello {x}"
- return fake_model
-
- class RootFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_gradio = LitGradio()
-
- def run(self):
- self.lit_gradio.run()
-
- def configure_layout(self):
- return [{"name": "home", "content": self.lit_gradio}]
-
- app = L.LightningApp(RootFlow())
-
-----
-
-Call run
-^^^^^^^^
-Finally, don't forget to call run inside the Root Flow to serve the Gradio app.
-
-.. code:: python
- :emphasize-lines: 24
-
- import lightning as L
- from lightning.app.components import ServeGradio
- import gradio as gr
-
- class LitGradio(ServeGradio):
-
- inputs = gr.inputs.Textbox(default='lightning', label='name input')
- outputs = gr.outputs.Textbox(label='output')
- examples = [["hello lightning"]]
-
- def predict(self, input_text):
- return self.model(input_text)
-
- def build_model(self):
- fake_model = lambda x: f"hello {x}"
- return fake_model
-
- class RootFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_gradio = LitGradio()
-
- def run(self):
- self.lit_gradio.run()
-
- def configure_layout(self):
- return [{"name": "home", "content": self.lit_gradio}]
-
- app = L.LightningApp(RootFlow())
diff --git a/docs/source-app/workflows/add_web_ui/gradio/index.rst b/docs/source-app/workflows/add_web_ui/gradio/index.rst
deleted file mode 100644
index 740ae93aae0c4..0000000000000
--- a/docs/source-app/workflows/add_web_ui/gradio/index.rst
+++ /dev/null
@@ -1,84 +0,0 @@
-:orphan:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- basic
- intermediate
-
-########################
-Add a web UI with Gradio
-########################
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1: Connect Gradio
- :description: Learn how to connect Gradio to a Lightning Component.
- :col_css: col-md-6
- :button_link: basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: 2: Enable two-way communication
- :description: Enable two-way communication between Gradio and a Lightning App.
- :col_css: col-md-6
- :button_link: intermediate.html
- :height: 150
- :tag: [documentation coming soon]
-
-.. raw:: html
-
-
-
-
-----
-
-********
-Examples
-********
-Here are a few example apps that use a Gradio web UI.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/gradio/intermediate.rst b/docs/source-app/workflows/add_web_ui/gradio/intermediate.rst
deleted file mode 100644
index bb20d566243b1..0000000000000
--- a/docs/source-app/workflows/add_web_ui/gradio/intermediate.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-#######################################
-Add a web UI with Gradio (intermediate)
-#######################################
-
-.. note:: documentation coming soon.
-
-
-*************************************
-Interact with a component from the UI
-*************************************
-
-.. warning:: is there such a thing for this with gradio?
-
-
-----
-
-*************************************
-Interact with the UI from a component
-*************************************
-
-.. warning:: is there such a thing for this with gradio?
diff --git a/docs/source-app/workflows/add_web_ui/html/basic.rst b/docs/source-app/workflows/add_web_ui/html/basic.rst
deleted file mode 100644
index cb9bb5293f7c9..0000000000000
--- a/docs/source-app/workflows/add_web_ui/html/basic.rst
+++ /dev/null
@@ -1,166 +0,0 @@
-##############################
-Add a web UI with HTML (basic)
-##############################
-**Audience:** Users who want to add a web UI written in HTMlapp.
-
-**Prereqs:** Basic html knowledge.
-
-----
-
-*************
-What is HTML?
-*************
-HyperText Markup Language (HTML) is the Language used to create web pages. Use HTML for simple
-web user interfaces that tend to be more static.
-
-For reactive web applications, we recommend using: React.js, Angular.js or Vue.js
-
-----
-
-*******************
-Create an HTML page
-*******************
-The first step is to create an HTML file named **index.html**:
-
-.. code:: html
-
-
-
-
-
-
- Hello World
-
-
-
-----
-
-************************
-Create the HTML demo app
-************************
-
-..
- To explain how to use html with Lightning, let's replicate the |html_app_link|.
-
- .. |html_app_link| raw:: html
-
- example running here
-
-In the next few sections we'll build an app step-by-step.
-First **create a file named app.py** with the app content (in the same folder as index.html):
-
-.. code:: bash
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
-
-
- class HelloComponent(L.LightningFlow):
- def configure_layout(self):
- return frontend.StaticWebFrontend(serve_dir='.')
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.hello_component = HelloComponent()
-
- def run(self):
- self.hello_component.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.hello_component}
- return tab1
-
-
- app = L.LightningApp(LitApp())
-
-----
-
-***********
-Run the app
-***********
-Run the app locally to see it!
-
-.. code:: python
-
- lightning run app app.py
-
-Now run it on the cloud as well:
-
-.. code:: python
-
- lightning run app app.py --cloud
-
-----
-
-************************
-Step-by-step walkthrough
-************************
-In this section, we explain each part of this code in detail.
-
-----
-
-Enable an HTML UI for the component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Give the component an HTML UI, by returning a ``StaticWebFrontend`` object from the ``configure_layout`` method:
-
-.. code:: bash
- :emphasize-lines: 6,7
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
-
- class HelloComponent(L.LightningFlow):
- def configure_layout(self):
- return frontend.StaticWebFrontend(serve_dir='.')
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.hello_component = HelloComponent()
-
- def run(self):
- self.hello_component.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.hello_component}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-The folder path given in ``StaticWebFrontend(serve_dir=)`` must point to a folder with an ``index.html`` page.
-
-----
-
-Route the UI in the root component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The final step, is to tell the Root component in which tab to render this component's UI.
-In this case, we render the ``HelloComponent`` UI in the ``home`` tab of the application.
-
-.. code:: python
- :emphasize-lines: 18, 19
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
-
- class HelloComponent(L.LightningFlow):
- def configure_layout(self):
- return frontend.StaticWebFrontend(serve_dir='.')
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.hello_component = HelloComponent()
-
- def run(self):
- self.hello_component.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.hello_component}
- return tab1
-
- app = L.LightningApp(LitApp())
diff --git a/docs/source-app/workflows/add_web_ui/html/index.rst b/docs/source-app/workflows/add_web_ui/html/index.rst
deleted file mode 100644
index 0eae9309877a7..0000000000000
--- a/docs/source-app/workflows/add_web_ui/html/index.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-:orphan:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- basic
- intermediate
-
-######################
-Add a web UI with HTML
-######################
-**Audience:** Users who want to add a web UI using plain html.
-
-**Prereqs:** Basic html knowledge.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1: Connect HTML
- :description: Learn how to connect an HTML app.
- :col_css: col-md-6
- :button_link: basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: 2: Enable two-way communication
- :description: Enable two-way communication between HTML and a Lightning App.
- :col_css: col-md-6
- :button_link: intermediate.html
- :height: 150
- :tag: [docs coming soon]
-
-.. raw:: html
-
-
-
-
-----
-
-********
-Examples
-********
-Here are a few example apps that use an HTML web UI.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/html/intermediate.rst b/docs/source-app/workflows/add_web_ui/html/intermediate.rst
deleted file mode 100644
index 17b6b585c0173..0000000000000
--- a/docs/source-app/workflows/add_web_ui/html/intermediate.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-#####################################
-Add a web UI with HTML (intermediate)
-#####################################
-**Audience:** Users who want to add a web UI using plain html.
-
-**Prereqs:** Must have read the :doc:`html basic ` guide.
-
-----
-
-*******************************
-Interact with the App from HTML
-*******************************
-.. note:: documentation in progress
-
-----
-
-***********************************
-Interact with HTML from a component
-***********************************
-.. note:: documentation in progress
diff --git a/docs/source-app/workflows/add_web_ui/index.rst b/docs/source-app/workflows/add_web_ui/index.rst
deleted file mode 100644
index 79c0f16d66409..0000000000000
--- a/docs/source-app/workflows/add_web_ui/index.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-
-#############################
-Add a web user interface (UI)
-#############################
-
-**Audience:** Users who want to add a UI to their Lightning Apps
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/add_web_ui/index_content.rst b/docs/source-app/workflows/add_web_ui/index_content.rst
deleted file mode 100644
index f3d516c5af546..0000000000000
--- a/docs/source-app/workflows/add_web_ui/index_content.rst
+++ /dev/null
@@ -1,121 +0,0 @@
-*************************************
-Web UIs for non Javascript Developers
-*************************************
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Dash
- :description: Learn how to add a web UI built in Python with Dash.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/dash/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Gradio
- :description: Learn how to add a web UI built in Python with Gradio.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/gradio/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Panel
- :description: Learn how to add a web UI built in Python with Panel.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/panel/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Jupyter Notebook
- :description: Learn how to enable a web UI that is a Jupyter Notebook.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/jupyter_basic.html
- :height: 150
- :tag: [docs coming soon]
-
-.. displayitem::
- :header: Streamlit
- :description: Learn how to add a web UI built in Python with Streamlit.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/streamlit/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: JustPy
- :description: Learn how to add a web UI built in Python with JustPy.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/justpy/index.html
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
-
-----
-
-*********************************
-Web UIs for Javascript Developers
-*********************************
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Any javascript framework
- :description: Learn how to link up any javascript framework to a Lightning app.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/integrate_any_javascript_framework.html
- :height: 150
- :tag: advanced
-
-.. displayitem::
- :header: Angular.js
- :description: Learn how to add a web UI built in Javascript with Angular.js
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/angular_js_intermediate.html
- :height: 150
- :tag: [Docs coming soon]
-
-.. displayitem::
- :header: HTML
- :description: Learn how to add a web UI built with html.
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/html/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: React.js
- :description: Learn how to add a web UI built in Javascript with React.js
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/react/index.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Vue.js
- :description: Learn how to add a web UI built in Javascript with Vue.js
- :col_css: col-md-4
- :button_link: ../../workflows/add_web_ui/vue_js_intermediate.html
- :height: 150
- :tag: [Docs coming soon]
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/integrate_any_javascript_framework.rst b/docs/source-app/workflows/add_web_ui/integrate_any_javascript_framework.rst
deleted file mode 100644
index 66625289a6e49..0000000000000
--- a/docs/source-app/workflows/add_web_ui/integrate_any_javascript_framework.rst
+++ /dev/null
@@ -1,164 +0,0 @@
-:orphan:
-
-##################################
-Integrate any javascript framework
-##################################
-**Audience:** Advanced web developers with complex apps that may not have been covered by the other tutorials
-
-**Pre-requisites:** Intermediate knowledge of html and javascript
-
-----
-
-************************
-Import LightningState.js
-************************
-To connect any javascript framework, import the `LightningState.js `_ library.
-LightningState.js enables two-way communication between a javascript framework and a Lightning app.
-
-To import this library, add this to your html:
-
-.. code:: html
-
-
-
-Once it's imported, use it inside your app, this example uses it inside a React App:
-
-.. code-block::
- :emphasize-lines: 1, 5
-
- import { useLightningState } from "./hooks/useLightningState";
- import cloneDeep from "lodash/cloneDeep";
-
- function App() {
- const { lightningState, updateLightningState } = useLightningState();
-
- const modify_and_send_back_the_state = async (event: ChangeEvent) => {
- if (lightningState) {
- const newLightningState = cloneDeep(lightningState);
- // Update the state and send it back.
- newLightningState.flows.counter += 1
-
- updateLightningState(newLightningState);
- }
- };
-
- return (
-
-
- );
- }
-
- export default App;
-
-----
-
-************************
-Update the Lightning app
-************************
-Use `updateLightningState` to update the lightning app. Here we update a variable called counter.
-
-.. code-block::
- :emphasize-lines: 11
-
- import { useLightningState } from "./hooks/useLightningState";
- import cloneDeep from "lodash/cloneDeep";
-
- function App() {
- const { lightningState, updateLightningState } = useLightningState();
-
- const modify_and_send_back_the_state = async (event: ChangeEvent) => {
- if (lightningState) {
- const newLightningState = cloneDeep(lightningState);
- // Update the state and send it back.
- newLightningState.flows.counter += 1
-
- updateLightningState(newLightningState);
- }
- };
-
- return (
-
-
- );
- }
-
- export default App;
-
-----
-
-**************************************
-Receive updates from the Lightning app
-**************************************
-Whenever a variable in the Lightning app changes, the javascript app will receive those values via `lightningState`.
-
-Extract any variable from the state and update the javascript app:
-
-.. code-block::
- :emphasize-lines: 5
-
- import { useLightningState } from "./hooks/useLightningState";
- import cloneDeep from "lodash/cloneDeep";
-
- function App() {
- const { lightningState, updateLightningState } = useLightningState();
-
- const modify_and_send_back_the_state = async (event: ChangeEvent) => {
- if (lightningState) {
- const newLightningState = cloneDeep(lightningState);
- // Update the state and send it back.
- newLightningState.flows.counter += 1
-
- updateLightningState(newLightningState);
- }
- };
-
- return (
-
-
- );
- }
-
- export default App;
-
-----
-
-********
-Examples
-********
-
-See this in action in these examples:
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: React.js
- :description: Explore how React.js uses lightningState.js
- :col_css: col-md-4
- :button_link: react/communicate_between_react_and_lightning.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/jupyter_basic.rst b/docs/source-app/workflows/add_web_ui/jupyter_basic.rst
deleted file mode 100644
index 61f58ab40670d..0000000000000
--- a/docs/source-app/workflows/add_web_ui/jupyter_basic.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-:orphan:
-
-#####################################
-Add a Jupyter Notebook web UI (basic)
-#####################################
-**Audience:** Users who want to enable a Jupyter notebook UI.
-
-**Prereqs:** Basic python knowledge.
-
-TODO
-
-----
-
-***************************
-What is a Jupyter Notebook?
-***************************
-
-TODO
-
-----
-
-*******************
-Install Jupyter Lab
-*******************
-
-First, install Jupyter Lab.
-
-.. code:: bash
-
- pip install jupyterlab
-
-----
-
-********
-Examples
-********
-Here are a few example apps that use Jupyter Lab.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: TODO
- :col_css: col-md-4
- :button_link: angular_js_intermediate.html
- :height: 150
-
-.. displayitem::
- :header: Example 2
- :description: TODO
- :col_css: col-md-4
- :button_link: angular_js_intermediate.html
- :height: 150
-
-.. displayitem::
- :header: Example 3
- :description: TODO
- :col_css: col-md-4
- :button_link: angular_js_intermediate.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/justpy/index.rst b/docs/source-app/workflows/add_web_ui/justpy/index.rst
deleted file mode 100644
index 1626930c13d82..0000000000000
--- a/docs/source-app/workflows/add_web_ui/justpy/index.rst
+++ /dev/null
@@ -1,92 +0,0 @@
-:orphan:
-
-########################
-Add a web UI with JustPy
-########################
-
-
-******
-JustPy
-******
-
-The `JustPy `_ framework is an object oriented high-level Python Web Framework that requires no JavaScript programming, while at the same time providing the full flexibility of a frontend framework.
-
-Additionally, it provides a higher level API called `Quasar `_ with stylized components.
-
-
-You can install ``justpy`` from PyPi.
-
-.. code-block::
-
- pip install justpy
-
-*******
-Example
-*******
-
-
-In the following example, we are creating a simple UI with 2 buttons.
-When clicking the first button, the flow state ``counter`` is incremented and re-rendered on the UI.
-
-
-First of all, you would need to import the ``JustPyFrontend`` and return it from the ``configure_layout`` hook of the flow.
-
-.. code-block::
-
- from typing import Callable
-
- from lightning import LightningApp, LightningFlow
- from lightning.app.frontend import JustPyFrontend
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- print(self.counter)
-
- def configure_layout(self):
- return JustPyFrontend(render_fn=render_fn)
-
-Secondly, you would need to implement a ``render_fn`` that takes as input a ``get_state`` function and return a function.
-
-
-.. code-block::
-
- def render_fn(get_state: Callable) -> Callable:
- import justpy as jp
-
- def webpage():
- wp = jp.QuasarPage(dark=True)
- # the `a=wp` argument adds the div to the web page
- d = jp.Div(classes="q-pa-md q-gutter-sm", a=wp)
- container = jp.QBtn(color="primary", text="Counter: 0")
-
- async def click(*_):
- state = get_state()
- state.counter += 1
- container.text = f"Counter: {state.counter}"
-
- button = jp.QBtn(color="primary", text="Click Me!", click=click)
-
- d.add(button)
- d.add(container)
-
- return wp
-
- return webpage
-
-
-Finally, you can wrap your flow in a LightningAp.
-
-.. code-block::
-
- app = LightningApp(Flow())
-
-Now, you can run the Lightning App with:
-
-.. code-block::
-
- lightning_app run app app.py
diff --git a/docs/source-app/workflows/add_web_ui/panel/basic.rst b/docs/source-app/workflows/add_web_ui/panel/basic.rst
deleted file mode 100644
index b033312f6f0d1..0000000000000
--- a/docs/source-app/workflows/add_web_ui/panel/basic.rst
+++ /dev/null
@@ -1,369 +0,0 @@
-:orphan:
-
-###############################
-Add a web UI with Panel (basic)
-###############################
-
-**Audience:** Users who want to add a web UI written with Python and Panel.
-
-**Prereqs:** Basic Python knowledge.
-
-----
-
-**************
-What is Panel?
-**************
-
-`Panel`_ and the `HoloViz`_ ecosystem provide unique and powerful
-features such as big data visualization using `DataShader`_, easy cross filtering
-using `HoloViews`_, streaming and much more.
-
-* Panel is highly flexible and ties into the PyData and Jupyter ecosystems as you can develop in notebooks and use ipywidgets. You can also develop in .py files.
-
-* Panel is one of the most popular data app frameworks in Python with `more than 400.000 downloads a month `_. It's especially popular in the scientific community.
-
-* Panel is used, for example, by Rapids to power `CuxFilter`_, a CuDF based big data visualization framework.
-
-* Panel can be deployed on your favorite server or cloud including `Lightning`_.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-intro.gif
- :alt: Example Panel App
-
- Example Panel App
-
-Panel is **particularly well suited for Lightning Apps** that need to display live progress. This is because the Panel server can react
-to state changes and asynchronously push messages from the server to the client using web socket communication.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-streaming-intro.gif
- :alt: Example Panel Streaming App
-
- Example Panel Streaming App
-
-Install Panel with:
-
-.. code:: bash
-
- pip install panel
-
-----
-
-*********************
-Run a basic Panel App
-*********************
-
-In the next few sections, we'll build an App step-by-step.
-
-First, create a file named ``app_panel.py`` with the App content:
-
-.. code:: python
-
- # app_panel.py
-
- import panel as pn
-
- pn.panel("Hello **Panel ⚡** World").servable()
-
-Then, create a file named ``app.py`` with the following App content:
-
-.. code:: python
-
- # app.py
-
- import lightning as L
- from lightning.app.frontend import PanelFrontend
-
-
- class LitPanel(L.LightningFlow):
-
- def configure_layout(self):
- return PanelFrontend("app_panel.py")
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_panel = LitPanel()
-
- def run(self):
- self.lit_panel.run()
-
- def configure_layout(self):
- return {"name": "home", "content": self.lit_panel}
-
-
- app = L.LightningApp(LitApp())
-
-Finally, add ``panel`` to your ``requirements.txt`` file:
-
-.. code:: bash
-
- echo 'panel' >> requirements.txt
-
-.. note:: This is a best practice to make Apps reproducible.
-
-----
-
-***********
-Run the App
-***********
-
-Run the App locally:
-
-.. code:: bash
-
- lightning_app run app app.py
-
-The App should look like this:
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-lightning-basic.png
- :alt: Basic Panel Lightning App
-
- Basic Panel Lightning App
-
-Now, run it on the cloud:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud
-
-----
-
-*************************
-Step-by-step walk-through
-*************************
-
-In this section, we explain each part of the code in detail.
-
-----
-
-0. Define a Panel app
-^^^^^^^^^^^^^^^^^^^^^
-
-First, find the Panel app you want to integrate. In this example, that app looks like:
-
-.. code:: python
-
- import panel as pn
-
- pn.panel("Hello **Panel ⚡** World").servable()
-
-Refer to the `Panel documentation `_ and `awesome-panel `_ for more complex examples.
-
-----
-
-1. Add Panel to a Component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Link this app to the Lightning App by using the ``PanelFrontend`` class which needs to be returned from
-the ``configure_layout`` method of the Lightning Component you want to connect to Panel.
-
-.. code:: python
- :emphasize-lines: 7-10
-
- import lightning as L
- from lightning.app.frontend import PanelFrontend
-
-
- class LitPanel(L.LightningFlow):
-
- def configure_layout(self):
- return PanelFrontend("app_panel.py")
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_panel = LitPanel()
-
- def run(self):
- self.lit_panel.run()
-
- def configure_layout(self):
- return {"name": "home", "content": self.lit_panel}
-
-
- app = L.LightningApp(LitApp())
-
-The argument of the ``PanelFrontend`` class, points to the script, notebook, or function that
-runs your Panel app.
-
-----
-
-2. Route the UI in the root component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The second step, is to tell the Root component in which tab to render this component's UI.
-In this case, we render the ``LitPanel`` UI in the ``home`` tab of the app.
-
-.. code:: python
- :emphasize-lines: 19-20
-
- import lightning as L
- from lightning.app.frontend import PanelFrontend
-
-
- class LitPanel(L.LightningFlow):
-
- def configure_layout(self):
- return PanelFrontend("app_panel.py")
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_panel = LitPanel()
-
- def run(self):
- self.lit_panel.run()
-
- def configure_layout(self):
- return {"name": "home", "content": self.lit_panel}
-
- app = L.LightningApp(LitApp())
-
-----
-
-*************
-Tips & Tricks
-*************
-
-0. Use autoreload while developing
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-To speed up your development workflow, you can run your Lightning App with Panel **autoreload** by
-setting the environment variable ``PANEL_AUTORELOAD`` to ``yes``.
-
-Try running the following:
-
-.. code-block::
-
- PANEL_AUTORELOAD=yes lightning run app app.py
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-lightning-autoreload.gif
- :alt: Basic Panel Lightning App with autoreload
-
- Basic Panel Lightning App with autoreload
-
-1. Theme your App
-^^^^^^^^^^^^^^^^^
-
-To theme your App you, can use the Lightning accent color ``#792EE5`` with the `FastListTemplate`_.
-
-Try replacing the contents of ``app_panel.py`` with the following:
-
-.. code:: bash
-
- # app_panel.py
-
- import panel as pn
- import plotly.express as px
-
- ACCENT = "#792EE5"
-
- pn.extension("plotly", sizing_mode="stretch_width", template="fast")
- pn.state.template.param.update(
- title="⚡ Hello Panel + Lightning ⚡", accent_base_color=ACCENT, header_background=ACCENT
- )
-
- pn.config.raw_css.append(
- """
- .bk-root:first-of-type {
- height: calc( 100vh - 200px ) !important;
- }
- """
- )
-
-
- def get_panel_theme():
- """Returns 'default' or 'dark'"""
- return pn.state.session_args.get("theme", [b"default"])[0].decode()
-
-
- def get_plotly_template():
- if get_panel_theme() == "dark":
- return "plotly_dark"
- return "plotly_white"
-
-
- def get_plot(length=5):
- xseries = [index for index in range(length + 1)]
- yseries = [x**2 for x in xseries]
- fig = px.line(
- x=xseries,
- y=yseries,
- template=get_plotly_template(),
- color_discrete_sequence=[ACCENT],
- range_x=(0, 10),
- markers=True,
- )
- fig.layout.autosize = True
- return fig
-
-
- length = pn.widgets.IntSlider(value=5, start=1, end=10, name="Length")
- dynamic_plot = pn.panel(
- pn.bind(get_plot, length=length), sizing_mode="stretch_both", config={"responsive": True}
- )
- pn.Column(length, dynamic_plot).servable()
-
-
-Install some additional libraries and remember to add the dependencies to the ``requirements.txt`` file:
-
-
-.. code:: bash
-
- echo 'plotly' >> requirements.txt
- echo 'pandas' >> requirements.txt
-
-Finally run the App
-
-.. code:: bash
-
- lightning_app run app app.py
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-lightning-theme.gif
- :alt: Basic Panel Plotly Lightning App with theming
-
- Basic Panel Plotly Lightning App with theming
-
-.. _Panel: https://panel.holoviz.org/
-.. _FastListTemplate: https://panel.holoviz.org/reference/templates/FastListTemplate.html#templates-gallery-fastlisttemplate
-.. _HoloViz: https://holoviz.org/
-.. _DataShader: https://datashader.org/
-.. _HoloViews: https://holoviews.org/
-.. _Lightning: https://lightning.ai/
-.. _CuxFilter: https://github.com/rapidsai/cuxfilter
-.. _AwesomePanel: https://github.com/awesome-panel/awesome-panel
-
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: 2: Enable two-way communication
- :description: Enable two-way communication between Panel and a Lightning App.
- :col_css: col-md-6
- :button_link: intermediate.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Add a web user interface (UI)
- :description: Users who want to add a UI to their Lightning Apps
- :col_css: col-md-6
- :button_link: ../index.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/panel/index.rst b/docs/source-app/workflows/add_web_ui/panel/index.rst
deleted file mode 100644
index 0d48a1dc9f7ea..0000000000000
--- a/docs/source-app/workflows/add_web_ui/panel/index.rst
+++ /dev/null
@@ -1,85 +0,0 @@
-:orphan:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- basic
- intermediate
-
-#######################
-Add a web UI with Panel
-#######################
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1: Connect Panel
- :description: Learn how to connect Panel to a Lightning Component.
- :col_css: col-md-6
- :button_link: basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: 2: Enable two-way communication
- :description: Enable two-way communication between Panel and a Lightning App.
- :col_css: col-md-6
- :button_link: intermediate.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
-
-----
-
-********
-Examples
-********
-
-Here are a few example apps that use a Panel web UI.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/panel/intermediate.rst b/docs/source-app/workflows/add_web_ui/panel/intermediate.rst
deleted file mode 100644
index 20cf141248739..0000000000000
--- a/docs/source-app/workflows/add_web_ui/panel/intermediate.rst
+++ /dev/null
@@ -1,210 +0,0 @@
-:orphan:
-
-######################################
-Add a web UI with Panel (intermediate)
-######################################
-
-**Audience:** Users who want to communicate between the Lightning App and Panel.
-
-**Prereqs:** Must have read the :doc:`Panel basic ` guide.
-
-----
-
-**************************************
-Interact with the Component from Panel
-**************************************
-
-The ``PanelFrontend`` enables user interactions with the Lightning App using widgets.
-You can modify the state variables of a Lightning Component using the ``AppStateWatcher``.
-
-For example, here we increase the ``count`` variable of the Lightning Component every time a user
-presses a button:
-
-.. code:: python
-
- # app_panel.py
-
- import panel as pn
- from lightning.app.frontend import AppStateWatcher
-
- pn.extension(sizing_mode="stretch_width")
-
- app = AppStateWatcher()
-
- submit_button = pn.widgets.Button(name="submit")
-
- @pn.depends(submit_button, watch=True)
- def submit(_):
- app.state.count += 1
-
- @pn.depends(app.param.state)
- def current_count(_):
- return f"current count: {app.state.count}"
-
- pn.Column(
- submit_button,
- current_count,
- ).servable()
-
-
-
-.. code:: python
-
- # app.py
-
- import lightning as L
- from lightning.app.frontend import PanelFrontend
-
- class LitPanel(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.count = 0
- self.last_count = 0
-
- def run(self):
- if self.count != self.last_count:
- self.last_count = self.count
- print("Count changed to: ", self.count)
-
- def configure_layout(self):
- return PanelFrontend("app_panel.py")
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_panel = LitPanel()
-
- def run(self):
- self.lit_panel.run()
-
- def configure_layout(self):
- return {"name": "home", "content": self.lit_panel}
-
-
- app = L.LightningApp(LitApp())
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-lightning-counter-from-frontend.gif
- :alt: Panel Lightning App updating a counter from the frontend
-
- Panel Lightning App updating a counter from the frontend
-
-----
-
-************************************
-Interact with Panel from a Component
-************************************
-
-To update the `PanelFrontend` from any Lightning Component, update the property in the Component.
-Make sure to call the ``run`` method from the parent component.
-
-In this example, we update the ``count`` value of the Component:
-
-.. code:: python
-
- # app_panel.py
-
- import panel as pn
- from lightning.app.frontend import AppStateWatcher
-
- app = AppStateWatcher()
-
- pn.extension(sizing_mode="stretch_width")
-
- def counter(state):
- return f"Counter: {state.count}"
-
- last_update = pn.bind(counter, app.param.state)
-
- pn.panel(last_update).servable()
-
-.. code:: python
-
- # app.py
-
- from datetime import datetime as dt
- from lightning.app.frontend import PanelFrontend
-
- import lightning as L
-
-
- class LitPanel(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.count = 0
- self._last_update = dt.now()
-
- def run(self):
- now = dt.now()
- if (now - self._last_update).microseconds >= 250:
- self.count += 1
- self._last_update = now
- print("Counter changed to: ", self.count)
-
- def configure_layout(self):
- return PanelFrontend("app_panel.py")
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_panel = LitPanel()
-
- def run(self):
- self.lit_panel.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_panel}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-lightning-counter-from-component.gif
- :alt: Panel Lightning App updating a counter from the component
-
- Panel Lightning App updating a counter from the Component
-
-----
-
-*************
-Tips & Tricks
-*************
-
-* Caching: Panel provides the easy to use ``pn.state.cache`` memory based, ``dict`` caching. If you are looking for something persistent try `DiskCache `_ its really powerful and simple to use. You can use it to communicate large amounts of data between the components and frontend(s).
-
-* Notifications: Panel provides easy to use `notifications `_. You can for example use them to provide notifications about runs starting or ending.
-
-* Tabulator Table: Panel provides the `Tabulator table `_ which features expandable rows. The table is useful to provide for example an overview of you runs. But you can dig into the details by clicking and expanding the row.
-
-* Task Scheduling: Panel provides easy to use `task scheduling `_. You can use this to for example read and display files created by your components on a scheduled basis.
-
-* Terminal: Panel provides the `Xterm.js terminal `_ which can be used to display live logs from your components and allow you to provide a terminal interface to your component.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/panel-lightning-github-runner.gif
- :alt: Panel Lightning App running models on github
-
- Panel Lightning App running models on GitHub
-
-----
-
-**********
-Next Steps
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add a web user interface (UI)
- :description: Users who want to add a UI to their Lightning Apps
- :col_css: col-md-6
- :button_link: ../index.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/react/communicate_between_react_and_lightning.rst b/docs/source-app/workflows/add_web_ui/react/communicate_between_react_and_lightning.rst
deleted file mode 100644
index ef836e7e4b0d1..0000000000000
--- a/docs/source-app/workflows/add_web_ui/react/communicate_between_react_and_lightning.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-#######################################
-Communicate Between React and Lightning
-#######################################
-**Audience:** Anyone who wants to add a web user interface (UI) written in react to their app.
-
-**pre-requisites:** Make sure you've already connected the React and Lightning app.
-
-**Difficulty level:** intermediate.
-
-----
-
-************
-Example code
-************
-To illustrate how to communicate between a React app and a lightning App, we'll be using the `example_app.py` file
-which :doc:`lightning init react-ui ` created:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
-
-and the App.tsx file also created by :doc:`lightning init react-ui `:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/ui/src/App.tsx
-
-----
-
-******************************
-Update React --> Lightning app
-******************************
-To change the Lightning app from the React app, use `updateLightningState`.
-
-In this example, when you press **Start printing** in the React UI, it toggles
-the `react_ui.vars.should_print`:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/ui/src/App.tsx
- :emphasize-lines: 20, 21, 23
-
-By changing that variable in the Lightning app state, it sets **react_ui.should_print** to True, which enables the
-Lightning app to print:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
- :emphasize-lines: 10, 22
-
-----
-
-******************************
-Update React <-- Lightning app
-******************************
-To change the React app from the Lightning app, use the values from the `lightningState`.
-
-In this example, when the ``react_ui.counter`` increaes in the Lightning app:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
- :emphasize-lines: 18, 24
-
-The React UI updates the text on the screen to reflect the count
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/ui/src/App.tsx
- :emphasize-lines: 15
diff --git a/docs/source-app/workflows/add_web_ui/react/connect_react_and_lightning.rst b/docs/source-app/workflows/add_web_ui/react/connect_react_and_lightning.rst
deleted file mode 100644
index bacdd3d299b05..0000000000000
--- a/docs/source-app/workflows/add_web_ui/react/connect_react_and_lightning.rst
+++ /dev/null
@@ -1,107 +0,0 @@
-################################
-Connect React to a Lightning app
-################################
-**Audience:** Users who already have a react app and want to connect it to a Lightning app.
-
-**pre-requisites:** Make sure you already have a react app you want to connect.
-
-**Difficulty level:** intermediate.
-
-----
-
-************
-Example code
-************
-To illustrate how to connect a React app and a lightning App, we'll be using the `example_app.py` file
-which :doc:`lightning_app init react-ui ` created:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
-
-and the App.tsx file also created by :doc:`lightning_app init react-ui `:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/ui/src/App.tsx
-
-----
-
-*************************************
-Connect the component to the react UI
-*************************************
-The first step is to connect the dist folder of the react app using `StaticWebFrontend`:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
- :emphasize-lines: 13
-
-the dist folder must contain an index.html file which is generated by the compilating command `yarn build` which
-we'll explore later.
-
-----
-
-**********************************
-Connect component to the root flow
-**********************************
-Next, connect your component to the root flow. Display the react app on the tab of your choice
-using `configure_layout`:
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
- :emphasize-lines: 19, 27
-
-----
-
-*********************************
-Connect React and Lightning state
-*********************************
-At this point, the React app will render in the Lightning app. Test it out!
-
-.. code:: bash
-
- lightning_app run app example_app.py
-
-However, to make powerful React+Lightning apps, you must also connect the Lightning App state to the react app.
-These lines enable two-way communication between the react app and the Lightning app.
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/ui/src/App.tsx
- :emphasize-lines: 10, 13
-
-----
-
-****************
-Component vs App
-****************
-Notice that in this guide, we connected a single react app to a single component.
-
-.. literalinclude:: ../../../../../src/lightning/app/cli/react-ui-template/example_app.py
- :emphasize-lines: 6-13
-
-You can use this single react app for the FULL Lightning app, or you can specify a React app for EACH component.
-
-.. code:: python
- :emphasize-lines: 5, 9, 18-20
-
- import lightning as L
-
-
- class ComponentA(L.LightningFlow):
- def configure_layout(self):
- return L.app.frontend.StaticWebFrontend(Path(__file__).parent / "react_app_1/dist")
-
-
- class ComponentB(L.LightningFlow):
- def configure_layout(self):
- return L.app.frontend.StaticWebFrontend(Path(__file__).parent / "react_app_2/dist")
-
-
- class HelloLitReact(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.react_app_1 = ComponentA()
- self.react_app_2 = ComponentB()
-
- def configure_layout(self):
- tab_1 = {"name": "App 1", "content": self.react_app_1}
- tab_2 = {"name": "App 2", "content": self.react_app_2}
- return tab_1, tab_2
-
-
- app = L.LightningApp(HelloLitReact())
-
-This is a powerful idea that allows each Lightning component to have a self-contained web UI.
diff --git a/docs/source-app/workflows/add_web_ui/react/create_react_template.rst b/docs/source-app/workflows/add_web_ui/react/create_react_template.rst
deleted file mode 100644
index a5626bc32dcc1..0000000000000
--- a/docs/source-app/workflows/add_web_ui/react/create_react_template.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-######################################
-Create a React Template (intermediate)
-######################################
-**Audience:** Anyone who wants to add a web user interface (UI) written in react to their app.
-
-----
-
-**************
-What is react?
-**************
-`React.js `_ is a JavaScript library for building user interfaces.
-A huge number of websites are written in React.js (like Facebook).
-
-----
-
-************************
-Bring your own React app
-************************
-If you already have a React.js app, then you don't need the section below. However, it might be helpful
-to see our React template so you can understand how to connect it to a Lightning app.
-
-----
-
-****************************
-Create the react-ui template
-****************************
-Lightning can generate a react-ui template out of the box (generated with `Vite `_).
-
-Run this command to set up a react-ui template for a component:
-
-.. code:: bash
-
- lightning init react-ui
-
-If everything was successful, run the example_app.py listed in the output of the command:
-
-.. code:: bash
-
- INFO: Checking pre-requisites for react
- INFO:
- found npm version: 8.5.5
- found node version: 16.15.0
- found yarn version: 1.22.10
-
- ...
- ...
-
- ⚡ run the example_app.py to see it live!
- lightning run app react-ui/example_app.py
-
-If the command didn't work, make sure to install `npm+nodejs `_, and `yarn `_.
diff --git a/docs/source-app/workflows/add_web_ui/react/index.rst b/docs/source-app/workflows/add_web_ui/react/index.rst
deleted file mode 100644
index ba0f8d97d67d1..0000000000000
--- a/docs/source-app/workflows/add_web_ui/react/index.rst
+++ /dev/null
@@ -1,106 +0,0 @@
-:orphan:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- create_react_template
- connect_react_and_lightning
- communicate_between_react_and_lightning
- react_development_workflow
-
-##########################
-Add a web UI with React.js
-##########################
-**Audience:** Anyone who wants to add a web user interface (UI) written in react to their app.
-
-**Prereqs:** Basic html knowledge.
-
-----
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1: Create a React project template
- :description: Use our React template to start a react app or bring your own.
- :col_css: col-md-6
- :button_link: create_react_template.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: 2: Connect a React app and a Lightning app
- :description: Learn how to connect a React app to a Lightning app.
- :col_css: col-md-6
- :button_link: connect_react_and_lightning.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: 3: Communicate between React and Lightning
- :description: Learn how to communicate between a React app and a Lightning app.
- :col_css: col-md-6
- :button_link: communicate_between_react_and_lightning.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: 4: Develop like a React pro
- :description: Learn the development workflow of a React developer.
- :col_css: col-md-6
- :button_link: react_development_workflow.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
-
-----
-
-********
-Examples
-********
-Here are a few example apps that use a React web UI.
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/react/react_development_workflow.rst b/docs/source-app/workflows/add_web_ui/react/react_development_workflow.rst
deleted file mode 100644
index 02d855f383d69..0000000000000
--- a/docs/source-app/workflows/add_web_ui/react/react_development_workflow.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-#########################################
-Add a web UI with React.js (intermediate)
-#########################################
-**Audience:** Anyone who wants to add a web user interface (UI) written in react to their app.
-
-**pre-requisites:** You already have a React app connected with a Lightning app.
-
-----
-
-**********************
-Develop your react app
-**********************
-Every time you make a change to your React.js app, you must call `yarn build` to apply the changes (this is a React.js thing):
-
-.. code:: bash
-
- # if you're lost, the right folder has a package.json in it
- cd folder-with-ui-folder/ui
- yarn build
-
-This can get very repetitive, there is a "hot reload" command that you can enable with:
-
-.. code:: bash
-
- # TODO
-
-There are many other tricks that React.js developers use to improve their development speed.
diff --git a/docs/source-app/workflows/add_web_ui/streamlit/basic.rst b/docs/source-app/workflows/add_web_ui/streamlit/basic.rst
deleted file mode 100644
index ced0314af54be..0000000000000
--- a/docs/source-app/workflows/add_web_ui/streamlit/basic.rst
+++ /dev/null
@@ -1,186 +0,0 @@
-###################################
-Add a web UI with Streamlit (basic)
-###################################
-**Audience:** Users who want to add a web UI written with Python.
-
-**Prereqs:** Basic python knowledge.
-
-----
-
-******************
-What is Streamlit?
-******************
-Streamlit is a web user interface builder for Python developers. Streamlit builds beautiful web pages
-directly from Python.
-
-Install Streamlit with:
-
-.. code:: bash
-
- pip install streamlit
-
-----
-
-*************************
-Run a basic streamlit app
-*************************
-
-..
- To explain how to use Streamlit with Lightning, let's replicate the |st_link|.
-
- .. |st_link| raw:: html
-
- example running here
-
-In the next few sections we'll build an app step-by-step.
-First **create a file named app.py** with the app content:
-
-.. code:: python
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
- import streamlit as st
-
- def your_streamlit_app(lightning_app_state):
- st.write('hello world')
-
- class LitStreamlit(L.LightningFlow):
- def configure_layout(self):
- return frontend.StreamlitFrontend(render_fn=your_streamlit_app)
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_streamlit = LitStreamlit()
-
- def run(self):
- self.lit_streamlit.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_streamlit}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-add "streamlit" to a requirements.txt file:
-
-.. code:: bash
-
- echo 'streamlit' >> requirements.txt
-
-this is a best practice to make apps reproducible.
-
-----
-
-***********
-Run the app
-***********
-Run the app locally to see it!
-
-.. code:: python
-
- lightning run app app.py
-
-Now run it on the cloud as well:
-
-.. code:: python
-
- lightning run app app.py --cloud
-
-----
-
-************************
-Step-by-step walkthrough
-************************
-In this section, we explain each part of this code in detail.
-
-----
-
-0. Define a streamlit app
-^^^^^^^^^^^^^^^^^^^^^^^^^
-First, find the streamlit app you want to integrate. In this example, that app looks like:
-
-.. code:: python
-
- import streamlit as st
-
- def your_streamlit_app():
- st.write('hello world')
-
-Refer to the `Streamlit documentation `_ for more complex examples.
-
-----
-
-1. Add Streamlit to a component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Link this function to the Lightning App by using the ``StreamlitFrontend`` class which needs to be returned from
-the ``configure_layout`` method of the Lightning component you want to connect to Streamlit.
-
-.. code:: python
- :emphasize-lines: 9-11
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
- import streamlit as st
-
- def your_streamlit_app(lightning_app_state):
- st.write('hello world')
-
- class LitStreamlit(L.LightningFlow):
- def configure_layout(self):
- return frontend.StreamlitFrontend(render_fn=your_streamlit_app)
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_streamlit = LitStreamlit()
-
- def run(self):
- self.lit_streamlit.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_streamlit}
- return tab1
-
- app = L.LightningApp(LitApp())
-
-The ``render_fn`` argument of the ``StreamlitFrontend`` class, points to a function that runs your Streamlit app.
-The first argument to the function is the lightning app state. Any changes to the app state update the app.
-
-----
-
-2. Route the UI in the root component
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The second step, is to tell the Root component in which tab to render this component's UI.
-In this case, we render the ``LitStreamlit`` UI in the ``home`` tab of the application.
-
-.. code:: python
- :emphasize-lines: 22
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
- import streamlit as st
-
- def your_streamlit_app(lightning_app_state):
- st.write('hello world')
-
- class LitStreamlit(L.LightningFlow):
- def configure_layout(self):
- return frontend.StreamlitFrontend(render_fn=your_streamlit_app)
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_streamlit = LitStreamlit()
-
- def run(self):
- self.lit_streamlit.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_streamlit}
- return tab1
-
- app = L.LightningApp(LitApp())
diff --git a/docs/source-app/workflows/add_web_ui/streamlit/index.rst b/docs/source-app/workflows/add_web_ui/streamlit/index.rst
deleted file mode 100644
index 2496729d45660..0000000000000
--- a/docs/source-app/workflows/add_web_ui/streamlit/index.rst
+++ /dev/null
@@ -1,84 +0,0 @@
-:orphan:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- basic
- intermediate
-
-###########################
-Add a web UI with Streamlit
-###########################
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: 1: Connect Streamlit
- :description: Learn how to connect Streamlit to a Lightning Component.
- :col_css: col-md-6
- :button_link: basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: 2: Enable two-way communication
- :description: Enable two-way communication between Streamlit and a Lightning App.
- :col_css: col-md-6
- :button_link: intermediate.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
-
-----
-
-********
-Examples
-********
-Here are a few example apps that use a Streamlit web UI.
-
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Example 1
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 2
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. displayitem::
- :header: Example 3
- :description: Show off your work! Contribute an example.
- :col_css: col-md-4
- :button_link: ../../../contribute_app.html
- :height: 150
- :tag: Waiting for contributed example
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/add_web_ui/streamlit/intermediate.rst b/docs/source-app/workflows/add_web_ui/streamlit/intermediate.rst
deleted file mode 100644
index a89b6e6a5ce55..0000000000000
--- a/docs/source-app/workflows/add_web_ui/streamlit/intermediate.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-##########################################
-Add a web UI with Streamlit (intermediate)
-##########################################
-**Audience:** Users who want to communicate between the Lightning App and Streamlit.
-
-**Prereqs:** Must have read the :doc:`streamlit basic ` guide.
-
-----
-
-************************************
-Interact with the App from Streamlit
-************************************
-The streamlit UI enables user interactions with the Lightning App via UI elements like buttons.
-To modify the variables of a Lightning component, access the ``lightning_app_state`` variable in .
-
-For example, here we increase the count variable of the Lightning Component every time a user presses a button:
-
-.. code:: python
- :emphasize-lines: 8, 14
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
- import streamlit as st
-
-
- def your_streamlit_app(lightning_app_state):
- if st.button("press to increase count"):
- lightning_app_state.count += 1
- st.write(f"current count: {lightning_app_state.count}")
-
-
- class LitStreamlit(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.count = 0
-
- def configure_layout(self):
- return frontend.StreamlitFrontend(render_fn=your_streamlit_app)
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_streamlit = LitStreamlit()
-
- def run(self):
- self.lit_streamlit.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_streamlit}
- return tab1
-
-
- app = L.LightningApp(LitApp())
-
-----
-
-****************************************
-Interact with Streamlit from a component
-****************************************
-To update the streamlit UI from any Lightning component, update the property in the component and make sure to call ``run`` from the
-parent component.
-
-In this example we update the value of the counter from the component:
-
-.. code:: python
- :emphasize-lines: 7, 15
-
- # app.py
- import lightning as L
- import lightning.app.frontend as frontend
- import streamlit as st
-
-
- def your_streamlit_app(lightning_app_state):
- st.write(f"current count: {lightning_app_state.count}")
-
-
- class LitStreamlit(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.count = 0
-
- def run(self):
- self.count += 1
-
- def configure_layout(self):
- return frontend.StreamlitFrontend(render_fn=your_streamlit_app)
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_streamlit = LitStreamlit()
-
- def run(self):
- self.lit_streamlit.run()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_streamlit}
- return tab1
-
-
- app = L.LightningApp(LitApp())
diff --git a/docs/source-app/workflows/add_web_ui/vue_js_intermediate.rst b/docs/source-app/workflows/add_web_ui/vue_js_intermediate.rst
deleted file mode 100644
index e8d9f3e843155..0000000000000
--- a/docs/source-app/workflows/add_web_ui/vue_js_intermediate.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-:orphan:
-
-#######################################
-Add a web UI with Vue.js (intermediate)
-#######################################
-coming...
diff --git a/docs/source-app/workflows/arrange_tabs/arrange_app_basic.rst b/docs/source-app/workflows/arrange_tabs/arrange_app_basic.rst
deleted file mode 100644
index 91c0e53854760..0000000000000
--- a/docs/source-app/workflows/arrange_tabs/arrange_app_basic.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-########################
-Arrange app tabs (basic)
-########################
-**Audience:** Users who want to control the layout of their app user interface.
-
-----
-
-*****************************
-Enable a full-page single tab
-*****************************
-
-To enable a single tab on the app UI, return a single dictionary from the ``configure_layout`` method:
-
-.. code:: python
- :emphasize-lines: 9
-
- import lightning as L
-
-
- class DemoComponent(L.demo.dumb_component):
- def configure_layout(self):
- tab1 = {"name": "THE TAB NAME", "content": self.component_a}
- return tab1
-
-
- app = L.LightningApp(DemoComponent())
-
-
-The "name" key defines the visible name of the tab on the UI. It also shows up in the URL.
-The **"content"** key defines the target component to render in that tab.
-When returning a single tab element like shown above, the UI will display it in full-page mode.
-
-
-----
-
-********************
-Enable multiple tabs
-********************
-
-.. code:: python
- :emphasize-lines: 7
-
- import lightning as L
-
-
- class DemoComponent(L.demo.dumb_component):
- def configure_layout(self):
- tab1 = {"name": "Tab A", "content": self.component_a}
- tab2 = {"name": "Tab B", "content": self.component_b}
- return tab1, tab2
-
-
- app = L.LightningApp(DemoComponent())
-
-The order matters! Try any of the following configurations:
-
-.. code:: python
- :emphasize-lines: 4, 9
-
- def configure_layout(self):
- tab1 = {"name": "Tab A", "content": self.component_a}
- tab2 = {"name": "Tab B", "content": self.component_b}
- return tab1, tab2
-
-
- def configure_layout(self):
- tab1 = {"name": "Tab A", "content": self.component_a}
- tab2 = {"name": "Tab B", "content": self.component_b}
- return tab2, tab1
diff --git a/docs/source-app/workflows/arrange_tabs/arrange_app_intermediate.rst b/docs/source-app/workflows/arrange_tabs/arrange_app_intermediate.rst
deleted file mode 100644
index 1bb638b1d2c0c..0000000000000
--- a/docs/source-app/workflows/arrange_tabs/arrange_app_intermediate.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-###############################
-Arrange app tabs (intermediate)
-###############################
-
-.. TODO:: fill-in
-
-----
-
-***********************************
-Render components with a defined UI
-***********************************
-
-component directly
-
-----
-
-*************
-Render a link
-*************
-
-tensorboard link
diff --git a/docs/source-app/workflows/arrange_tabs/index.rst b/docs/source-app/workflows/arrange_tabs/index.rst
deleted file mode 100644
index f639c0238e1bb..0000000000000
--- a/docs/source-app/workflows/arrange_tabs/index.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-################
-Arrange App Tabs
-################
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/arrange_tabs/index_content.rst b/docs/source-app/workflows/arrange_tabs/index_content.rst
deleted file mode 100644
index 66ac9328e67a7..0000000000000
--- a/docs/source-app/workflows/arrange_tabs/index_content.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- arrange_app_basic
- arrange_app_intermediate
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Basic
- :description: Learn how to enable and layout your app UI
- :col_css: col-md-6
- :button_link: arrange_app_basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Intermediate
- :description: Learn about all the possible ways of rendering a component.
- :col_css: col-md-6
- :button_link: arrange_app_intermediate.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_command_line_interface/app.py b/docs/source-app/workflows/build_command_line_interface/app.py
deleted file mode 100644
index 373aa2bfd137e..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/app.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from commands.notebook.run import RunNotebook, RunNotebookConfig
-from lit_jupyter import JupyterLab
-
-from lightning.app import LightningFlow, LightningApp, CloudCompute
-from lightning.app.structures import Dict
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.notebooks = Dict()
-
- # 1. Annotates the handler input with the Notebook config.
- def run_notebook(self, config: RunNotebookConfig):
- if config.name in self.notebooks:
- return f"The Notebook {config.name} already exists."
- else:
- # 2. Dynamically creates the Notebook if it doesn't exist and runs it.
- self.notebooks[config.name] = JupyterLab(
- cloud_compute=CloudCompute(config.cloud_compute)
- )
- self.notebooks[config.name].run()
- return f"The Notebook {config.name} was created."
-
- def configure_commands(self):
- # 3. Returns a list of dictionaries with the format:
- # {"command_name": CustomClientCommand(method=self.custom_server_handler)}
- return [{"run notebook": RunNotebook(method=self.run_notebook)}]
-
- def configure_layout(self):
- # 4. Dynamically displays the Notebooks in the Lightning App View.
- return [{"name": n, "content": w} for n, w in self.notebooks.items()]
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/workflows/build_command_line_interface/cli.rst b/docs/source-app/workflows/build_command_line_interface/cli.rst
deleted file mode 100644
index 176e416b31630..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/cli.rst
+++ /dev/null
@@ -1,144 +0,0 @@
-:orphan:
-
-###########################################
-1. Develop a CLI with server side code only
-###########################################
-
-We are going to learn how to create a simple command-line interface.
-
-Lightning provides a flexible way to create complex CLI without much effort.
-
-----
-
-*************************
-1. Implement a simple CLI
-*************************
-
-To create your first CLI, you need to override the :class:`~lightning.app.core.flow.LightningFlow.configure_commands` hook and return a list of dictionaries where the keys are the commands and the values are the server side handlers.
-
-First, create a file ``app.py`` and copy-paste the following code in to the file:
-
-.. literalinclude:: example_command.py
-
-----
-
-**************
-2. Run the App
-**************
-
-Execute the following command in a terminal:
-
-.. code-block::
-
- lightning_app run app app.py
-
-The following appears the terminal:
-
-.. code-block::
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- []
-
-----
-
-***************************
-3. Connect to a running App
-***************************
-
-In another terminal, connect to the running App.
-When you connect to an App, the Lightning CLI is replaced by the App CLI. To exit the App CLI, you need to run ``lightning_app disconnect``.
-
-.. code-block::
-
- lightning_app connect localhost
-
-To see a list of available commands:
-
-.. code-block::
-
- lightning_app --help
- You are connected to the cloud Lightning App: localhost.
- Usage: lightning_app [OPTIONS] COMMAND [ARGS]...
-
- --help Show this message and exit.
-
- Lightning App Commands
- add Add a name.
-
-To find the arguments of the commands:
-
-.. code-block::
-
- lightning_app add --help
- You are connected to the cloud Lightning App: localhost.
- Usage: lightning_app add [ARGS]...
-
- Options
- name: Add description
-
-----
-
-********************
-4. Execute a command
-********************
-
-Trigger the command line exposed by your App:
-
-.. code-block::
-
- lightning_app add --name=my_name
- WARNING: Lightning Command Line Interface is an experimental feature and unannounced changes are likely.
-
-In your first terminal, **Received name: my_name** and **["my_name"]** are printed.
-
-.. code-block::
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- []
- Received name: my_name
- ["my_name]
-
-----
-
-**************************
-5. Disconnect from the App
-**************************
-
-To exit the App CLI, you need to run ``lightning_app disconnect``.
-
-.. code-block::
-
- lightning_app disconnect
- You are disconnected from the local Lightning App.
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: 2. Implement a CLI with client side code execution
- :description: Learn how to develop a complex API for your application
- :col_css: col-md-6
- :button_link: cli_client.html
- :height: 150
-
-.. displayitem::
- :header: Develop a RESTful API
- :description: Learn how to develop an API for your application.
- :col_css: col-md-6
- :button_link: ../build_rest_api/index.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_command_line_interface/cli_client.rst b/docs/source-app/workflows/build_command_line_interface/cli_client.rst
deleted file mode 100644
index 589db930bf5fa..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/cli_client.rst
+++ /dev/null
@@ -1,175 +0,0 @@
-:orphan:
-
-######################################################
-2. Develop a CLI with server and client code execution
-######################################################
-
-We've learned how to create a simple command-line interface. But in real-world use-cases, an App Builder wants to provide more complex functionalities where trusted code is executed on the client side.
-
-Lightning provides a flexible way to create complex CLI without much effort.
-
-In this example, we’ll create a CLI to dynamically run Notebooks:
-
-
-----
-
-**************************
-1. Implement a complex CLI
-**************************
-
-First of all, lets' create the following file structure:
-
-.. code-block:: python
-
- app_folder/
- commands/
- notebook/
- run.py
- app.py
-
-We'll use the `Jupyter-Component `_. Follow the installation steps on the repo to install the Component.
-
-Add the following code to ``commands/notebook/run.py``:
-
-.. literalinclude:: commands/notebook/run.py
-
-Add the following code to ``app.py``:
-
-.. literalinclude:: app.py
-
-----
-
-**********************************************
-2. Run the App and check the API documentation
-**********************************************
-
-In a terminal, run the following command and open ``http://127.0.0.1:7501/docs`` in a browser.
-
-.. code-block:: python
-
- lightning_app run app app.py
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
-
-----
-
-***************************
-3. Connect to a running App
-***************************
-
-In another terminal, connect to the running App.
-When you connect to an App, the Lightning CLI is replaced by the App CLI. To exit the App CLI, you need to run ``lightning_app disconnect``.
-
-.. code-block::
-
- lightning_app connect localhost
-
- Storing `run_notebook` under /Users/thomas/.lightning/lightning_connection/commands/run_notebook.py
- You can review all the downloaded commands under /Users/thomas/.lightning/lightning_connection/commands folder.
- You are connected to the local Lightning App.
-
-To see a list of available commands:
-
-.. code-block::
-
- lightning_app --help
-
- You are connected to the cloud Lightning App: localhost.
- Usage: lightning_app [OPTIONS] COMMAND [ARGS]...
-
- --help Show this message and exit.
-
- Lightning App Commands
- run notebook Run a Notebook.
-
-
-To find the arguments of the commands:
-
-.. code-block::
-
- lightning_app run notebook --help
-
- You are connected to the cloud Lightning App: localhost.
- usage: notebook [-h] [--name NAME] [--cloud_compute CLOUD_COMPUTE]
-
- Run Notebook Parser
-
- optional arguments:
- -h, --help show this help message and exit
- --name NAME
- --cloud_compute CLOUD_COMPUTE
-
-----
-
-********************
-4. Execute a command
-********************
-
-And then you can trigger the command-line exposed by your App.
-
-Run the first Notebook with the following command:
-
-.. code-block:: python
-
- lightning_app run notebook --name="my_notebook"
- WARNING: Lightning Command Line Interface is an experimental feature and unannounced changes are likely.
- The notebook my_notebook was created.
-
-And run a second notebook.
-
-.. code-block:: python
-
- lightning_app run notebook --name="my_notebook_2"
- WARNING: Lightning Command Line Interface is an experimental feature and unannounced changes are likely.
- The notebook my_notebook_2 was created.
-
-Here is a recording of the Lightning App:
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/commands_1.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/commands_1.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-**************************
-5. Disconnect from the App
-**************************
-
-To exit the App CLI, you need to run **lightning disconnect**.
-
-.. code-block::
-
- lightning_app disconnect
- You are disconnected from the local Lightning App.
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: 1. Develop a CLI with server side code only
- :description: Learn how to develop a simple CLI for your App.
- :col_css: col-md-6
- :button_link: cli.html
- :height: 150
-
-.. displayitem::
- :header: Develop a RESTful API
- :description: Learn how to develop an API for your App.
- :col_css: col-md-6
- :button_link: ../build_rest_api/index.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_command_line_interface/commands/__init__.py b/docs/source-app/workflows/build_command_line_interface/commands/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/docs/source-app/workflows/build_command_line_interface/commands/notebook/__init__.py b/docs/source-app/workflows/build_command_line_interface/commands/notebook/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/docs/source-app/workflows/build_command_line_interface/commands/notebook/run.py b/docs/source-app/workflows/build_command_line_interface/commands/notebook/run.py
deleted file mode 100644
index e0a6463c2d6a3..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/commands/notebook/run.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from argparse import ArgumentParser
-from uuid import uuid4
-
-from pydantic import BaseModel
-
-from lightning.app.utilities.commands import ClientCommand
-
-
-class RunNotebookConfig(BaseModel):
- name: str
- cloud_compute: str
-
-
-class RunNotebook(ClientCommand):
- description = "Run a Notebook."
-
- def run(self):
- # 1. Define your own argument parser. You can use argparse, click, etc...
- parser = ArgumentParser(description='Run Notebook Parser')
- parser.add_argument("--name", type=str, default=None)
- parser.add_argument("--cloud_compute", type=str, default="cpu")
- hparams = parser.parse_args()
-
- # 2. Invoke the server side handler by sending a payload.
- response = self.invoke_handler(
- config=RunNotebookConfig(
- name=hparams.name or str(uuid4()),
- cloud_compute=hparams.cloud_compute,
- ),
- )
-
- # 3. Print the server response.
- print(response)
diff --git a/docs/source-app/workflows/build_command_line_interface/example_command.py b/docs/source-app/workflows/build_command_line_interface/example_command.py
deleted file mode 100644
index 4d837fc007171..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/example_command.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from lightning import LightningApp, LightningFlow
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.names = []
-
- def run(self):
- print(self.names)
-
- def add_name(self, name: str):
- """Add a name."""
- print(f"Received name: {name}")
- self.names.append(name)
-
- def configure_commands(self):
- # This can be invoked with `lightning add --name=my_name`
- commands = [
- {"add": self.add_name},
- ]
- return commands
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/workflows/build_command_line_interface/index.rst b/docs/source-app/workflows/build_command_line_interface/index.rst
deleted file mode 100644
index 9a3e24f784910..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/index.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-############################
-Command-line Interface (CLI)
-############################
-
-**Audience:** Users looking to create a command line interface (CLI) for their application.
-
-----
-
-**************
-What is a CLI?
-**************
-
-A Command-line Interface (CLI) is an user interface (UI) in a terminal to interact with a specific program.
-
-.. note::
-
- The Lightning guideline to build CLI is `lightning_app ...` or ` ...`.
-
-As an example, Lightning provides a CLI to interact with your Lightning Apps and the `lightning.ai `_ platform as follows:
-
-.. code-block:: bash
-
- main
- ├── fork - Forks an App.
- ├── init - Initializes a Lightning App and/or Component.
- │ ├── app
- │ ├── component
- │ ├── pl-app - Creates an App from your PyTorch Lightning source files.
- │ └── react-ui - Creates a React UI to give a Lightning Component a React.js web UI
- ├── install - Installs a Lightning App and/or Component.
- │ ├── app
- │ └── component
- ├── list - Lists Lightning AI self-managed resources (apps)
- │ └── apps - Lists your Lightning AI Apps.
- ├── login - Logs in to your lightning.ai account.
- ├── logout - Logs out of your lightning.ai account.
- ├── run - Runs a Lightning App locally or on the cloud.
- │ └── app - Runs an App from a file.
- ├── show - Shows given resource.
- │ └── logs - Shows cloud application logs. By default prints logs for all currently available Components.
- ├── stop - Stops your App.
- └── tree - Shows the command tree of your CLI.
-
-Learn more about `Command-line interfaces here `_.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/build_command_line_interface/index_content.rst b/docs/source-app/workflows/build_command_line_interface/index_content.rst
deleted file mode 100644
index ced369dbfd815..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/index_content.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-**************************************
-Develop a command line interface (CLI)
-**************************************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: 1. Develop a CLI with server side code only
- :description: Learn how to develop a simple CLI for your application
- :col_css: col-md-6
- :button_link: cli.html
- :height: 150
-
-.. displayitem::
- :header: 2. Develop a CLI with server and client code execution
- :description: Learn how to develop a complex CLI for your application
- :col_css: col-md-6
- :button_link: cli_client.html
- :height: 150
-
-.. raw:: html
-
-
-
-
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a RESTful API
- :description: Learn how to develop an API for your application.
- :col_css: col-md-6
- :button_link: ../build_rest_api/index.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_command_line_interface/post_example.py b/docs/source-app/workflows/build_command_line_interface/post_example.py
deleted file mode 100644
index c57a2d9426539..0000000000000
--- a/docs/source-app/workflows/build_command_line_interface/post_example.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from lightning.app import LightningFlow, LightningApp
-from lightning.app.api import Post
-
-
-class Flow(LightningFlow):
- # 1. Define the state
- def __init__(self):
- super().__init__()
- self.names = []
-
- # 2. Optional, but used to validate names
- def run(self):
- print(self.names)
-
- # 3. Method executed when a request is received.
- def handle_post(self, name: str):
- self.names.append(name)
- return f'The name {name} was registered'
-
- # 4. Defines this Component's Restful API. You can have several routes.
- def configure_api(self):
- # Your own defined route and handler
- return [Post(route="/name", method=self.handle_post)]
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/workflows/build_lightning_app/from_pytorch_lightning_script.rst b/docs/source-app/workflows/build_lightning_app/from_pytorch_lightning_script.rst
deleted file mode 100644
index f489edaedc13f..0000000000000
--- a/docs/source-app/workflows/build_lightning_app/from_pytorch_lightning_script.rst
+++ /dev/null
@@ -1,109 +0,0 @@
-#######################################################
-Develop a Lightning App from a PyTorch Lightning script
-#######################################################
-
-**Audience:** Users who want to develop a Lightning App (App) from their PyTorch Lightning (PL) scripts.
-
-----
-
-*************************************************************
-What developing a Lightning App from a PL script does for you
-*************************************************************
-
-Developing an App from a PL script allows you to immediately run on the cloud and share the progress with friends.
-Once you're happy with your model, you can immediately expand beyond just model development to things like
-making your own inference APIs, research demos, or even speeding up your data pipeline.
-
-The PyTorch Lightning App is your entry point to the full end-to-end ML licefycle.
-
-----
-
-******************
-Develop a template
-******************
-
-To develop a template from a PyTorch Lightning script, use this command:
-
-.. code:: bash
-
- lightning_app init pl-app path/to/the/pl_script.py
-
-
-If your script is not at the root of the project folder, and you'd like to include all source files within that folder, you can specify the root path as the first argument:
-
-.. code:: bash
-
- lightning_app init pl-app path/to/project/root path/to/the/pl_script.py
-
-
-The default trainer App lets you train a model with a beautiful UI locally and on the cloud with zero effort!
-
-----
-
-***********
-Run the App
-***********
-
-.. note:: This section is under construction.
-
-Run the App locally:
-
-.. code:: bash
-
- lightning_app run app pl-app/app.py
-
-Or run the App on the cloud so you can share with collaborators and even use all the cloud GPUs you want.
-
-.. code:: bash
-
- lightning_app run app pl-app/app.py --cloud
-
-
-.. figure:: https://storage.googleapis.com/grid-packages/pytorch-lightning-app/docs-thumbnail.png
- :alt: Screenshot of the PyTorch Lightning app running in the cloud
-
-
-----
-
-*******************
-Modify the template
-*******************
-
-The command above generates an App file like this:
-
-.. TODO:: list the file and show how to extend it
-
-.. code:: python
-
- from your_app_name import ComponentA, ComponentB
-
- import lightning as L
-
-
- class LitApp(L.LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component_a = ComponentA()
- self.component_b = ComponentB()
-
- def run(self):
- self.component_a.run()
- self.component_b.run()
-
-
- app = L.LightningApp(LitApp())
-
-Now you can add your own components as you wish!
-
-----
-
-************
-Known issues
-************
-
-- The UI takes a couple seconds to load when opening the App, so please be patient.
-- The timer resets when refreshing the page.
-- The UI for adding new environment variables does not provide an option to delete an entry.
-- A bug exists that leaves the script hanging at the start of training when using the DDP strategy.
-- DDP-spawn is not supported due to pickling issues.
-- It is currently not possible to submit a new run once the script has finished or failed.
diff --git a/docs/source-app/workflows/build_lightning_app/from_scratch.rst b/docs/source-app/workflows/build_lightning_app/from_scratch.rst
deleted file mode 100644
index 9042f105711cd..0000000000000
--- a/docs/source-app/workflows/build_lightning_app/from_scratch.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-####################################
-Develop a Lightning App from Scratch
-####################################
-
-**Audience:** Users who want to develop a Lightning App from scratch.
-
-**Prereqs:** You must have finished the `Basic levels `_.
-
-----
-
-.. include:: from_scratch_content.rst
diff --git a/docs/source-app/workflows/build_lightning_app/from_scratch_content.rst b/docs/source-app/workflows/build_lightning_app/from_scratch_content.rst
deleted file mode 100644
index a528fa6088736..0000000000000
--- a/docs/source-app/workflows/build_lightning_app/from_scratch_content.rst
+++ /dev/null
@@ -1,60 +0,0 @@
-
-**************
-WAIT!
-**************
-Before you build a Lightning App from scratch, see if you can find an app that is similar to what you need
-in the `Lightning App Gallery `_.
-
-Once you find the Lightning App you want, press "Clone & Run" to see it running on the cloud, then download the code
-and change what you want!
-
-----
-
-******************
-Build from scratch
-******************
-If you didn't find a Lightning App similar to the one you need, simply create a file named **app.py** with these contents:
-
-.. code:: python
-
- import lightning as L
-
-
- class WordComponent(L.LightningWork):
- def __init__(self, word):
- super().__init__()
- self.word = word
-
- def run(self):
- print(self.word)
-
-
- class LitApp(L.LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.hello = WordComponent("hello")
- self.world = WordComponent("world")
-
- def run(self):
- print("This is a simple Lightning app, make a better app!")
- self.hello.run()
- self.world.run()
-
-
- app = L.LightningApp(LitApp())
-
-----
-
-Run the Lightning App
-^^^^^^^^^^^^^^^^^^^^^
-Run the Lightning App locally:
-
-.. code:: bash
-
- lightning_app run app app.py
-
-Run the Lightning App on the cloud:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud
diff --git a/docs/source-app/workflows/build_lightning_app/index.rst b/docs/source-app/workflows/build_lightning_app/index.rst
deleted file mode 100644
index e60f0355afb8e..0000000000000
--- a/docs/source-app/workflows/build_lightning_app/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-:orphan:
-
-#######################
-Develop a Lightning App
-#######################
-
-A Lightning App (App) is a collection of components interacting together. Learn how to develop a basic App template.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/build_lightning_app/index_content.rst b/docs/source-app/workflows/build_lightning_app/index_content.rst
deleted file mode 100644
index 45264d85a4adc..0000000000000
--- a/docs/source-app/workflows/build_lightning_app/index_content.rst
+++ /dev/null
@@ -1,32 +0,0 @@
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- from_scratch
- from_pytorch_lightning_script
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a Lightning App from scratch
- :description: Learn how to Develop a Lightning App from scratch
- :col_css: col-md-6
- :button_link: from_scratch.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Develop an App from a PyTorch Lightning script
- :description: Share your PyTorch Lightning training on the cloud, run on cloud GPUs, or extend your App
- :col_css: col-md-6
- :button_link: from_pytorch_lightning_script.html
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_lightning_component/basic.rst b/docs/source-app/workflows/build_lightning_component/basic.rst
deleted file mode 100644
index 07fac58cf21da..0000000000000
--- a/docs/source-app/workflows/build_lightning_component/basic.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-#############################
-Develop a Lightning Component
-#############################
-
-**Audience:** Users who want to develop a Lightning Component.
-
-----
-
-.. include:: from_scratch_component_content.rst
diff --git a/docs/source-app/workflows/build_lightning_component/from_scratch_component_content.rst b/docs/source-app/workflows/build_lightning_component/from_scratch_component_content.rst
deleted file mode 100644
index b29c19db40129..0000000000000
--- a/docs/source-app/workflows/build_lightning_component/from_scratch_component_content.rst
+++ /dev/null
@@ -1,153 +0,0 @@
-*******************************
-LightningFlow vs. LightningWork
-*******************************
-
-.. _flow_vs_work:
-
-.. raw:: html
-
-
-
-There are two types of components in Lightning, **LightningFlow** and **LightningWork**.
-
-Use a **LightningFlow** component for any programming logic that runs in less than 1 second.
-
-.. code:: python
-
- for i in range(10):
- print(f"{i}: this kind of code belongs in a LightningFlow")
-
-Use a **LightningWork** component for any programming logic that takes more than 1 second or requires its own hardware.
-
-.. code:: python
-
- from time import sleep
-
- for i in range(100000):
- sleep(2.0)
- print(f"{i} LightningWork: work that is long running or may never end (like a server)")
-
-----
-
-**************************************************
-What developing a Lightning Component does for you
-**************************************************
-Lightning Components break up complex systems into modular components. The first obvious benefit is that components
-can be reused across other apps. This means you can build once, test it and forget it.
-
-As a researcher it also means that your code can be taken to production without needing a team of engineers to help
-productionize it.
-
-As a machine learning engineer, it means that your cloud system is:
-
-- fault tolerant
-- cloud agnostic
-- testable (unlike YAML/CI/CD code)
-- version controlled
-- enables cross-functional collaboration
-
-----
-
-**************
-WAIT!
-**************
-Before you build a Lightning component from scratch, see if you can find a component that is similar to what you need
-in the `Lightning component Gallery `_.
-
-Once you find the component you want, download the code and change what you want!
-
-----
-
-*****************************************
-Build a Lighitning component from scratch
-*****************************************
-If you didn't find a Lightning component similar to the one you need, you can build one from scratch.
-
-----
-
-Build a LightningFlow
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-To implement a LightningFlow, simply subclass ``LightningFlow`` and define the run method:
-
-.. code:: python
- :emphasize-lines: 5
-
- # app.py
- import lightning as L
-
-
- class LitFlow(L.LightningFlow):
- def run(self):
- for i in range(10):
- print(f"{i}: this kind of code belongs in a LightningFlow")
-
-
- app = L.LightningApp(LitFlow())
-
-run the app
-
-.. code:: bash
-
- lightning_app run app app.py
-
-----
-
-Build a LightningWork
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Only implement a LightningWork if this particular piece of code:
-
-- takes more than 1 second to execute
-- requires its own set of cloud resources
-- or both
-
-To implement a LightningWork, simply subclass ``LightningWork`` and define the run method:
-
-.. code:: python
- :emphasize-lines: 6
-
- # app.py
- from time import sleep
- import lightning as L
-
-
- class LitWork(L.LightningWork):
- def run(self):
- for i in range(100000):
- sleep(2.0)
- print(f"{i} LightningWork: work that is long running or may never end (like a server)")
-
-A LightningWork must always be attached to a LightningFlow and explicitly asked to ``run()``:
-
-.. code:: python
- :emphasize-lines: 13, 16
-
- from time import sleep
- import lightning as L
-
-
- class LitWork(L.LightningWork):
- def run(self):
- for i in range(100000):
- sleep(2.0)
- print(f"{i} LightningWork: work that is long running or may never end (like a server)")
-
-
- class LitFlow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_work = LitWork()
-
- def run(self):
- self.lit_work.run()
-
-
- app = L.LightningApp(LitFlow())
-
-run the app
-
-.. code:: bash
-
- lightning_app run app app.py
diff --git a/docs/source-app/workflows/build_lightning_component/index.rst b/docs/source-app/workflows/build_lightning_component/index.rst
deleted file mode 100644
index 8620a9b9fd8d5..0000000000000
--- a/docs/source-app/workflows/build_lightning_component/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-:orphan:
-
-#############################
-Develop a Lightning Component
-#############################
-
-A Lightning App (App) is a collection of components interacting together. Learn how to build a Lightning Component (Component) in this section.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/build_lightning_component/index_content.rst b/docs/source-app/workflows/build_lightning_component/index_content.rst
deleted file mode 100644
index 9a940bc8b89b6..0000000000000
--- a/docs/source-app/workflows/build_lightning_component/index_content.rst
+++ /dev/null
@@ -1,122 +0,0 @@
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- basic
- ../add_components
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- intermediate
- ../run_work_in_parallel
- ../run_work_once
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- ../enable_fault_tolerance
-
-******
-Basics
-******
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a Lightning Component
- :description: Learn the basics of developing a Lightning Component
- :col_css: col-md-4
- :button_link: basic.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Explore community Lightning Components
- :description: Discover community-built Lightning Components
- :col_css: col-md-4
- :button_link: https://lightning.ai/components
- :height: 150
- :tag: basic
-
-.. raw:: html
-
-
-
-
-----
-
-************
-Intermediate
-************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add a UI to a component
- :description: Learn about all the possible ways of rendering a component.
- :col_css: col-md-4
- :button_link: intermediate.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Run LightningWork in parallel
- :description: Learn about running LightningWork in parallel.
- :col_css: col-md-4
- :button_link: ../run_work_in_parallel.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Run LightningWork once
- :description: Learn about running LightningWork multiple times or once.
- :col_css: col-md-4
- :button_link: ../run_work_once.html
- :height: 150
- :tag: intermediate
-
-.. displayitem::
- :header: Publish a Lightning component
- :description: Learn the basics of publishing a Lightning component.
- :col_css: col-md-4
- :button_link: publish_a_component.html
- :height: 150
- :tag: intermediate
-
-.. raw:: html
-
-
-
-
-
-----
-
-********
-Advanced
-********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Enable fault tolerance
- :description: Learn how to make a component fault tolerant.
- :col_css: col-md-4
- :button_link: ../enable_fault_tolerance.html
- :height: 150
- :tag: advanced
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_lightning_component/intermediate.rst b/docs/source-app/workflows/build_lightning_component/intermediate.rst
deleted file mode 100644
index b95ba36ca4c2d..0000000000000
--- a/docs/source-app/workflows/build_lightning_component/intermediate.rst
+++ /dev/null
@@ -1,71 +0,0 @@
-############################################
-Develop a Lightning Component (intermediate)
-############################################
-
-**Audience:** Users who want to connect a UI to a Lightning Component (Component).
-
-----
-
-*****************************
-Add a web user interface (UI)
-*****************************
-Every lightning component can have its own user interface (UI). Lightning components support any kind
-of UI interface such as dash, gradio, panel, react.js, streamlit, vue.js, web urls,
-etc...(:doc:`full list here <../add_web_ui/index>`).
-
-Let's say that we have a user interface defined in html:
-
-.. code:: html
-
-
-
-
-
-
- Hello World
-
-
-
-To *connect* this user interface to the Component, define the configure_layout method:
-
-.. code:: python
- :emphasize-lines: 5, 6
-
- import lightning as L
-
-
- class LitHTMLComponent(L.LightningFlow):
- def configure_layout(self):
- return L.app.frontend.StaticWebFrontend(serve_dir="path/to/folder/with/index.html/inside")
-
-Finally, route the Component's UI through the root Component's **configure_layout** method:
-
-.. code:: python
- :emphasize-lines: 14
-
- # app.py
- import lightning as L
-
-
- class LitHTMLComponent(L.LightningFlow):
- def configure_layout(self):
- return L.app.frontend.StaticWebFrontend(serve_dir="path/to/folder/with/index.html/inside")
-
-
- class LitApp(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_html_component = LitHTMLComponent()
-
- def configure_layout(self):
- tab1 = {"name": "home", "content": self.lit_html_component}
- return tab1
-
-
- app = L.LightningApp(LitApp())
-
-Run your App and you'll see the UI on the Lightning App view:
-
-.. code:: bash
-
- lightning run app app.py
diff --git a/docs/source-app/workflows/build_lightning_component/publish_a_component.rst b/docs/source-app/workflows/build_lightning_component/publish_a_component.rst
deleted file mode 100644
index bb5ec755ae190..0000000000000
--- a/docs/source-app/workflows/build_lightning_component/publish_a_component.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-#############################
-Publish a Lightning Component
-#############################
-
-**Audience:** Users who want to build a Ligthtning Component (Component) to publish to the Lightning Gallery
-
-----
-
-***********************************
-Develop a Component from a template
-***********************************
-
-The fastest way to build a Component that is ready to be published to the component Gallery is to use
-the default template.
-
-Generate your Component template with this command:
-
-.. code:: python
-
- lightning init component your-component-name
-
-----
-
-*****************
-Run the Component
-*****************
-
-To test that your Component works, first install all dependencies:
-
-.. code:: bash
-
- cd your-component
- pip install -r requirements.txt
- pip install -e .
-
-Now import your Component and use it in a Lightning App:
-
-.. code:: python
-
- # app.py
- from your_component import TemplateComponent
- import lightning as L
-
- class LitApp(L.LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.your_component = TemplateComponent()
-
- def run(self):
- print('this is a simple Lightning app to verify your component is working as expected')
- self.your_component.run()
-
- app = L.LightningApp(LitApp())
-
-and run the app:
-
-.. code:: bash
-
- lightning run app app.py
diff --git a/docs/source-app/workflows/build_rest_api/add_api.rst b/docs/source-app/workflows/build_rest_api/add_api.rst
deleted file mode 100644
index 00fd16d18715a..0000000000000
--- a/docs/source-app/workflows/build_rest_api/add_api.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-:orphan:
-
-############################
-Add an API Route to your App
-############################
-
-In order to add a new route, you need to override the :class:`~lightning.app.core.flow.LightningFlow.configure_api` hook and return a list of :class:`~lightning.app.api.http_methods.HttpMethod` such as :class:`~lightning.app.api.http_methods.Get`, :class:`~lightning.app.api.http_methods.Post`, :class:`~lightning.app.api.http_methods.Put`, :class:`~lightning.app.api.http_methods.Delete`.
-
-----
-
-**********************
-1. Create a simple App
-**********************
-
-We're going to create a single route ``/name`` that takes a string input ``name`` and stores the value within the ``names`` attribute of the flow state.
-
-Create a file called ``app.py`` and copy-paste the following code in to the file:
-
-.. literalinclude:: post_example.py
-
-----
-
-**************
-2. Run the App
-**************
-
-Execute the following command in a terminal:
-
-.. code-block::
-
- lightning_app run app app.py
-
-The following appears:
-
-.. code-block::
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
-
-----
-
-****************
-3. Check the API
-****************
-
-The Lightning App framework automatically generates API documentation from your App using `Swagger UI `_.
-
-You can access it by accessing the following URL: ``http://127.0.0.1:7501/docs`` in your browser and validate your API with the route ``/name`` directly from the documentation page as shown below.
-
-.. video:: https://pl-public-data.s3.amazonaws.com/assets_lightning/rest_post.mp4
- :poster: https://pl-public-data.s3.amazonaws.com/assets_lightning/rest_png.png
- :width: 600
- :class: background-video
- :autoplay:
- :loop:
- :muted:
-
-Alternatively, you can invoke the route directly from a second terminal using `curl `_.
-
-.. code-block::
-
- curl -X 'POST' \
- 'http://127.0.0.1:7501/name?name=my_name' \
- -H 'accept: application/json' \
- -d ''
-
- "The name my_name was registered"
-
-And you can see the following in your first terminal running your App.
-
-.. code-block::
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- []
- ["my_name"]
-
-**************************************
-Develop a command line interface (CLI)
-**************************************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add Requests Validation
- :description: Learn how to use pydantic with your API.
- :col_css: col-md-6
- :button_link: request_validation.html
- :height: 150
-
-.. displayitem::
- :header: Develop a Command Line Interface (CLI)
- :description: Learn how to develop an CLI for your App.
- :col_css: col-md-6
- :button_link: ../build_command_line_interface/index.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_rest_api/index.rst b/docs/source-app/workflows/build_rest_api/index.rst
deleted file mode 100644
index 590f3d03d538d..0000000000000
--- a/docs/source-app/workflows/build_rest_api/index.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-:orphan:
-
-###########
-RESTful API
-###########
-
-**Audience:** Users looking to create an API in their App to allow users to activate functionalities from external sources.
-
-----
-
-**********************
-What is a RESTful API?
-**********************
-
-A RESTful API is a set of external URL routes exposed by a server that enables clients to trigger some functionalities, such as getting or putting some data, uploading files, etc..
-
-This provides great flexibility for users as they can easily discover functionalities made available by the App Builders.
-
-The Lightning App framework supports the four primary HTTP methods: `GET`, `POST`, `PUT`, `DELETE`.
-
-These methods are guidelines to organize your RESTful Services and help users understand your functionalities.
-
-* **`GET`:** Reads data from the server.
-* **`POST`:** Creates new resources.
-* **`PUT`:** Updates/replaces existing resources.
-* **`DELETE`:** Deletes resources.
-
-Learn more about `HTTP Methods for RESTful Services here `_.
-
-The Lightning App framework uses the popular `FastAPI `_ and `Pydantic `_ frameworks under the hood. This means you can use all their features while building your App.
-
-----
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/build_rest_api/index_content.rst b/docs/source-app/workflows/build_rest_api/index_content.rst
deleted file mode 100644
index 9f77225f24f59..0000000000000
--- a/docs/source-app/workflows/build_rest_api/index_content.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-**************
-Develop an API
-**************
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add an API Route to your App
- :description: Learn how to develop a simple API for your App.
- :col_css: col-md-6
- :button_link: add_api.html
- :height: 150
-
-.. displayitem::
- :header: Add Requests Validation
- :description: Learn how to use pydantic with your API.
- :col_css: col-md-6
- :button_link: cli_client.html
- :height: 150
-
-.. raw:: html
-
-
-
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Develop a Command-line Interface
- :description: Learn how to develop an CLI for your App.
- :col_css: col-md-6
- :button_link: ../build_command_line_interface/index.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/build_rest_api/models.py b/docs/source-app/workflows/build_rest_api/models.py
deleted file mode 100644
index 7ebb3ac8c8c17..0000000000000
--- a/docs/source-app/workflows/build_rest_api/models.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from pydantic import BaseModel
-
-
-# 1. Subclass the BaseModel and defines your payload format.
-class NamePostConfig(BaseModel):
- name: str
diff --git a/docs/source-app/workflows/build_rest_api/post_example.py b/docs/source-app/workflows/build_rest_api/post_example.py
deleted file mode 100644
index 0e56117a44fcf..0000000000000
--- a/docs/source-app/workflows/build_rest_api/post_example.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from lightning.app import LightningFlow, LightningApp
-from lightning.app.api import Post
-
-
-class Flow(LightningFlow):
- # 1. Define the state
- def __init__(self):
- super().__init__()
- self.names = []
-
- # 2. Optional, but used to validate names
- def run(self):
- print(self.names)
-
- # 3. Method executed when a request is received.
- def handle_post(self, name: str):
- self.names.append(name)
- return f'The name {name} was registered'
-
- # 4. Defines this Component's Restful API. You can have several routes.
- def configure_api(self):
- return [Post(route="/name", method=self.handle_post)]
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/workflows/build_rest_api/post_example_pydantic.py b/docs/source-app/workflows/build_rest_api/post_example_pydantic.py
deleted file mode 100644
index 35ae78186f538..0000000000000
--- a/docs/source-app/workflows/build_rest_api/post_example_pydantic.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from models import NamePostConfig # 2. Import your custom model.
-
-from lightning.app import LightningFlow, LightningApp
-from lightning.app.api import Post
-
-
-class Flow(LightningFlow):
- # 1. Define the state
- def __init__(self):
- super().__init__()
- self.names = []
-
- # 2. Optional, but used to validate names
- def run(self):
- print(self.names)
-
- # 3. Annotate your input with your custom pydantic model.
- def handle_post(self, config: NamePostConfig):
- self.names.append(config.name)
- return f'The name {config} was registered'
-
- # 4. Defines this Component's Restful API. You can have several routes.
- def configure_api(self):
- return [
- Post(
- route="/name",
- method=self.handle_post,
- )
- ]
-
-
-app = LightningApp(Flow())
diff --git a/docs/source-app/workflows/build_rest_api/request_validation.rst b/docs/source-app/workflows/build_rest_api/request_validation.rst
deleted file mode 100644
index 6caaccdc239d2..0000000000000
--- a/docs/source-app/workflows/build_rest_api/request_validation.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-:orphan:
-
-***********************
-Add Requests Validation
-***********************
-
-The Lightning App framework uses the popular `FastAPI `_ and `Pydantic `_ frameworks under the hood. This means you can use all their features while building your App.
-
-pydantic enables fast data validation and settings management using Python type annotations and FastAPI is a modern, fast (high-performance), web framework for building APIs.
-
-You can easily use pydantic by defining your own payload format.
-
-.. literalinclude:: models.py
-
-Then, type your handler input with your custom model.
-
-.. literalinclude:: post_example_pydantic.py
-
-After running the updated App, the App documentation ``/name`` has changed and takes JSON with ``{"name": ...}`` as input.
-
-.. figure:: https://pl-public-data.s3.amazonaws.com/assets_lightning/rest_post_pydantic.png
- :alt: Rest API with pydantic
- :width: 100 %
-
-You can invoke the RESTful API route ``/name`` with the following command:
-
-.. code-block:: bash
-
- curl -X 'POST' \
- 'http://127.0.0.1:7501/name' \
- -H 'accept: application/json' \
- -H 'Content-Type: application/json' \
- -d '{
- "name": "my_name"
- }'
-
-.. note::
-
- Using curl, you can pass a JSON payload using the ``-d`` argument.
-
-----
-
-**********
-Learn more
-**********
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Add an API Route to your App
- :description: Learn how to develop a simple API for your App.
- :col_css: col-md-6
- :button_link: add_api.html
- :height: 150
-
-.. displayitem::
- :header: Develop a Command Line Interface (CLI)
- :description: Learn how to develop an CLI for your App.
- :col_css: col-md-6
- :button_link: ../build_command_line_interface/index.html
- :height: 150
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/debug_locally.rst b/docs/source-app/workflows/debug_locally.rst
deleted file mode 100644
index cd5a5a80fde7c..0000000000000
--- a/docs/source-app/workflows/debug_locally.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-#####################################
-Debug a Distributed Cloud App Locally
-#####################################
diff --git a/docs/source-app/workflows/enable_fault_tolerance.rst b/docs/source-app/workflows/enable_fault_tolerance.rst
deleted file mode 100644
index b1630d4d396ac..0000000000000
--- a/docs/source-app/workflows/enable_fault_tolerance.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-######################
-Enable Fault Tolerance
-######################
diff --git a/docs/source-app/workflows/extend_app.rst b/docs/source-app/workflows/extend_app.rst
deleted file mode 100644
index bc7ffd7d2f87a..0000000000000
--- a/docs/source-app/workflows/extend_app.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-######################
-Extend an Existing App
-######################
-You can extend a Lightning App by using community components or building your own.
-
-----
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Add more Components
- :description: Extend an App by adding a prebuilt component.
- :col_css: col-md-4
- :button_link: add_components.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Add a web user interface (UI)
- :description: Extend an App by adding a web user interface (UI)
- :col_css: col-md-4
- :button_link: add_web_ui/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Add a URL link
- :description: Extend an App by adding a web URL link
- :col_css: col-md-4
- :button_link: add_web_link.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Build a Component
- :description: Extend an App by building a Component
- :col_css: col-md-4
- :button_link: build_lightning_component/index.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Add a server
- :description: Extend an App by adding a server to a Component.
- :col_css: col-md-4
- :button_link: add_server/index.html
- :height: 150
- :tag: Intermediate
-
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/index.rst b/docs/source-app/workflows/index.rst
deleted file mode 100644
index 9993971873286..0000000000000
--- a/docs/source-app/workflows/index.rst
+++ /dev/null
@@ -1,186 +0,0 @@
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- access_app_state
- add_web_ui/index
- add_web_link
- secrets <../glossary/secrets>
- arrange_tabs/index
- connect components <../levels/intermediate/connect_lightning_components>
- build components <../levels/basic/build_a_lightning_component>
- run_work_once
- cloud compute <../core_api/lightning_work/compute>
- build_command_line_interface/index
- rest API <../glossary/restful_api/restful_api>
- extend_app
- build_lightning_component/publish_a_component
- add_server/index
- run_app_on_cloud/index
- run_work_in_parallel
- drive <../glossary/storage/drive>
- share_app
- share_files_between_components
-
-#######
-How to:
-#######
-
-.. raw:: html
-
-
-
-
-.. displayitem::
- :header: Access the App State
- :description: Learn to work with the app state
- :col_css: col-md-4
- :button_link: access_app_state.html
- :height: 180
-
-.. displayitem::
- :header: Add a web user interface
- :description: Learn how to add React, StreamLit, Dash to your App.
- :col_css: col-md-4
- :button_link: add_web_ui/index.html
- :height: 180
-
-.. displayitem::
- :header: Add a web link
- :description: Learn how to embed external websites
- :col_css: col-md-4
- :button_link: add_web_link.html
- :height: 180
-
-.. displayitem::
- :header: Add encrypted secrets
- :description: Learn how to organize your UI
- :col_css: col-md-4
- :button_link: ../glossary/secrets.html
- :height: 180
-
-.. displayitem::
- :header: Arrange App tabs
- :description: Learn how to organize your UI
- :col_css: col-md-4
- :button_link: arrange_tabs/index.html
- :height: 180
-
-.. displayitem::
- :header: Build a Lightning App
- :description: Simple App to get started
- :col_css: col-md-4
- :button_link: ../levels/basic/connect_lightning_components.html
- :height: 180
-
-.. displayitem::
- :header: Build a Lightning Component
- :description: Understand how to separated the glue from the actual work
- :col_css: col-md-4
- :button_link: ../levels/basic/build_a_lightning_component.html
- :height: 180
-
-.. displayitem::
- :header: Cache Work run calls
- :description: Understand how to trigger a work run method
- :col_css: col-md-4
- :button_link: run_work_once.html
- :height: 180
-
-.. displayitem::
- :header: Customize your cloud compute
- :description: Select machines to run on
- :col_css: col-md-4
- :button_link: ../core_api/lightning_work/compute.html
- :height: 180
-
-.. displayitem::
- :header: Develop a Command Line Interface (CLI)
- :description: Learn to develop a CLI
- :col_css: col-md-4
- :button_link: build_command_line_interface/index.html
- :height: 180
-
-.. displayitem::
- :header: Develop a Lightning App
- :description: Learn to connect components together into a Lightning App
- :col_css: col-md-4
- :button_link: ../levels/basic/connect_lightning_components.html
- :height: 180
-
-.. displayitem::
- :header: Develop a REST API
- :description: Learn to deploy a model behind a REST API
- :col_css: col-md-4
- :button_link: ../glossary/restful_api/restful_api.html
- :height: 180
-
-.. displayitem::
- :header: Extend an existing App
- :description: Learn where to go next with an App
- :col_css: col-md-4
- :button_link: extend_app.html
- :height: 180
-
-.. displayitem::
- :header: Publish a Lightning Component
- :description: Share your components with others
- :col_css: col-md-4
- :button_link: build_lightning_component/publish_a_component.html
- :height: 180
-
-.. displayitem::
- :header: Run a server within a Lightning App
- :description: Lightning Work can be infinite jobs
- :col_css: col-md-4
- :button_link: add_server/index.html
- :height: 180
-
-.. displayitem::
- :header: Run an App on the cloud
- :description: Learn how to get things done in the cloud with ease
- :col_css: col-md-4
- :button_link: run_app_on_cloud/index.html
- :height: 180
-
-.. displayitem::
- :header: Run Works in parallel
- :description: Learn how to make your Work non blocking
- :col_css: col-md-4
- :button_link: run_work_in_parallel.html
- :height: 180
-
-.. displayitem::
- :header: Save files
- :description: Learn how to save files in a work by using Drive
- :col_css: col-md-4
- :button_link: ../glossary/storage/drive.html
- :height: 180
-
-.. displayitem::
- :header: Share an App
- :description: Learn how to share your work with others
- :col_css: col-md-4
- :button_link: share_app.html
- :height: 180
-
-.. displayitem::
- :header: Share files between components
- :description: Learn how Lightning Storage emulates a single filesystem in a distributed setting
- :col_css: col-md-4
- :button_link: share_files_between_components.html
- :height: 180
-
-.. displayitem::
- :header: Mount Cloud Data
- :description: Learn how Lightning Mounts are used to make the contents of an cloud object store bucket available on disk when running in the cloud.
- :col_css: col-md-4
- :button_link: mount_cloud_object_store.html
- :height: 180
-
-
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/mount_cloud_object_store.rst b/docs/source-app/workflows/mount_cloud_object_store.rst
deleted file mode 100644
index 31ac2a44aa3c0..0000000000000
--- a/docs/source-app/workflows/mount_cloud_object_store.rst
+++ /dev/null
@@ -1,141 +0,0 @@
-:orphan:
-
-##############
-Add Cloud Data
-##############
-
-**Audience:** Users who want to read files stored in a Cloud Object Bucket in an app.
-
-******************************
-Mounting Public AWS S3 Buckets
-******************************
-
-===================
-Add Mount to a Work
-===================
-
-To mount data from a cloud bucket to your app compute, initialize a :class:`~lightning.app.storage.mount.Mount`
-object with the source path of the s3 bucket and the absolute directory path where it should be mounted and
-pass the :class:`~lightning.app.storage.mount.Mount` to the :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute`
-of the :class:`~lightning.app.core.work.LightningWork` it should be mounted on.
-
-In this example, we will mount an S3 bucket: ``s3://ryft-public-sample-data/esRedditJson/`` to ``/content/esRedditJson/``.
-
-.. code-block:: python
-
- from lightning.app import CloudCompute
- from lightning.app.storage import Mount
-
- self.my_work = MyWorkClass(
- cloud_compute=CloudCompute(
- mounts=Mount(
- source="s3://ryft-public-sample-data/esRedditJson/",
- mount_path="/content/esRedditJson/",
- ),
- )
- )
-
-You can also pass multiple mounts to a single work by passing a ``List[Mount(...), ...]`` to the
-``CloudCompute(mounts=...)`` argument.
-
-.. note::
-
- * Mounts supported up to 1 Million files, 5GB per file. Need larger mounts? Contact support@lightning.ai
- * When adding multiple mounts, each one should have a unique ``mount_path``.
- * A maximum of 10 :class:`~lightning.app.storage.mount.Mount`\s can be added to a :class:`~lightning.app.core.work.LightningWork`.
-
-=======================
-Read Files From a Mount
-=======================
-
-Once a :class:`~lightning.app.storage.mount.Mount` object is passed to :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute`,
-you can access, list, or read any file from the mount under the specified ``mount_path``, just like you would if it
-was on your local machine.
-
-Assuming your ``mount_path`` is ``"/content/esRedditJson/"`` you can do the following:
-
-----------
-Read Files
-----------
-
-.. code-block:: python
-
- with open("/content/esRedditJson/esRedditJson1", "r") as f:
- some_data = f.read()
-
- # do something with "some_data"...
-
-----------
-List Files
-----------
-
-.. code-block:: python
-
- files = os.listdir("/content/esRedditJson/")
-
---------------------
-See the Full Example
---------------------
-
-.. code-block:: python
- :emphasize-lines: 10,15
-
- import os
-
- import lightning as L
- from lightning.app import CloudCompute
- from lightning.app.storage import Mount
-
- class ReadMount(L.LightningWork):
- def run(self):
- # Print a list of files stored in the mounted S3 Bucket.
- files = os.listdir("/content/esRedditJson/")
- for file in files:
- print(file)
-
- # Read the contents of a particular file in the bucket "esRedditJson1"
- with open("/content/esRedditJson/esRedditJson1", "r") as f:
- some_data = f.read()
- # do something with "some_data"...
-
- class Flow(L.LightningFlow):
- def __init__(self):
- super().__init__()
- self.my_work = ReadMount(
- cloud_compute=CloudCompute(
- mounts=Mount(
- source="s3://ryft-public-sample-data/esRedditJson/",
- mount_path="/content/esRedditJson/",
- ),
- )
- )
-
- def run(self):
- self.my_work.run()
-
-.. note::
-
- When running a Lightning App on your local machine, any :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute`
- configuration (including a :class:`~lightning.app.storage.mount.Mount`) is ignored at runtime. If you need access to
- these files on your local disk, you should download a copy of them to your machine.
-
-.. note::
-
- Mounted files from an S3 bucket are ``read-only``. Any modifications, additions, or deletions
- to files in the mounted directory will not be reflected in the cloud object store.
-
-----
-
-**********************************************
-Mounting Private AWS S3 Buckets - Coming Soon!
-**********************************************
-
-We'll Let you know when this feature is ready!
-
-----
-
-************************************************
-Mounting Google Cloud GCS Buckets - Coming Soon!
-************************************************
-
-We'll Let you know when this feature is ready!
diff --git a/docs/source-app/workflows/run_app_on_cloud/cloud_files.rst b/docs/source-app/workflows/run_app_on_cloud/cloud_files.rst
deleted file mode 100644
index 8e8f01a00f3d7..0000000000000
--- a/docs/source-app/workflows/run_app_on_cloud/cloud_files.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-.. _ignore:
-
-##################################
-Configure Your Lightning Cloud App
-##################################
-
-**Audience:** Users who want to control Lightning App files on the cloud.
-
-----
-
-**************************************
-Ignore file uploads to Lightning cloud
-**************************************
-Running Lightning Apps on the cloud will upload the source code of your app to the cloud. You can use ``.lightningignore`` file(s) to ignore files or directories while uploading. The `.lightningignore` file follows the same format as a `.gitignore`
-file.
-
-For example, the source code directory below with the ``.lightningignore`` file will ignore the file named
-``model.pt`` and directory named ``data_dir``.
-
-.. code:: bash
-
- .
- ├── README.md
- ├── app.py
- ├── data_dir
- │ ├── image1.png
- │ ├── image2.png
- │ └── ...
- ├── .lightningignore
- ├── requirements.txt
- └── model.pt
-
-.. code:: bash
-
- ~/project/home ❯ cat .lightningignore
- model.pt
- data_dir
-
-A sample ``.lightningignore`` file can be found `here `_.
-
-If you are a component author and your components creates local files that you want to ignore, you can do:
-
-.. code-block:: python
-
- class MyComponent(L.LightningWork): # or L.LightningFlow
- def __init__(self):
- super().__init__()
- self.lightningignore = ("model.pt", "data_dir")
-
-
-This has the benefit that the files will be ignored automatically for all the component users, making an easier
-transition between running locally vs in the cloud.
-
-----
-
-*******************
-Structure app files
-*******************
-
-We recommend your app contain the following files:
-
-.. code:: bash
-
- .
- ├── .lightning (auto-generated- contains Lightning configuration)
- ├── .lightningignore (contains files not to upload to the cloud)
- ├── app.py
- ├── README.md (optional- a markdown description of your app)
- └── requirements.txt (optional- contains all your app dependencies)
diff --git a/docs/source-app/workflows/run_app_on_cloud/index.rst b/docs/source-app/workflows/run_app_on_cloud/index.rst
deleted file mode 100644
index 55bc3b6807809..0000000000000
--- a/docs/source-app/workflows/run_app_on_cloud/index.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-#####################
-Run apps on the cloud
-#####################
-
-.. include:: index_content.rst
diff --git a/docs/source-app/workflows/run_app_on_cloud/index_content.rst b/docs/source-app/workflows/run_app_on_cloud/index_content.rst
deleted file mode 100644
index 737b4365df4e2..0000000000000
--- a/docs/source-app/workflows/run_app_on_cloud/index_content.rst
+++ /dev/null
@@ -1,115 +0,0 @@
-.. _run_app_in_cloud:
-
-.. toctree::
- :maxdepth: 1
- :hidden:
-
- cloud_files
- lightning_cloud
- on_prem
- on_your_own_machine
-
-**Audience:** Users who want to share or scale Lightning Apps.
-
-----
-
-*****************************
-Run on Lightning Public Cloud
-*****************************
-
-You can run Lightning Apps for free on the Public Lightning cloud with a single flag!
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Run on Lightning Cloud
- :description: Learn how to run on the Lightning public cloud
- :col_css: col-md-4
- :button_link: lightning_cloud.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Choose Hardware
- :description: Configure you app cloud resources
- :col_css: col-md-4
- :button_link: ../../core_api/lightning_work/compute.html
- :height: 150
- :tag: Basic
-
-.. displayitem::
- :header: Set Environment Variables
- :description: Manage your environment variables in the cloud
- :col_css: col-md-4
- :button_link: ../../glossary/environment_variables.html
- :height: 150
- :tag: Basic
-
-.. displayitem::
- :header: Configure Your Lightning Cloud App
- :description: Customize your cloud apps files
- :col_css: col-md-4
- :button_link: cloud_files.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Manage App Dependencies
- :description: Configure your python requirements or use a custom docker image
- :col_css: col-md-4
- :button_link: ../../glossary/build_config/build_config.html
- :height: 150
- :tag: Intermediate
-
-.. displayitem::
- :header: Share Files Between Works
- :description: Learn more about data transferring
- :col_css: col-md-4
- :button_link: ../../glossary/storage/storage.html
- :height: 150
- :tag: Intermediate
-
-.. raw:: html
-
-
-
-
-----
-
-************
-Other Clouds
-************
-
-.. raw:: html
-
-
-
-
-.. Add callout items below this line
-
-.. displayitem::
- :header: Run On Your Own Machine
- :description: Run Lightning Apps on any machine
- :col_css: col-md-4
- :button_link: on_your_own_machine.html
- :height: 150
- :tag: basic
-
-.. displayitem::
- :header: Run On Your Private Cloud
- :description: Run Lightning Apps on your own cloud
- :col_css: col-md-4
- :button_link: on_prem.html
- :height: 150
- :tag: basic
-
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/run_app_on_cloud/lightning_cloud.rst b/docs/source-app/workflows/run_app_on_cloud/lightning_cloud.rst
deleted file mode 100644
index 494a523852bd9..0000000000000
--- a/docs/source-app/workflows/run_app_on_cloud/lightning_cloud.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-#######################
-Run an App on the Cloud
-#######################
-
-**Audience:** Users who want to share their apps or run on specialized hardware (like GPUs).
-
-----
-
-*********************************
-Run on the public Lightning cloud
-*********************************
-To run any app on the public lightning cloud use the ``--cloud`` argument:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud
-
-
-.. note::
- By default, running your apps on the public Lightning cloud is free of charge using default CPUs, and any app uploaded to the Lightning cloud will be shared with the community (source code and app view will be public). If you would like to make your apps private please `contact us `_.
-
-If your app contains ``LightningWork`` components that require more compute resources, such as larger CPUs or **GPUs**, you'll need to add credits to your Lightning AI account.
-
-
-----
-
-**************************
-Add dependencies to my app
-**************************
-
-
-Add all dependencies required to run your app to a `requirements.txt` file in your app's directory. Read :ref:`build_config` for more details.
-
-
-
-----
-
-
-********
-Name app
-********
-
-Simply use the ``--name`` flag when running your app, for example:
-
-.. code:: bash
-
- lightning_app run app app.py --cloud --name my-awesome-app
-
-Alternatively, you can change the name of the app in the ``.lightning`` file:
-
-.. code:: bash
-
- ~/project/home ❯ cat .lightning
- name: my-awesome-app
-
-The ``.lightning`` file is a general configuration file.
-To learn more about optional configuration file parameters, see :class:`~lightning.app.utilities.packaging.app_config.AppConfig`.
-
-------
-
-********************
-Choose Cloud Compute
-********************
-
-You can configure the hardware your app is running on by setting a :class:`~lightning.app.utilities.packaging.cloud_compute.CloudCompute` object onto the ``cloud_compute`` property of your work's.
-
-Learn more with the :ref:`cloud_compute` guide
diff --git a/docs/source-app/workflows/run_app_on_cloud/on_prem.rst b/docs/source-app/workflows/run_app_on_cloud/on_prem.rst
deleted file mode 100644
index be0a954f29b16..0000000000000
--- a/docs/source-app/workflows/run_app_on_cloud/on_prem.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-###########################
-Run an App on Private Cloud
-###########################
-
-
-To run Lightning apps on a private or on-prem cluster, `contact us `_.
diff --git a/docs/source-app/workflows/run_app_on_cloud/on_your_own_machine.rst b/docs/source-app/workflows/run_app_on_cloud/on_your_own_machine.rst
deleted file mode 100644
index 8226a1a00469b..0000000000000
--- a/docs/source-app/workflows/run_app_on_cloud/on_your_own_machine.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-#######################
-Run on your own machine
-#######################
-
-**Audience:** Users who want to run Lightning App on a remote machine.
-
-----
-
-***********
-Run via ssh
-***********
-To run a Lightning App on any machine, simply ssh to the machine and run the app directly
-
-.. code:: bash
-
- # Copy over credentials from your local machine to your cloud machine
- scp ~/.lightning/credentials.json your_name@your_cloud_machine:~/.lightning
-
- # log into your cloud machine
- ssh your_name@your_cloud_machine
-
- # get your code on the machine and install deps
- ...
-
- # start the app
- lightning run app app.py
diff --git a/docs/source-app/workflows/run_app_snippet.rst b/docs/source-app/workflows/run_app_snippet.rst
deleted file mode 100644
index 6283cb88104eb..0000000000000
--- a/docs/source-app/workflows/run_app_snippet.rst
+++ /dev/null
@@ -1,33 +0,0 @@
-:orphan:
-
-***********
-Run the app
-***********
-
-.. raw:: html
-
-
-
-
-Run the app with the ``run`` command
-
-.. code:: bash
-
- lightning_app run app app.py
-
-.. raw:: html
-
-
-
-
-
-Add the ``--cloud`` argument to run on the `lightning cloud `_.
-
-.. code:: bash
-
- lightning_app run app app.py --cloud
-
-.. raw:: html
-
-
-
diff --git a/docs/source-app/workflows/run_components_on_different_hardware.rst b/docs/source-app/workflows/run_components_on_different_hardware.rst
deleted file mode 100644
index 9685c3461e511..0000000000000
--- a/docs/source-app/workflows/run_components_on_different_hardware.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-####################################
-Run components on different hardware
-####################################
diff --git a/docs/source-app/workflows/run_on_private_cloud.rst b/docs/source-app/workflows/run_on_private_cloud.rst
deleted file mode 100644
index 84d64e9060bde..0000000000000
--- a/docs/source-app/workflows/run_on_private_cloud.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-:orphan:
-
-######################
-Run on a private cloud
-######################
-**Audience:** Users looking to run Lightning apps on their private cloud accounts.
-
-----
-
-******************************
-Run on a private cloud account
-******************************
-For enterprise, startups and University use-cases, Lightning AI can run on your own AWS account (with your own credentials), with all the infrastructure fully managed by us.
-To enable this, contact our support team to get started:
-
-onprem@lightning.ai
-
-----
-
-
-***********
-Run on-prem
-***********
-For enterprise-level security with full control of the Lightning AI system on your own on-prem cluster, contact our support team to get started:
-
-onprem@lightning.ai
diff --git a/docs/source-app/workflows/run_work_in_parallel.rst b/docs/source-app/workflows/run_work_in_parallel.rst
deleted file mode 100644
index b87e653afc920..0000000000000
--- a/docs/source-app/workflows/run_work_in_parallel.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-#############################
-Run LightningWork in parallel
-#############################
-**Audience:** Users who want to run a LightningWork in parallel (asynchronously).
-
-**Prereqs:** You must have finished the :doc:`Basic levels <../levels/basic/index>`.
-
-----
-
-.. include:: run_work_in_parallel_content.rst
diff --git a/docs/source-app/workflows/run_work_in_parallel_content.rst b/docs/source-app/workflows/run_work_in_parallel_content.rst
deleted file mode 100644
index 1c8d5b374dbb2..0000000000000
--- a/docs/source-app/workflows/run_work_in_parallel_content.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-************************************
-When to run a Components in parallel
-************************************
-Run LightningWork in parallel when you want to execute work in the background or at the same time as another work.
-An example of when this comes up in machine learning is when data streams-in while a model trains.
-
-----
-
-************
-Toy example
-************
-By default, a Component must complete before the next one runs. We can enable one
-component to start in parallel which allows the code to proceed without having
-to wait for the first one to finish.
-
-.. lit_tabs::
- :descriptions: No parallel components; Allow the train component to run in parallel; When the component runs, it will run in parallel; The next component is unblocked and can now immediately run.
- :code_files: /workflows/scripts/parallel/toy_app.py; /workflows/scripts/parallel/toy_parallel.py; /workflows/scripts/parallel/toy_parallel.py; /workflows/scripts/parallel/toy_parallel.py;
- :highlights: ; 18; 23; 24;
- :enable_run: true
- :tab_rows: 3
- :height: 540px
-
-----
-
-*******************************
-Multiple components in parallel
-*******************************
-In this example, we start all 3 components at once. The first two start in parallel, which
-allows the third component to run without waiting for the others to finish.
-
-.. lit_tabs::
- :descriptions: No parallel components; Enable 2 components to run in parallel; Start both components together in parallel; Last component is not blocked and can start immediately.
- :code_files: /workflows/scripts/parallel/toy_two_parallel_not_started.py; /workflows/scripts/parallel/toy_two_parallel.py; /workflows/scripts/parallel/toy_two_parallel.py; /workflows/scripts/parallel/toy_two_parallel.py
- :highlights: ; 18, 19; 23, 24; 25
- :enable_run: true
- :tab_rows: 3
- :height: 540px
diff --git a/docs/source-app/workflows/run_work_once.rst b/docs/source-app/workflows/run_work_once.rst
deleted file mode 100644
index 8bdd576a2bc76..0000000000000
--- a/docs/source-app/workflows/run_work_once.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-##########################
-Cache LightningWork Runs
-##########################
-
-**Audience:** Users who want to know how ``LightningWork`` works.
-
-**Level:** Advanced
-
-**Prereqs**: Level 16+ and read the :doc:`Event Loop guide <../glossary/event_loop>`.
-
-----
-
-.. include:: run_work_once_content.rst
diff --git a/docs/source-app/workflows/run_work_once_content.rst b/docs/source-app/workflows/run_work_once_content.rst
deleted file mode 100644
index 355c2ed2bac43..0000000000000
--- a/docs/source-app/workflows/run_work_once_content.rst
+++ /dev/null
@@ -1,151 +0,0 @@
-
-********************************************************
-What caching the calls of Work's run method does for you
-********************************************************
-
-By default, the run method in a LightningWork (Work) "remembers" (caches) the input arguments it is getting called with and does not execute again if called with the same arguments again.
-In other words, the run method only executes when the input arguments have never been seen before.
-
-You can turn caching on or off:
-
-.. code-block:: python
-
- # Run only when the input arguments change (default)
- work = MyWork(cache_calls=True)
-
- # Run every time regardless of whether input arguments change or not
- work = MyWork(cache_calls=False)
-
-To better understand this, imagine that every day you want to sequentially download and process some data and then train a model on that data.
-As explained in the :doc:`Event Loop guide <../../glossary/event_loop>`, the Lightning App runs within an infinite while loop, so the pseudo-code of your application might looks like this:
-
-.. code-block:: python
-
- from datetime import datetime
-
- # Lightning code
- while True: # This is the Lightning Event Loop
-
- # Your code
- today = datetime.now().strftime("%D") # '05/25/22'
- data_processor.run(today)
- train_model.run(data_processor.data)
-
-In this scenario, you want your components to run ``once`` a day, and no more than that! But your code is running within an infinite loop, how can this even work?
-This is where the Work's internal caching mechanism comes in. By default, Lightning caches a hash of the input provided to its run method and won't re-execute the method if the same input is provided again.
-In the example above, the **data_processor** component run method receives the string **"05/25/22"**. It runs one time and any further execution during the day is skipped until tomorrow is reached and the work run method receives **06/25/22**. This logic applies everyday.
-This caching mechanism is inspired from how `React.js Components and Props `_ renders websites. Only changes to the inputs re-trigger execution.
-
-***************
-Caching Example
-***************
-
-Here's an example of this behavior with LightningWork:
-
-.. code:: python
- :emphasize-lines: 11, 17
-
- import lightning as L
-
-
- class ExampleWork(L.LightningWork):
- def run(self, *args, **kwargs):
- print(f"I received the following props: args: {args} kwargs: {kwargs}")
-
-
- work = ExampleWork()
- work.run(value=1)
-
- # Providing the same value. This won't run as already cached.
- work.run(value=1)
- work.run(value=1)
- work.run(value=1)
- work.run(value=1)
-
- # Changing the provided value. This isn't cached and will run again.
- work.run(value=10)
-
-And you should see the following by running the code above:
-
-.. code-block:: console
-
- $ python example.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- # After you have clicked `run` on the UI.
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 10}
-
-As you can see, the intermediate run didn't execute, as we would expected when ``cache_calls=True``.
-
-***********************************
-Implications of turning caching off
-***********************************
-
-By setting ``cache_calls=False``, Lightning won't cache the return value and re-execute the run method on every call.
-
-.. code:: python
- :emphasize-lines: 7
-
- from lightning.app import LightningWork
-
-
- class ExampleWork(LightningWork):
- def run(self, *args, **kwargs):
- print(f"I received the following props: args: {args} kwargs: {kwargs}")
-
-
- work = ExampleWork(cache_calls=False)
- work.run(value=1)
-
- # Providing the same value. This won't run as already cached.
- work.run(value=1)
- work.run(value=1)
- work.run(value=1)
- work.run(value=1)
-
- # Changing the provided value. This isn't cached and will run again.
- work.run(value=10)
-
-.. code-block:: console
-
- $ python example.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- # After you have clicked `run` on the UI.
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 1}
- I received the following props: args: () kwargs: {'value': 10}
-
-Be aware than when setting both ``cache_calls=False`` and ``parallel=False`` to a work, the code after the ``self.work.run()`` is unreachable
-as the work continuously execute in a blocking way.
-
-.. code-block:: python
- :emphasize-lines: 9-10
-
- from lightning.app import LightningApp, LightningFlow, LightningWork
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
-
- self.work = Work(cache_calls=False, parallel=False)
-
- def run(self):
- print("HERE BEFORE")
- self.work.run()
- print("HERE AFTER")
-
-
- app = LightningApp(Flow())
-
-.. code-block:: console
-
- $ lightning run app app.py
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- print("HERE BEFORE")
- print("HERE BEFORE")
- print("HERE BEFORE")
- ...
diff --git a/docs/source-app/workflows/schedule_apps.rst b/docs/source-app/workflows/schedule_apps.rst
deleted file mode 100644
index 7b596cd08b179..0000000000000
--- a/docs/source-app/workflows/schedule_apps.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-#################
-Schedule App Runs
-#################
diff --git a/docs/source-app/workflows/scripts/parallel/toy_app.py b/docs/source-app/workflows/scripts/parallel/toy_app.py
deleted file mode 100644
index 8cfcd61c5bd31..0000000000000
--- a/docs/source-app/workflows/scripts/parallel/toy_app.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class AnalyzeComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class LitWorkflow(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('cpu'))
-
-
- def run(self):
- self.train.run("machine A counting")
- self.analyze.run("machine B counting")
-
-
-app = LightningApp(LitWorkflow())
diff --git a/docs/source-app/workflows/scripts/parallel/toy_parallel.py b/docs/source-app/workflows/scripts/parallel/toy_parallel.py
deleted file mode 100644
index 6b4059caa4209..0000000000000
--- a/docs/source-app/workflows/scripts/parallel/toy_parallel.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class AnalyzeComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class LitWorkflow(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'), parallel=True)
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('cpu'))
-
-
- def run(self):
- self.train.run("machine A counting")
- self.analyze.run("machine B counting")
-
-
-app = LightningApp(LitWorkflow())
diff --git a/docs/source-app/workflows/scripts/parallel/toy_two_parallel.py b/docs/source-app/workflows/scripts/parallel/toy_two_parallel.py
deleted file mode 100644
index 57967137f0f25..0000000000000
--- a/docs/source-app/workflows/scripts/parallel/toy_two_parallel.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class AnalyzeComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class LitWorkflow(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'), parallel=True)
- self.baseline_1 = TrainComponent(cloud_compute=CloudCompute('cpu'), parallel=True)
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('cpu'))
-
- def run(self):
- self.train.run("machine A counting")
- self.baseline_1.run("machine C counting")
- self.analyze.run("machine B counting")
-
-app = LightningApp(LitWorkflow())
diff --git a/docs/source-app/workflows/scripts/parallel/toy_two_parallel_not_started.py b/docs/source-app/workflows/scripts/parallel/toy_two_parallel_not_started.py
deleted file mode 100644
index 7ded9d8a93935..0000000000000
--- a/docs/source-app/workflows/scripts/parallel/toy_two_parallel_not_started.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# app.py
-from lightning.app import LightningWork, LightningFlow, LightningApp, CloudCompute
-
-
-class TrainComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class AnalyzeComponent(LightningWork):
- def run(self, message):
- for i in range(100000000000):
- print(message, i)
-
-class LitWorkflow(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.train = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.baseline_1 = TrainComponent(cloud_compute=CloudCompute('cpu'))
- self.analyze = AnalyzeComponent(cloud_compute=CloudCompute('cpu'))
-
- def run(self):
- self.train.run("machine A counting")
- self.baseline_1.run("machine C counting")
- self.analyze.run("machine B counting")
-
-app = LightningApp(LitWorkflow())
diff --git a/docs/source-app/workflows/share_app.rst b/docs/source-app/workflows/share_app.rst
deleted file mode 100644
index 8d7482e21d2c3..0000000000000
--- a/docs/source-app/workflows/share_app.rst
+++ /dev/null
@@ -1,33 +0,0 @@
-############
-Share an App
-############
-**Audience:** Users who want to show off their work.
-
-----
-
-***********************************
-Option 1: Run on the cloud to share
-***********************************
-To share an app, simply run your app on the cloud:
-
-.. code:: bash
-
- lightning run app app.py --cloud
-
-Then share the link that's generated.
-
-----
-
-**********************************
-Option 2: Expose a tunnel to share
-**********************************
-If you'd like to share yourself, feel free to run the app in local mode
-and expose the URlapp.
-
-Run local:
-
-.. code:: bash
-
- lightning run app app.py
-
-And then, use one of the many guides to `expose a tunnel `_.
diff --git a/docs/source-app/workflows/share_files_between_components.rst b/docs/source-app/workflows/share_files_between_components.rst
deleted file mode 100644
index 5ccf2b9e2a441..0000000000000
--- a/docs/source-app/workflows/share_files_between_components.rst
+++ /dev/null
@@ -1,120 +0,0 @@
-:orphan:
-
-##############################
-Share Files Between Components
-##############################
-
-.. note:: The contents of this page is still in progress!
-
-**Audience:** Users who want to share files between components.
-
-----
-
-**********************************
-Why do I need distributed storage?
-**********************************
-In a Lightning App some components can be executed on their own hardware. Distributed storage
-enables a file saved by a component on one machine to be used by components in other machines (transparently).
-
-If you've asked the question "how do I use the checkpoint from this model to deploy this other thing", you've
-needed distributed storage.
-
-----
-
-************
-Write a file
-************
-To write a file, first create a reference to the file with the :class:`~lightning.app.storage.path.Path` class, then write to it:
-
-.. code:: python
-
- from lightning.app.storage import Path
-
- # file reference
- boring_file_reference = Path("boring_file.txt")
-
- # write to that file
- with open(self.boring_file_reference, "w") as f:
- f.write("yolo")
-
-
-----
-
-**********
-Use a file
-**********
-To use a file, pass the reference to the file:
-
-.. code:: python
-
- f = open(boring_file_reference, "r")
- print(f.read())
-
-----
-
-..
- ********************************
- Create a directory - coming soon
- ********************************
-
-
- ----
-
- ******************************
- Use a directory - coming soon
- ******************************
- TODO
-
- ----
-
-*********************************
-Example: Share a model checkpoint
-*********************************
-A common workflow in ML is to use a checkpoint created by another component.
-First, define a component that saves a checkpoint:
-
-.. literalinclude:: ./share_files_between_components/app.py
- :lines: -19
-
-Next, define a component that needs the checkpoints:
-
-.. literalinclude:: ./share_files_between_components/app.py
- :lines: 20-31
-
-Link both components via a parent component:
-
-.. literalinclude:: ./share_files_between_components/app.py
- :lines: 32-
-
-
-Run the app above with the following command:
-
-.. code-block:: bash
-
- lightning run app docs/source/workflows/share_files_between_components/app.py
-
-.. code-block:: console
-
- Your Lightning App is starting. This won't take long.
- INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
- Loaded checkpoint_1: tensor([0, 1, 2, 3, 4])
- Loaded checkpoint_2: tensor([0, 1, 2, 3, 4])
-
-
-For example, here we save a file on one component and use it in another component:
-
-.. code:: python
-
- from lightning.app.storage import Path
-
-
- class ComponentA(LightningWork):
- def __init__(self):
- super().__init__()
- self.boring_path = None
-
- def run(self):
- # This should be used as a REFERENCE to the file.
- self.boring_path = Path("boring_file.txt")
- with open(self.boring_path, "w") as f:
- f.write(FILE_CONTENT)
diff --git a/docs/source-app/workflows/share_files_between_components/app.py b/docs/source-app/workflows/share_files_between_components/app.py
deleted file mode 100644
index 7bf0686f65954..0000000000000
--- a/docs/source-app/workflows/share_files_between_components/app.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import os
-
-import torch
-
-from lightning.app import LightningWork, LightningFlow, LightningApp
-from lightning.app.storage.path import Path
-
-
-class ModelTraining(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.checkpoints_path = Path("./checkpoints")
-
- def run(self):
- # make fake checkpoints
- checkpoint_1 = torch.tensor([0, 1, 2, 3, 4])
- checkpoint_2 = torch.tensor([0, 1, 2, 3, 4])
- os.makedirs(self.checkpoints_path, exist_ok=True)
- checkpoint_path = str(self.checkpoints_path / "checkpoint_{}.ckpt")
- torch.save(checkpoint_1, str(checkpoint_path).format("1"))
- torch.save(checkpoint_2, str(checkpoint_path).format("2"))
-
-
-class ModelDeploy(LightningWork):
- def __init__(self, ckpt_path, *args, **kwargs):
- super().__init__()
- self.ckpt_path = ckpt_path
-
- def run(self):
- ckpts = os.listdir(self.ckpt_path)
- checkpoint_1 = torch.load(os.path.join(self.ckpt_path, ckpts[0]))
- checkpoint_2 = torch.load(os.path.join(self.ckpt_path, ckpts[1]))
- print(f"Loaded checkpoint_1: {checkpoint_1}")
- print(f"Loaded checkpoint_2: {checkpoint_2}")
-
-
-class LitApp(LightningFlow):
- def __init__(self):
- super().__init__()
- self.train = ModelTraining()
- self.deploy = ModelDeploy(ckpt_path=self.train.checkpoints_path)
-
- def run(self):
- self.train.run()
- self.deploy.run()
-
-
-app = LightningApp(LitApp())
diff --git a/docs/source-app/workflows/test_an_app.rst b/docs/source-app/workflows/test_an_app.rst
deleted file mode 100644
index c51ae3aa8f652..0000000000000
--- a/docs/source-app/workflows/test_an_app.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:orphan:
-
-###########
-Test an App
-###########
diff --git a/docs/source-pytorch/versioning.rst b/docs/source-pytorch/versioning.rst
index 96038e63cf807..ebae1f920a5a6 100644
--- a/docs/source-pytorch/versioning.rst
+++ b/docs/source-pytorch/versioning.rst
@@ -16,8 +16,6 @@ A Lightning release number is in the format of ``MAJOR.MINOR.PATCH``.
With every release, we publish a changelog where we list additions, removals, deprecations, changed functionality and fixes.
-The ``lightning.app`` package is an exception to this rule, as it may contain any change with or without deprecations in any of the releases.
-
API Stability
*************
diff --git a/examples/app/argparse/app.py b/examples/app/argparse/app.py
deleted file mode 100644
index 5fa8039908eb3..0000000000000
--- a/examples/app/argparse/app.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import argparse
-
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-
-
-class Work(LightningWork):
- def __init__(self, cloud_compute):
- super().__init__(cloud_compute=cloud_compute)
-
- def run(self):
- pass
-
-
-class Flow(LightningFlow):
- def __init__(self, cloud_compute):
- super().__init__()
- self.work = Work(cloud_compute)
-
- def run(self):
- assert self.work.cloud_compute.name == "gpu", self.work.cloud_compute.name
- self.stop()
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- parser.add_argument("--use_gpu", action="store_true", default=False, help="Whether to use GPU in the cloud")
- hparams = parser.parse_args()
- app = LightningApp(Flow(CloudCompute("gpu" if hparams.use_gpu else "cpu")))
diff --git a/examples/app/boring/.gitignore b/examples/app/boring/.gitignore
deleted file mode 100644
index 94018704d9f90..0000000000000
--- a/examples/app/boring/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-lightning_logs
-*.pt
-.storage/
-.shared/
-data
-*.ckpt
-redis-stable
-node_modules
-*.rdb
-boring_file.txt
diff --git a/examples/app/boring/app.py b/examples/app/boring/app.py
deleted file mode 100644
index 0dfaedfae0107..0000000000000
--- a/examples/app/boring/app.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import os
-
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-from lightning.app.components import TracerPythonScript
-from lightning.app.storage.path import Path
-
-FILE_CONTENT = """
-Hello there!
-This tab is currently an IFrame of the FastAPI Server running in `DestinationFileAndServeWork`.
-Also, the content of this file was created in `SourceFileWork` and then transferred to `DestinationFileAndServeWork`.
-Are you already 🤯 ? Stick with us, this is only the beginning. Lightning is 🚀.
-"""
-
-
-class SourceFileWork(LightningWork):
- def __init__(self, cloud_compute: CloudCompute = CloudCompute(), **kwargs):
- super().__init__(parallel=True, **kwargs, cloud_compute=cloud_compute)
- self.boring_path = None
-
- def run(self):
- # This should be used as a REFERENCE to the file.
- self.boring_path = "lit://boring_file.txt"
- with open(self.boring_path, "w", encoding="utf-8") as f:
- f.write(FILE_CONTENT)
-
-
-class DestinationFileAndServeWork(TracerPythonScript):
- def run(self, path: Path):
- assert path.exists()
- self.script_args += [f"--filepath={path}", f"--host={self.host}", f"--port={self.port}"]
- super().run()
-
-
-class BoringApp(LightningFlow):
- def __init__(self):
- super().__init__()
- self.source_work = SourceFileWork()
- self.dest_work = DestinationFileAndServeWork(
- script_path=os.path.join(os.path.dirname(__file__), "scripts/serve.py"),
- port=1111,
- parallel=False, # runs until killed.
- cloud_compute=CloudCompute(),
- raise_exception=True,
- )
-
- @property
- def ready(self) -> bool:
- return self.dest_work.is_running
-
- def run(self):
- self.source_work.run()
- if self.source_work.has_succeeded:
- # the flow passes the file from one work to another.
- self.dest_work.run(self.source_work.boring_path)
- self.stop("Boring App End")
-
- def configure_layout(self):
- return {"name": "Boring Tab", "content": self.dest_work.url + "/file"}
-
-
-app = LightningApp(BoringApp())
diff --git a/examples/app/boring/app_dynamic.py b/examples/app/boring/app_dynamic.py
deleted file mode 100644
index b08b8cf5ce10d..0000000000000
--- a/examples/app/boring/app_dynamic.py
+++ /dev/null
@@ -1,72 +0,0 @@
-import os
-
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-from lightning.app.components import TracerPythonScript
-from lightning.app.storage.path import Path
-from lightning.app.structures import Dict
-
-FILE_CONTENT = """
-Hello there!
-This tab is currently an IFrame of the FastAPI Server running in `DestinationFileAndServeWork`.
-Also, the content of this file was created in `SourceFileWork` and then transferred to `DestinationFileAndServeWork`.
-Are you already 🤯 ? Stick with us, this is only the beginning. Lightning is 🚀.
-"""
-
-
-class SourceFileWork(LightningWork):
- def __init__(self, cloud_compute: CloudCompute = CloudCompute(), **kwargs):
- super().__init__(parallel=True, **kwargs, cloud_compute=cloud_compute)
- self.boring_path = None
-
- def run(self):
- # This should be used as a REFERENCE to the file.
- self.boring_path = "lit://boring_file.txt"
- with open(self.boring_path, "w") as f:
- f.write(FILE_CONTENT)
-
-
-class DestinationFileAndServeWork(TracerPythonScript):
- def run(self, path: Path):
- assert path.exists()
- self.script_args += [f"--filepath={path}", f"--host={self.host}", f"--port={self.port}"]
- super().run()
-
-
-class BoringApp(LightningFlow):
- def __init__(self):
- super().__init__()
- self.dict = Dict()
-
- @property
- def ready(self) -> bool:
- if "dst_w" in self.dict:
- return self.dict["dst_w"].url != ""
- return False
-
- def run(self):
- # create dynamically the source_work at runtime
- if "src_w" not in self.dict:
- self.dict["src_w"] = SourceFileWork()
-
- self.dict["src_w"].run()
-
- if self.dict["src_w"].has_succeeded:
- # create dynamically the dst_w at runtime
- if "dst_w" not in self.dict:
- self.dict["dst_w"] = DestinationFileAndServeWork(
- script_path=os.path.join(os.path.dirname(__file__), "scripts/serve.py"),
- port=1111,
- parallel=False, # runs until killed.
- cloud_compute=CloudCompute(),
- raise_exception=True,
- )
-
- # the flow passes the file from one work to another.
- self.dict["dst_w"].run(self.dict["src_w"].boring_path)
- self.stop("Boring App End")
-
- def configure_layout(self):
- return {"name": "Boring Tab", "content": self.dict["dst_w"].url + "/file" if "dst_w" in self.dict else ""}
-
-
-app = LightningApp(BoringApp(), log_level="debug")
diff --git a/examples/app/boring/scripts/__init__.py b/examples/app/boring/scripts/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/examples/app/boring/scripts/serve.py b/examples/app/boring/scripts/serve.py
deleted file mode 100644
index dedd6013985ca..0000000000000
--- a/examples/app/boring/scripts/serve.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import argparse
-import os
-
-import uvicorn
-from fastapi import FastAPI
-from fastapi.requests import Request
-from fastapi.responses import HTMLResponse
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser("Server Parser")
- parser.add_argument("--filepath", type=str, help="Where to find the `filepath`")
- parser.add_argument("--host", type=str, default="0.0.0.0", help="Server host`")
- parser.add_argument("--port", type=int, default="8888", help="Server port`")
- hparams = parser.parse_args()
-
- fastapi_service = FastAPI()
-
- if not os.path.exists(str(hparams.filepath)):
- content = ["The file wasn't transferred"]
- else:
- with open(hparams.filepath) as fo:
- content = fo.readlines() # read the file received from SourceWork.
-
- @fastapi_service.get("/file")
- async def get_file_content(request: Request, response_class=HTMLResponse):
- lines = "\n".join(["" + line + "
" for line in content])
- return HTMLResponse(f"")
-
- uvicorn.run(app=fastapi_service, host=hparams.host, port=hparams.port)
diff --git a/examples/app/commands_and_api/.lightningignore b/examples/app/commands_and_api/.lightningignore
deleted file mode 100644
index f7275bbbd035b..0000000000000
--- a/examples/app/commands_and_api/.lightningignore
+++ /dev/null
@@ -1 +0,0 @@
-venv/
diff --git a/examples/app/commands_and_api/app.py b/examples/app/commands_and_api/app.py
deleted file mode 100644
index 3f59c117c4180..0000000000000
--- a/examples/app/commands_and_api/app.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from command import CustomCommand, CustomConfig
-from lightning import LightningFlow
-from lightning.app.api import Get, Post
-from lightning.app.core.app import LightningApp
-
-
-async def handler():
- print("Has been called")
- return "Hello World !"
-
-
-class ChildFlow(LightningFlow):
- def nested_command(self, name: str):
- """A nested command."""
- print(f"Hello {name}")
-
- def configure_commands(self):
- return [{"nested_command": self.nested_command}]
-
-
-class FlowCommands(LightningFlow):
- def __init__(self):
- super().__init__()
- self.names = []
- self.child_flow = ChildFlow()
-
- def run(self):
- if self.names:
- print(self.names)
-
- def command_without_client(self, name: str):
- """A command without a client."""
- self.names.append(name)
-
- def command_with_client(self, config: CustomConfig):
- self.names.append(config.name)
-
- def configure_commands(self):
- commands = [
- {"command_without_client": self.command_without_client},
- {"command_with_client": CustomCommand(self.command_with_client)},
- ]
- return commands + self.child_flow.configure_commands()
-
- def configure_api(self):
- return [
- Post("/user/command_without_client", self.command_without_client),
- Get("/pure_function", handler),
- ]
-
-
-app = LightningApp(FlowCommands(), log_level="debug")
diff --git a/examples/app/commands_and_api/command.py b/examples/app/commands_and_api/command.py
deleted file mode 100644
index e2dd26f684b03..0000000000000
--- a/examples/app/commands_and_api/command.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from argparse import ArgumentParser
-
-from lightning.app.utilities.commands import ClientCommand
-from pydantic import BaseModel
-
-
-class CustomConfig(BaseModel):
- name: str
-
-
-class CustomCommand(ClientCommand):
- description = "A command with a client."
-
- def run(self):
- parser = ArgumentParser()
- parser.add_argument("--name", type=str)
- args = parser.parse_args()
- self.invoke_handler(config=CustomConfig(name=args.name))
diff --git a/examples/app/components/python/__init__.py b/examples/app/components/python/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/examples/app/components/python/app.py b/examples/app/components/python/app.py
deleted file mode 100644
index 944cb7de2995d..0000000000000
--- a/examples/app/components/python/app.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import os
-from pathlib import Path
-
-from lightning.app import LightningApp, LightningFlow
-
-from examples.components.python.component_tracer import PLTracerPythonScript
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- script_path = Path(__file__).parent / "pl_script.py"
- self.tracer_python_script = PLTracerPythonScript(script_path)
-
- def run(self):
- assert os.getenv("GLOBAL_RANK", "0") == "0"
- if not self.tracer_python_script.has_started:
- self.tracer_python_script.run()
- if self.tracer_python_script.has_succeeded:
- self.stop("tracer script succeed")
- if self.tracer_python_script.has_failed:
- self.stop("tracer script failed")
-
-
-app = LightningApp(RootFlow())
diff --git a/examples/app/components/python/component_popen.py b/examples/app/components/python/component_popen.py
deleted file mode 100644
index bc70b9f47b16d..0000000000000
--- a/examples/app/components/python/component_popen.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pathlib import Path
-
-from lightning.app.components import PopenPythonScript
-
-if __name__ == "__main__":
- comp = PopenPythonScript(Path(__file__).parent / "pl_script.py")
- comp.run()
diff --git a/examples/app/components/python/component_tracer.py b/examples/app/components/python/component_tracer.py
deleted file mode 100644
index 3e2e96f38a7f3..0000000000000
--- a/examples/app/components/python/component_tracer.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from lightning.app.components import TracerPythonScript
-from lightning.app.storage.path import Path
-from lightning.app.utilities.tracer import Tracer
-from lightning.pytorch import Trainer
-
-
-class PLTracerPythonScript(TracerPythonScript):
- """This component can be used for ANY PyTorch Lightning script to track its progress and extract its best model
- path."""
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # Define the component state.
- self.global_step = None
- self.best_model_path = None
-
- def configure_tracer(self) -> Tracer:
- from lightning.pytorch.callbacks import Callback
-
- class MyInjectedCallback(Callback):
- def __init__(self, lightning_work):
- self.lightning_work = lightning_work
-
- def on_train_start(self, trainer, pl_module) -> None:
- print("This code doesn't belong to the script but was injected.")
- print("Even the Lightning Work is available and state transfer works !")
- print(self.lightning_work)
-
- def on_batch_train_end(self, trainer, *_) -> None:
- # On every batch end, collects some information.
- # This is communicated automatically to the rest of the app,
- # so you can track your training in real time in the Lightning App UI.
- self.lightning_work.global_step = trainer.global_step
- best_model_path = trainer.checkpoint_callback.best_model_path
- if best_model_path:
- self.lightning_work.best_model_path = Path(best_model_path)
-
- # This hook would be called every time
- # before a Trainer `__init__` method is called.
-
- def trainer_pre_fn(trainer, *args, **kwargs):
- kwargs["callbacks"] = kwargs.get("callbacks", []) + [MyInjectedCallback(self)]
- return {}, args, kwargs
-
- tracer = super().configure_tracer()
- tracer.add_traced(Trainer, "__init__", pre_fn=trainer_pre_fn)
- return tracer
-
-
-if __name__ == "__main__":
- comp = PLTracerPythonScript(Path(__file__).parent / "pl_script.py")
- res = comp.run()
diff --git a/examples/app/components/python/pl_script.py b/examples/app/components/python/pl_script.py
deleted file mode 100644
index 75538daf4bed2..0000000000000
--- a/examples/app/components/python/pl_script.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from lightning.pytorch import Trainer
-from lightning.pytorch.demos.boring_classes import BoringModel
-
-if __name__ == "__main__":
- model = BoringModel()
- trainer = Trainer(max_epochs=1, accelerator="cpu", devices=2, strategy="ddp")
- trainer.fit(model)
- trainer.validate(model)
- trainer.test(model)
- trainer.predict(model)
diff --git a/examples/app/components/serve/gradio/app.py b/examples/app/components/serve/gradio/app.py
deleted file mode 100644
index ec07e4ba99c06..0000000000000
--- a/examples/app/components/serve/gradio/app.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from functools import partial
-
-import gradio as gr
-import requests
-import torch
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.components import ServeGradio
-from PIL import Image
-
-
-# Credit to @akhaliq for his inspiring work.
-# Find his original code there: https://huggingface.co/spaces/akhaliq/AnimeGANv2/blob/main/app.py
-class AnimeGANv2UI(ServeGradio):
- inputs = gr.inputs.Image(type="pil")
- outputs = gr.outputs.Image(type="pil")
- elon = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Elon_Musk_Royal_Society_%28crop2%29.jpg/330px-Elon_Musk_Royal_Society_%28crop2%29.jpg"
- img = Image.open(requests.get(elon, stream=True).raw)
- img.save("elon.jpg")
- examples = [["elon.jpg"]]
-
- def __init__(self):
- super().__init__()
- self.ready = False
-
- def predict(self, img):
- return self.model(img=img)
-
- def build_model(self):
- repo = "AK391/animegan2-pytorch:main"
- model = torch.hub.load(repo, "generator", device="cpu")
- face2paint = torch.hub.load(repo, "face2paint", size=512, device="cpu")
- self.ready = True
- return partial(face2paint, model=model)
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.demo = AnimeGANv2UI()
-
- def run(self):
- self.demo.run()
-
- def configure_layout(self):
- tabs = []
- if self.demo.ready:
- tabs.append({"name": "Home", "content": self.demo})
- return tabs
-
-
-app = LightningApp(RootFlow())
diff --git a/examples/app/components/serve/gradio/beyonce.jpg b/examples/app/components/serve/gradio/beyonce.jpg
deleted file mode 100644
index 68b6084475b01..0000000000000
Binary files a/examples/app/components/serve/gradio/beyonce.jpg and /dev/null differ
diff --git a/examples/app/components/serve/gradio/requirements.txt b/examples/app/components/serve/gradio/requirements.txt
deleted file mode 100644
index 25aceddaba262..0000000000000
--- a/examples/app/components/serve/gradio/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-gradio
diff --git a/examples/app/dag/.gitignore b/examples/app/dag/.gitignore
deleted file mode 100644
index fcb9fa9fe4d29..0000000000000
--- a/examples/app/dag/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-df_data
-df_target
-X_train
-X_test
-y_train
-y_test
diff --git a/examples/app/dag/.lightningignore b/examples/app/dag/.lightningignore
deleted file mode 100644
index 78ae49041d0b6..0000000000000
--- a/examples/app/dag/.lightningignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*df_data*
-*df_target*
-*X_train*
-*X_test*
-*y_train*
-*y_test*
-*.shared*
-*.storage*
diff --git a/examples/app/dag/app.py b/examples/app/dag/app.py
deleted file mode 100644
index 7e94f64b62c5d..0000000000000
--- a/examples/app/dag/app.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import os
-from importlib import import_module
-
-import numpy as np
-import pandas as pd
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.components import TracerPythonScript
-from lightning.app.storage import Payload
-from lightning.app.structures import Dict, List
-from sklearn import datasets
-from sklearn.metrics import mean_squared_error
-
-
-def get_path(path):
- return os.path.join(os.path.dirname(__file__), path)
-
-
-class GetDataWork(LightningWork):
- """This component is responsible to download some data and store them with a PayLoad."""
-
- def __init__(self):
- super().__init__()
- self.df_data = None
- self.df_target = None
-
- def run(self):
- print("Starting data collection...")
- data = datasets.fetch_california_housing(data_home=get_path("data"))
- self.df_data = Payload(pd.DataFrame(data["data"], columns=data["feature_names"]))
- self.df_target = Payload(pd.DataFrame(data["target"], columns=["MedHouseVal"]))
- print("Finished data collection.")
-
-
-class ModelWork(LightningWork):
- """This component is receiving some data and train a sklearn model."""
-
- def __init__(self, model_path: str, parallel: bool):
- super().__init__(parallel=parallel)
- self.model_path, self.model_name = model_path.split(".")
- self.test_rmse = None
-
- def run(self, X_train: Payload, X_test: Payload, y_train: Payload, y_test: Payload):
- print(f"Starting training and evaluating {self.model_name}...")
- module = import_module(f"sklearn.{self.model_path}")
- model = getattr(module, self.model_name)()
- model.fit(X_train.value, y_train.value.ravel())
- y_test_prediction = model.predict(X_test.value)
- self.test_rmse = np.sqrt(mean_squared_error(y_test.value, y_test_prediction))
- print(f"Finished training and evaluating {self.model_name}.")
-
-
-class DAG(LightningFlow):
- """This component is a DAG."""
-
- def __init__(self, models_paths: list):
- super().__init__()
- # Step 1: Create a work to get the data.
- self.data_collector = GetDataWork()
-
- # Step 2: Create a tracer component. This is used to execute python script
- # and collect any outputs from its globals as Payloads.
- self.processing = TracerPythonScript(
- get_path("processing.py"),
- outputs=["X_train", "X_test", "y_train", "y_test"],
- )
-
- # Step 3: Create the work to train the models_paths in parallel.
- self.dict = Dict(**{
- model_path.split(".")[-1]: ModelWork(model_path, parallel=True) for model_path in models_paths
- })
-
- # Step 4: Some element to track components progress.
- self.has_completed = False
- self.metrics = {}
-
- def run(self):
- # Step 1 and 2: Download and process the data.
- self.data_collector.run()
- self.processing.run(
- df_data=self.data_collector.df_data,
- df_target=self.data_collector.df_target,
- )
-
- # Step 3: Launch n models training in parallel.
- for model, work in self.dict.items():
- work.run(
- X_train=self.processing.X_train,
- X_test=self.processing.X_test,
- y_train=self.processing.y_train,
- y_test=self.processing.y_test,
- )
- if work.test_rmse: # Use the state to control when to collect and stop.
- self.metrics[model] = work.test_rmse
- work.stop() # Stop the model work to reduce cost
-
- # Step 4: Print the score of each model when they are all finished.
- if len(self.metrics) == len(self.dict):
- print(self.metrics)
- self.has_completed = True
-
-
-class ScheduledDAG(LightningFlow):
- def __init__(self, dag_cls, **dag_kwargs):
- super().__init__()
- self.dags = List()
- self._dag_cls = dag_cls
- self.dag_kwargs = dag_kwargs
-
- def run(self):
- """Example of scheduling an infinite number of DAG runs continuously."""
- # Step 1: Every minute, create and launch a new DAG.
- if self.schedule("* * * * *"):
- print("Launching a new DAG")
- self.dags.append(self._dag_cls(**self.dag_kwargs))
-
- for dag in self.dags:
- if not dag.has_completed:
- dag.run()
-
-
-app = LightningApp(
- ScheduledDAG(
- DAG,
- models_paths=[
- "svm.SVR",
- "linear_model.LinearRegression",
- "tree.DecisionTreeRegressor",
- ],
- ),
-)
diff --git a/examples/app/dag/processing.py b/examples/app/dag/processing.py
deleted file mode 100644
index 245377fa8cbaa..0000000000000
--- a/examples/app/dag/processing.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import random
-
-from sklearn.model_selection import train_test_split
-from sklearn.preprocessing import MinMaxScaler
-
-print("Starting processing ...")
-scaler = MinMaxScaler()
-
-X_train, X_test, y_train, y_test = train_test_split(
- df_data.values, df_target.values, test_size=0.20, random_state=random.randint(0, 42)
-)
-X_train = scaler.fit_transform(X_train)
-X_test = scaler.transform(X_test)
-print("Finished processing.")
diff --git a/examples/app/dag/requirements.txt b/examples/app/dag/requirements.txt
deleted file mode 100644
index f669f518e7389..0000000000000
--- a/examples/app/dag/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-scikit-learn
-pandas
diff --git a/examples/app/display_name/.lightningignore b/examples/app/display_name/.lightningignore
deleted file mode 100644
index f7275bbbd035b..0000000000000
--- a/examples/app/display_name/.lightningignore
+++ /dev/null
@@ -1 +0,0 @@
-venv/
diff --git a/examples/app/display_name/app.py b/examples/app/display_name/app.py
deleted file mode 100644
index a3a36b8afb02c..0000000000000
--- a/examples/app/display_name/app.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from lightning.app import LightningApp, LightningFlow, LightningWork
-
-
-class Work(LightningWork):
- def __init__(self, start_with_flow=True):
- super().__init__(start_with_flow=start_with_flow)
-
- def run(self):
- pass
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = Work()
- self.w1 = Work(start_with_flow=False)
- self.w.display_name = "My Custom Name" # Not supported yet
- self.w1.display_name = "My Custom Name 1"
-
- def run(self):
- self.w.run()
- self.w1.run()
-
-
-app = LightningApp(Flow())
diff --git a/examples/app/drive/.gitignore b/examples/app/drive/.gitignore
deleted file mode 100644
index eaa5fa8755fc2..0000000000000
--- a/examples/app/drive/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-a.txt
diff --git a/examples/app/drive/app.py b/examples/app/drive/app.py
deleted file mode 100644
index e56636ce13887..0000000000000
--- a/examples/app/drive/app.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import os
-
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.storage import Drive
-
-
-class Work_1(LightningWork):
- def run(self, drive: Drive):
- # 1. Create a file.
- with open("a.txt", "w") as f:
- f.write("Hello World !")
-
- # 2. Put the file into the drive.
- drive.put("a.txt")
-
- # 3. Delete the locally.
- os.remove("a.txt")
-
-
-class Work_2(LightningWork):
- def __init__(self):
- super().__init__()
-
- def run(self, drive: Drive):
- print(drive.list(".")) # Prints ["a.txt"]
-
- print(os.path.exists("a.txt")) # Prints False
-
- drive.get("a.txt") # Transfer the file from this drive to the local filesystem.
-
- print(os.path.exists("a.txt")) # Prints True
-
- with open("a.txt") as f:
- print(f.readlines()[0]) # Prints Hello World !
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.drive_1 = Drive("lit://drive_1")
- self.work_1 = Work_1()
- self.work_2 = Work_2()
-
- def run(self):
- # Pass the drive to both works.
- self.work_1.run(self.drive_1)
- self.work_2.run(self.drive_1)
- self.stop("Application End!")
-
-
-app = LightningApp(Flow())
diff --git a/examples/app/hpo/README.md b/examples/app/hpo/README.md
deleted file mode 100644
index b9b648bcaf14f..0000000000000
--- a/examples/app/hpo/README.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# Build a Lightning Hyperparameter Optimization (HPO) App
-
-## A bit of background
-
-Traditionally, developing machine learning (ML) products requires choosing among a large space of
-hyperparameters while creating and training the ML models. Hyperparameter optimization
-(HPO) aims to find a well-performing hyperparameter configuration for a given ML model
-on a dataset at hand, including the ML model,
-its hyperparameters, and other data processing steps.
-
-HPOs free the human expert from a tedious and error-prone, manual hyperparameter tuning process.
-
-As an example, in the famous [scikit-learn](https://scikit-learn.org/stable/) library,
-hyperparameters are passed as arguments to the constructor of
-the estimator classes such as `C` kernel for
-[Support Vector Classifier](https://scikit-learn.org/stable/modules/classes.html?highlight=svm#module-sklearn.svm), etc.
-
-It is possible and recommended to search the hyperparameter space for the best validation score.
-
-An HPO search consists of:
-
-- an objective method
-- a defined parameter space
-- a method for searching or sampling candidates
-
-A naive method for sampling candidates is grid search, which exhaustively considers all
-hyperparameter combinations from a user-specified grid.
-
-Fortunately, HPO is an active area of research, and many methods have been developed to
-optimize the time required to get strong candidates.
-
-In the following tutorial, you will learn how to use Lightning together with [Optuna](https://optuna.org/).
-
-[Optuna](https://optuna.org/) is an open source HPO framework to automate hyperparameter search.
-Out-of-the-box, it provides efficient algorithms to search large spaces and prune unpromising trials for faster results.
-
-First, you will learn about the best practices on how to implement HPO without the Lightning Framework.
-Secondly, we will dive into a working HPO application with Lightning, and finally create a neat
-[HiPlot UI](https://facebookresearch.github.io/hiplot/_static/demo/demo_basic_usage.html?hip.filters=%5B%5D&hip.color_by=%22dropout%22&hip.PARALLEL_PLOT.order=%5B%22uid%22%2C%22dropout%22%2C%22lr%22%2C%22loss%22%2C%22optimizer%22%5D)
-for our application.
-
-## Getting started
-
-### Step 1: Download the data
-
-```bash
-python download_data.py
-```
-
-### Step 2: Run the HPO Lightning App without an UI
-
-```bash
-lightning run app app_wo_ui.py
-```
-
-### Step 3: Run the HPO Lightning App with HiPlot UI in Streamlit.
-
-```bash
-lightning run app app_wi_ui.py
-```
-
-## Learn More
-
-In the documentation, search for `Build a Sweep App`.
diff --git a/examples/app/hpo/app_wi_ui.py b/examples/app/hpo/app_wi_ui.py
deleted file mode 100644
index e6ef3f3a1b983..0000000000000
--- a/examples/app/hpo/app_wi_ui.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from pathlib import Path
-
-import optuna
-from hyperplot import HiPlotFlow
-from lightning.app import CloudCompute, LightningApp, LightningFlow
-from lightning.app.structures import Dict
-from objective import ObjectiveWork
-
-
-class RootHPOFlow(LightningFlow):
- def __init__(self, script_path, data_dir, total_trials, simultaneous_trials):
- super().__init__()
- self.script_path = script_path
- self.data_dir = data_dir
- self.total_trials = total_trials
- self.simultaneous_trials = simultaneous_trials
- self.num_trials = simultaneous_trials
- self._study = optuna.create_study()
- self.ws = Dict()
- self.hi_plot = HiPlotFlow()
-
- def run(self):
- if self.num_trials >= self.total_trials:
- self.stop()
-
- has_told_study = []
-
- for trial_idx in range(self.num_trials):
- work_name = f"objective_work_{trial_idx}"
- if work_name not in self.ws:
- objective_work = ObjectiveWork(
- script_path=self.script_path,
- data_dir=self.data_dir,
- cloud_compute=CloudCompute("cpu"),
- )
- self.ws[work_name] = objective_work
- if not self.ws[work_name].has_started:
- trial = self._study.ask(ObjectiveWork.distributions())
- self.ws[work_name].run(trial_id=trial._trial_id, **trial.params)
-
- if self.ws[work_name].metric and not self.ws[work_name].has_told_study:
- self.hi_plot.data.append({"x": -1 * self.ws[work_name].metric, **self.ws[work_name].params})
- self._study.tell(self.ws[work_name].trial_id, self.ws[work_name].metric)
- self.ws[work_name].has_told_study = True
-
- has_told_study.append(self.ws[work_name].has_told_study)
-
- if all(has_told_study):
- self.num_trials += self.simultaneous_trials
-
-
-if __name__ == "__main__":
- app = LightningApp(
- RootHPOFlow(
- script_path=str(Path(__file__).parent / "pl_script.py"),
- data_dir="data/hymenoptera_data_version_0",
- total_trials=6,
- simultaneous_trials=2,
- )
- )
diff --git a/examples/app/hpo/app_wo_ui.py b/examples/app/hpo/app_wo_ui.py
deleted file mode 100644
index e20318347a1f7..0000000000000
--- a/examples/app/hpo/app_wo_ui.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from pathlib import Path
-
-import optuna
-from lightning.app import CloudCompute, LightningApp, LightningFlow
-from lightning.app.structures import Dict
-from objective import ObjectiveWork
-
-
-class RootHPOFlow(LightningFlow):
- def __init__(self, script_path, data_dir, total_trials, simultaneous_trials):
- super().__init__()
- self.script_path = script_path
- self.data_dir = data_dir
- self.total_trials = total_trials
- self.simultaneous_trials = simultaneous_trials
- self.num_trials = simultaneous_trials
- self._study = optuna.create_study()
- self.ws = Dict()
-
- def run(self):
- if self.num_trials >= self.total_trials:
- self.stop()
-
- has_told_study = []
-
- for trial_idx in range(self.num_trials):
- work_name = f"objective_work_{trial_idx}"
- if work_name not in self.ws:
- objective_work = ObjectiveWork(
- script_path=self.script_path,
- data_dir=self.data_dir,
- cloud_compute=CloudCompute("cpu"),
- )
- self.ws[work_name] = objective_work
- if not self.ws[work_name].has_started:
- trial = self._study.ask(ObjectiveWork.distributions())
- self.ws[work_name].run(trial_id=trial._trial_id, **trial.params)
-
- if self.ws[work_name].metric and not self.ws[work_name].has_told_study:
- self._study.tell(self.ws[work_name].trial_id, self.ws[work_name].metric)
- self.ws[work_name].has_told_study = True
-
- has_told_study.append(self.ws[work_name].has_told_study)
-
- if all(has_told_study):
- self.num_trials += self.simultaneous_trials
-
-
-if __name__ == "__main__":
- app = LightningApp(
- RootHPOFlow(
- script_path=str(Path(__file__).parent / "pl_script.py"),
- data_dir="data/hymenoptera_data_version_0",
- total_trials=6,
- simultaneous_trials=2,
- )
- )
diff --git a/examples/app/hpo/download_data.py b/examples/app/hpo/download_data.py
deleted file mode 100644
index d82b86a9dee95..0000000000000
--- a/examples/app/hpo/download_data.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from utils import download_data
-
-data_dir = "hymenoptera_data_version_0"
-download_url = f"https://pl-flash-data.s3.amazonaws.com/{data_dir}.zip"
-download_data(download_url, "./data")
diff --git a/examples/app/hpo/hyperplot.py b/examples/app/hpo/hyperplot.py
deleted file mode 100644
index 8ff238ce38985..0000000000000
--- a/examples/app/hpo/hyperplot.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from lightning.app import LightningFlow
-from lightning.app.frontend import StreamlitFrontend
-from lightning.app.utilities.state import AppState
-
-
-class HiPlotFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.data = []
-
- def run(self):
- pass
-
- def configure_layout(self):
- return StreamlitFrontend(render_fn=render_fn)
-
-
-def render_fn(state: AppState):
- import json
-
- import hiplot as hip
- import streamlit as st
- from streamlit_autorefresh import st_autorefresh
-
- st.set_page_config(layout="wide")
- st_autorefresh(interval=1000, limit=None, key="refresh")
-
- if not state.data:
- st.write("No data available yet ! Stay tuned")
- return
-
- xp = hip.Experiment.from_iterable(state.data)
- ret_val = xp.to_streamlit(ret="selected_uids", key="hip").display()
- st.markdown("hiplot returned " + json.dumps(ret_val))
diff --git a/examples/app/hpo/objective.py b/examples/app/hpo/objective.py
deleted file mode 100644
index e320b66217db1..0000000000000
--- a/examples/app/hpo/objective.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import os
-import tempfile
-from datetime import datetime
-from typing import Optional
-
-import pandas as pd
-import torch
-from lightning.app import CloudCompute
-from lightning.app.components import TracerPythonScript
-from optuna.distributions import CategoricalDistribution, LogUniformDistribution
-from torchmetrics import Accuracy
-
-
-class ObjectiveWork(TracerPythonScript):
- def __init__(self, script_path: str, data_dir: str, cloud_compute: Optional[CloudCompute]):
- timestamp = datetime.now().strftime("%H:%M:%S")
- tmpdir = tempfile.TemporaryDirectory().name
- submission_path = os.path.join(tmpdir, f"{timestamp}.csv")
- best_model_path = os.path.join(tmpdir, f"{timestamp}.model.pt")
- super().__init__(
- script_path,
- script_args=[
- f"--train_data_path={data_dir}/train",
- f"--test_data_path={data_dir}/test",
- f"--submission_path={submission_path}",
- f"--best_model_path={best_model_path}",
- ],
- cloud_compute=cloud_compute,
- )
- self.data_dir = data_dir
- self.best_model_path = best_model_path
- self.submission_path = submission_path
- self.metric = None
- self.trial_id = None
- self.metric = None
- self.params = None
- self.has_told_study = False
-
- def run(self, trial_id: int, **params):
- self.trial_id = trial_id
- self.params = params
- self.script_args.extend([f"--{k}={v}" for k, v in params.items()])
- super().run()
- self.compute_metric()
-
- def _to_labels(self, path: str):
- return torch.from_numpy(pd.read_csv(path).label.values)
-
- def compute_metric(self):
- self.metric = -1 * float(
- Accuracy(task="binary")(
- self._to_labels(self.submission_path),
- self._to_labels(f"{self.data_dir}/ground_truth.csv"),
- )
- )
-
- @staticmethod
- def distributions():
- return {
- "backbone": CategoricalDistribution(["resnet18", "resnet34"]),
- "learning_rate": LogUniformDistribution(0.0001, 0.1),
- }
diff --git a/examples/app/hpo/pl_script.py b/examples/app/hpo/pl_script.py
deleted file mode 100644
index bbc453798431a..0000000000000
--- a/examples/app/hpo/pl_script.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import argparse
-import os
-
-import pandas as pd
-import torch
-from flash import Trainer
-from flash.image import ImageClassificationData, ImageClassifier
-
-# Parse arguments provided by the Work.
-parser = argparse.ArgumentParser()
-parser.add_argument("--train_data_path", type=str, required=True)
-parser.add_argument("--submission_path", type=str, required=True)
-parser.add_argument("--test_data_path", type=str, required=True)
-parser.add_argument("--best_model_path", type=str, required=True)
-# Optional
-parser.add_argument("--backbone", type=str, default="resnet18")
-parser.add_argument("--learning_rate", type=float, default=0.01)
-args = parser.parse_args()
-
-
-datamodule = ImageClassificationData.from_folders(
- train_folder=args.train_data_path,
- batch_size=8,
-)
-
-model = ImageClassifier(datamodule.num_classes, backbone=args.backbone)
-trainer = Trainer(fast_dev_run=True)
-trainer.fit(model, datamodule=datamodule)
-trainer.save_checkpoint(args.best_model_path)
-
-datamodule = ImageClassificationData.from_folders(
- predict_folder=args.test_data_path,
- batch_size=8,
-)
-
-predictions = Trainer().predict(model, datamodule=datamodule)
-submission_data = [
- {"filename": os.path.basename(p["metadata"]["filepath"]), "label": torch.argmax(p["preds"]).item()}
- for batch in predictions
- for p in batch
-]
-df = pd.DataFrame(submission_data)
-df.to_csv(args.submission_path, index=False)
diff --git a/examples/app/hpo/requirements.txt b/examples/app/hpo/requirements.txt
deleted file mode 100644
index bd85880da2237..0000000000000
--- a/examples/app/hpo/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-optuna
-lightning-flash[image,serve] == 0.7.0
-hiplot
diff --git a/examples/app/hpo/utils.py b/examples/app/hpo/utils.py
deleted file mode 100644
index a07ae73f8fd3e..0000000000000
--- a/examples/app/hpo/utils.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import os
-import os.path
-import tarfile
-import zipfile
-
-import requests
-
-
-def download_data(url: str, path: str = "data/", verbose: bool = False) -> None:
- """Download file with progressbar.
-
- # Code taken from: https://gist.github.com/ruxi/5d6803c116ec1130d484a4ab8c00c603
- # __author__ = "github.com/ruxi"
- # __license__ = "MIT"
-
- Usage:
- download_file('http://web4host.net/5MB.zip')
-
- """
- if url == "NEED_TO_BE_CREATED":
- raise NotImplementedError
-
- if not os.path.exists(path):
- os.makedirs(path)
- local_filename = os.path.join(path, url.split("/")[-1])
- r = requests.get(url, stream=True, verify=False)
- file_size = int(r.headers["Content-Length"]) if "Content-Length" in r.headers else 0
- chunk_size = 1024
- num_bars = int(file_size / chunk_size)
- if verbose:
- print({"file_size": file_size})
- print({"num_bars": num_bars})
-
- if not os.path.exists(local_filename):
- with open(local_filename, "wb") as fp:
- for chunk in r.iter_content(chunk_size=chunk_size):
- fp.write(chunk) # type: ignore
-
- def extract_tarfile(file_path: str, extract_path: str, mode: str):
- if os.path.exists(file_path):
- with tarfile.open(file_path, mode=mode) as tar_ref:
- for member in tar_ref.getmembers():
- try:
- tar_ref.extract(member, path=extract_path, set_attrs=False)
- except PermissionError:
- raise PermissionError(f"Could not extract tar file {file_path}")
-
- if ".zip" in local_filename:
- if os.path.exists(local_filename):
- with zipfile.ZipFile(local_filename, "r") as zip_ref:
- zip_ref.extractall(path) # noqa: S202
- elif local_filename.endswith(".tar.gz") or local_filename.endswith(".tgz"):
- extract_tarfile(local_filename, path, "r:gz")
- elif local_filename.endswith(".tar.bz2") or local_filename.endswith(".tbz"):
- extract_tarfile(local_filename, path, "r:bz2")
diff --git a/examples/app/installation_commands/app.py b/examples/app/installation_commands/app.py
deleted file mode 100644
index 526fcfef64413..0000000000000
--- a/examples/app/installation_commands/app.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# EXAMPLE COMPONENT: RUN A SCRIPT
-# app.py
-# !echo "I am installing a dependency not declared in a requirements file"
-# !pip install lmdb
-import lmdb
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-
-
-class YourComponent(LightningWork):
- def run(self):
- print(lmdb.version())
- print("lmdb successfully installed")
- print("Accessing a module in a Work or Flow body works!")
-
-
-class RootFlow(LightningFlow):
- def __init__(self, work):
- super().__init__()
- self.work = work
-
- def run(self):
- self.work.run()
-
-
-print(f"Accessing an object in main code body works!: version = {lmdb.version()}")
-
-
-# run on a cloud machine
-compute = CloudCompute("cpu")
-worker = YourComponent(cloud_compute=compute)
-app = LightningApp(RootFlow(worker))
diff --git a/examples/app/interruptible/app.py b/examples/app/interruptible/app.py
deleted file mode 100644
index a44fcf4dca3ed..0000000000000
--- a/examples/app/interruptible/app.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from time import sleep
-
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-
-
-class Work(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.counter = 0
-
- def run(self):
- while True:
- print(self.counter)
- self.counter += 1
- sleep(1)
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = Work(
- cloud_compute=CloudCompute("gpu", interruptible=True),
- start_with_flow=False,
- parallel=True,
- )
-
- def run(self):
- self.w.run()
- print(self.w.counter)
-
-
-app = LightningApp(Flow())
diff --git a/examples/app/justpy/app.py b/examples/app/justpy/app.py
deleted file mode 100644
index a4c9abc4cda1d..0000000000000
--- a/examples/app/justpy/app.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from typing import Callable
-
-from lightning import LightningApp, LightningFlow
-from lightning.app.frontend import JustPyFrontend
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- print(self.counter)
-
- def configure_layout(self):
- return JustPyFrontend(render_fn=render_fn)
-
-
-def render_fn(get_state: Callable) -> Callable:
- import justpy as jp
-
- def webpage():
- wp = jp.QuasarPage(dark=True)
- d = jp.Div(classes="q-pa-md q-gutter-sm", a=wp)
- container = jp.QBtn(color="primary", text="Counter: 0")
-
- async def click(*_):
- state = get_state()
- state.counter += 1
- container.text = f"Counter: {state.counter}"
-
- button = jp.QBtn(color="primary", text="Click Me!", click=click)
-
- d.add(button)
- d.add(container)
-
- return wp
-
- return webpage
-
-
-app = LightningApp(Flow())
diff --git a/examples/app/justpy/requirements.txt b/examples/app/justpy/requirements.txt
deleted file mode 100644
index 5f69409a4e4bb..0000000000000
--- a/examples/app/justpy/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-justpy
diff --git a/examples/app/layout/app.py b/examples/app/layout/app.py
deleted file mode 100644
index c57ab2eff78e7..0000000000000
--- a/examples/app/layout/app.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""An example showcasing how `configure_layout` can be used to nest user interfaces of different flows.
-
-Run the app:
-
-lightning run app examples/layout/demo.py
-
-This starts one server for each flow that returns a UI. Access the UI at the link printed in the terminal.
-
-"""
-
-import os
-from time import sleep
-
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.frontend import StaticWebFrontend, StreamlitFrontend
-
-
-class C11(LightningFlow):
- def __init__(self):
- super().__init__()
- self.message = "Hello Streamlit!"
-
- def run(self):
- pass
-
- def configure_layout(self):
- return StreamlitFrontend(render_fn=render_c11)
-
-
-def render_c11(state):
- import streamlit as st
-
- st.write(state.message)
-
-
-class C21(LightningFlow):
- def __init__(self):
- super().__init__()
-
- def run(self):
- pass
-
- def configure_layout(self):
- return StaticWebFrontend(os.path.join(os.path.dirname(__file__), "ui1"))
-
-
-class C22(LightningFlow):
- def __init__(self):
- super().__init__()
-
- def run(self):
- pass
-
- def configure_layout(self):
- return StaticWebFrontend(os.path.join(os.path.dirname(__file__), "ui2"))
-
-
-class C1(LightningFlow):
- def __init__(self):
- super().__init__()
- self.c11 = C11()
-
- def run(self):
- pass
-
-
-class C2(LightningFlow):
- def __init__(self):
- super().__init__()
- self.c21 = C21()
- self.c22 = C22()
-
- def run(self):
- pass
-
- def configure_layout(self):
- return [
- {"name": "one", "content": self.c21},
- {"name": "two", "content": self.c22},
- ]
-
-
-class Root(LightningFlow):
- def __init__(self):
- super().__init__()
- self.c1 = C1()
- self.c2 = C2()
-
- def run(self):
- sleep(10)
- self.stop("Layout End")
-
- def configure_layout(self):
- return [
- {"name": "one", "content": self.c1.c11},
- {"name": "two", "content": self.c2},
- {"name": "three", "content": "https://lightning.ai"},
- ]
-
-
-app = LightningApp(Root())
diff --git a/examples/app/layout/requirements.txt b/examples/app/layout/requirements.txt
deleted file mode 100644
index 12a4706528df6..0000000000000
--- a/examples/app/layout/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-streamlit
diff --git a/examples/app/layout/ui1/index.html b/examples/app/layout/ui1/index.html
deleted file mode 100644
index 11668dee1b911..0000000000000
--- a/examples/app/layout/ui1/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
- One
-
-
- One
-
-
diff --git a/examples/app/layout/ui2/index.html b/examples/app/layout/ui2/index.html
deleted file mode 100644
index 7398be1f7630d..0000000000000
--- a/examples/app/layout/ui2/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
- Two
-
-
- Two
-
-
diff --git a/examples/app/mount/app.py b/examples/app/mount/app.py
deleted file mode 100644
index b7e7c4df4746e..0000000000000
--- a/examples/app/mount/app.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import os
-
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-from lightning.app.storage import Mount
-
-
-class Work(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- def run(self):
- files = os.listdir("/content/esRedditJson/")
- for file in files:
- print(file)
- assert "esRedditJson1" in files
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work_1 = Work(
- cloud_compute=CloudCompute(
- mounts=Mount(
- source="s3://ryft-public-sample-data/esRedditJson/",
- mount_path="/content/esRedditJson/",
- ),
- )
- )
-
- def run(self):
- self.work_1.run()
-
-
-app = LightningApp(Flow())
diff --git a/examples/app/multi_node/README.md b/examples/app/multi_node/README.md
deleted file mode 100644
index aef152444f4a4..0000000000000
--- a/examples/app/multi_node/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Lightning & Multi Node Training
-
-Lightning supports makes multi-node training simple by providing a simple interface to orchestrate compute and data.
-
-## Multi Node with raw PyTorch
-
-You can run the multi-node raw PyTorch by running the following commands.
-
-Here is an example where you spawn your processes yourself.
-
-```bash
-lightning run app train_pytorch.py
-```
-
-or you can use the built-in component for it.
-
-```bash
-lightning run app train_pytorch_spawn.py
-```
-
-## Multi Node with raw PyTorch + Fabric
-
-You can run the multi-node raw PyTorch and Fabric by running the following commands.
-
-```bash
-lightning run app train_fabric.py
-```
-
-Using Fabric, you retain control over your loops while accessing in a minimal way all Lightning distributed strategies.
-
-## Multi Node with Lightning Trainer
-
-Lightning supports running Lightning Trainer from a script or within a Lightning Work.
-
-You can either run a script directly
-
-```bash
-lightning run app train_pl_script.py
-```
-
-or run your code within as a work.
-
-```bash
-lightning run app train_pl.py
-```
-
-## Multi Node with any frameworks
-
-```bash
-lightning run app train_any.py
-```
diff --git a/examples/app/multi_node/pl_boring_script.py b/examples/app/multi_node/pl_boring_script.py
deleted file mode 100644
index f14809354f405..0000000000000
--- a/examples/app/multi_node/pl_boring_script.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from lightning.pytorch import Trainer
-from lightning.pytorch.demos.boring_classes import BoringModel
-
-if __name__ == "__main__":
- model = BoringModel()
- trainer = Trainer(max_epochs=1)
- trainer.fit(model)
diff --git a/examples/app/multi_node/requirements.txt b/examples/app/multi_node/requirements.txt
deleted file mode 100644
index 12c6d5d5eac2a..0000000000000
--- a/examples/app/multi_node/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-torch
diff --git a/examples/app/multi_node/train_any.py b/examples/app/multi_node/train_any.py
deleted file mode 100644
index b3c89ad534f43..0000000000000
--- a/examples/app/multi_node/train_any.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from lightning.app import CloudCompute, LightningApp, LightningWork
-from lightning.app.components import MultiNode
-
-
-class AnyDistributedComponent(LightningWork):
- def run(
- self,
- main_address: str,
- main_port: int,
- num_nodes: int,
- node_rank: int,
- ):
- print(f"ADD YOUR DISTRIBUTED CODE: {main_address} {main_port} {num_nodes} {node_rank}.")
-
-
-app = LightningApp(
- MultiNode(
- AnyDistributedComponent,
- num_nodes=2,
- cloud_compute=CloudCompute("gpu"),
- )
-)
diff --git a/examples/app/multi_node/train_fabric.py b/examples/app/multi_node/train_fabric.py
deleted file mode 100644
index 2379c491f89aa..0000000000000
--- a/examples/app/multi_node/train_fabric.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import torch
-from lightning.app import CloudCompute, LightningApp, LightningWork
-from lightning.app.components import FabricMultiNode
-from lightning.fabric import Fabric
-
-
-class FabricPyTorchDistributed(LightningWork):
- def run(self):
- # 1. Prepare the model
- model = torch.nn.Sequential(
- torch.nn.Linear(1, 1),
- torch.nn.ReLU(),
- torch.nn.Linear(1, 1),
- )
-
- # 2. Create Fabric.
- fabric = Fabric(strategy="ddp", precision="16-mixed")
- model, optimizer = fabric.setup(model, torch.optim.SGD(model.parameters(), lr=0.01))
- criterion = torch.nn.MSELoss()
-
- # 3. Train the model for 1000 steps.
- for step in range(1000):
- model.zero_grad()
- x = torch.tensor([0.8]).to(fabric.device)
- target = torch.tensor([1.0]).to(fabric.device)
- output = model(x)
- loss = criterion(output, target)
- print(f"global_rank: {fabric.global_rank} step: {step} loss: {loss}")
- fabric.backward(loss)
- optimizer.step()
-
-
-# 8 GPUs: (2 nodes of 4 x v100)
-app = LightningApp(
- FabricMultiNode(
- FabricPyTorchDistributed,
- cloud_compute=CloudCompute("gpu-fast-multi"), # 4 x V100
- num_nodes=2,
- )
-)
diff --git a/examples/app/multi_node/train_lt.py b/examples/app/multi_node/train_lt.py
deleted file mode 100644
index 23a2863e757c7..0000000000000
--- a/examples/app/multi_node/train_lt.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# app.py
-from lightning.app import CloudCompute, LightningApp, LightningWork
-from lightning.app.components import LightningTrainerMultiNode
-from lightning.pytorch import Trainer
-from lightning.pytorch.demos.boring_classes import BoringModel
-
-
-class LightningTrainerDistributed(LightningWork):
- def run(self):
- model = BoringModel()
- trainer = Trainer(max_epochs=10, strategy="ddp")
- trainer.fit(model)
-
-
-# 8 GPUs: (2 nodes of 4 x v100)
-component = LightningTrainerMultiNode(
- LightningTrainerDistributed,
- num_nodes=2,
- cloud_compute=CloudCompute("gpu-fast-multi"), # 4 x v100
-)
-app = LightningApp(component)
diff --git a/examples/app/multi_node/train_lt_script.py b/examples/app/multi_node/train_lt_script.py
deleted file mode 100644
index 7f89bc95e9b17..0000000000000
--- a/examples/app/multi_node/train_lt_script.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from lightning.app import CloudCompute, LightningApp
-from lightning.app.components import LightningTrainerScript
-
-# 8 GPUs: (2 nodes of 4 x v100)
-app = LightningApp(
- LightningTrainerScript(
- "pl_boring_script.py",
- num_nodes=2,
- cloud_compute=CloudCompute("gpu-fast-multi"), # 4 x v100
- ),
-)
diff --git a/examples/app/multi_node/train_pytorch.py b/examples/app/multi_node/train_pytorch.py
deleted file mode 100644
index a1c7fb8eac207..0000000000000
--- a/examples/app/multi_node/train_pytorch.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# app.py
-# ! pip install torch
-import torch
-from lightning.app import CloudCompute, LightningApp, LightningWork
-from lightning.app.components import MultiNode
-from torch.nn.parallel.distributed import DistributedDataParallel
-
-
-def distributed_train(local_rank: int, main_address: str, main_port: int, num_nodes: int, node_rank: int, nprocs: int):
- # 1. SET UP DISTRIBUTED ENVIRONMENT
- global_rank = local_rank + node_rank * nprocs
- world_size = num_nodes * nprocs
-
- if torch.distributed.is_available() and not torch.distributed.is_initialized():
- torch.distributed.init_process_group(
- "nccl" if torch.cuda.is_available() else "gloo",
- rank=global_rank,
- world_size=world_size,
- init_method=f"tcp://{main_address}:{main_port}",
- )
-
- # 2. PREPARE DISTRIBUTED MODEL
- model = torch.nn.Linear(32, 2)
- device = torch.device(f"cuda:{local_rank}") if torch.cuda.is_available() else torch.device("cpu")
- model = DistributedDataParallel(model, device_ids=[local_rank] if torch.cuda.is_available() else None).to(device)
-
- # 3. SETUP LOSS AND OPTIMIZER
- criterion = torch.nn.MSELoss()
- optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
-
- # 4.TRAIN THE MODEL FOR 50 STEPS
- for step in range(50):
- model.zero_grad()
- x = torch.randn(64, 32).to(device)
- output = model(x)
- loss = criterion(output, torch.ones_like(output))
- print(f"global_rank: {global_rank} step: {step} loss: {loss}")
- loss.backward()
- optimizer.step()
-
- # 5. VERIFY ALL COPIES OF THE MODEL HAVE THE SAME WEIGTHS AT END OF TRAINING
- weight = model.module.weight.clone()
- torch.distributed.all_reduce(weight)
- assert torch.equal(model.module.weight, weight / world_size)
-
- print("Multi Node Distributed Training Done!")
-
-
-class PyTorchDistributed(LightningWork):
- def run(self, main_address: str, main_port: int, num_nodes: int, node_rank: int):
- nprocs = torch.cuda.device_count() if torch.cuda.is_available() else 1
- torch.multiprocessing.spawn(
- distributed_train, args=(main_address, main_port, num_nodes, node_rank, nprocs), nprocs=nprocs
- )
-
-
-# 8 GPUs: (2 nodes x 4 v 100)
-compute = CloudCompute("gpu-fast-multi") # 4 x v100
-component = MultiNode(PyTorchDistributed, num_nodes=2, cloud_compute=compute)
-app = LightningApp(component)
diff --git a/examples/app/multi_node/train_pytorch_spawn.py b/examples/app/multi_node/train_pytorch_spawn.py
deleted file mode 100644
index 8febfe5dcf696..0000000000000
--- a/examples/app/multi_node/train_pytorch_spawn.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import torch
-from lightning.app import CloudCompute, LightningApp, LightningWork
-from lightning.app.components import PyTorchSpawnMultiNode
-from torch.nn.parallel.distributed import DistributedDataParallel
-
-
-class PyTorchDistributed(LightningWork):
- def run(
- self,
- world_size: int,
- node_rank: int,
- global_rank: str,
- local_rank: int,
- ):
- # 1. Prepare the model
- model = torch.nn.Sequential(
- torch.nn.Linear(1, 1),
- torch.nn.ReLU(),
- torch.nn.Linear(1, 1),
- )
-
- # 2. Setup distributed training
- device = torch.device(f"cuda:{local_rank}") if torch.cuda.is_available() else torch.device("cpu")
- model = DistributedDataParallel(
- model.to(device), device_ids=[local_rank] if torch.cuda.is_available() else None
- )
-
- # 3. Prepare loss and optimizer
- criterion = torch.nn.MSELoss()
- optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
-
- # 4. Train the model for 1000 steps.
- for step in range(1000):
- model.zero_grad()
- x = torch.tensor([0.8]).to(device)
- target = torch.tensor([1.0]).to(device)
- output = model(x)
- loss = criterion(output, target)
- print(f"global_rank: {global_rank} step: {step} loss: {loss}")
- loss.backward()
- optimizer.step()
-
-
-# 8 GPUs: (2 nodes x 4 v 100)
-app = LightningApp(
- PyTorchSpawnMultiNode(
- PyTorchDistributed,
- num_nodes=2,
- cloud_compute=CloudCompute("gpu-fast-multi"), # 4 x v100
- )
-)
diff --git a/examples/app/payload/app.py b/examples/app/payload/app.py
deleted file mode 100644
index c92f589b088cd..0000000000000
--- a/examples/app/payload/app.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.storage import Payload
-
-
-class SourceFileWriterWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.value = None
-
- def run(self):
- self.value = Payload(42)
-
-
-class DestinationWork(LightningWork):
- def run(self, payload):
- assert payload.value == 42
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.src = SourceFileWriterWork()
- self.dst = DestinationWork()
-
- def run(self):
- self.src.run()
- self.dst.run(self.src.value)
- self.stop("Application End!")
-
-
-app = LightningApp(RootFlow())
diff --git a/examples/app/pickle_or_not/app.py b/examples/app/pickle_or_not/app.py
deleted file mode 100644
index aa7c3b01323da..0000000000000
--- a/examples/app/pickle_or_not/app.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import logging
-
-from lightning.app import LightningApp, LightningFlow, LightningWork
-
-logger = logging.getLogger(__name__)
-
-
-class PickleChecker(LightningWork):
- def run(self, pickle_image: bytes):
- parsed = self.parse_image(pickle_image)
- if parsed == b"it is a pickle":
- return True
- if parsed == b"it is not a pickle":
- return False
- raise Exception("Couldn't parse the image")
-
- @staticmethod
- def parse_image(image_str: bytes):
- return image_str
-
-
-class Slack(LightningFlow):
- def __init__(self):
- super().__init__()
-
- @staticmethod
- def send_message(message):
- logger.info(f"Sending message: {message}")
-
- def run(self):
- pass
-
-
-class RootComponent(LightningFlow):
- def __init__(self):
- super().__init__()
- self.pickle_checker = PickleChecker()
- self.slack = Slack()
- self.counter = 3
-
- def run(self):
- if self.counter > 0:
- logger.info(f"Running the app {self.counter}")
- image_str = b"it is not a pickle"
- if self.pickle_checker.run(image_str):
- self.slack.send_message("It's a pickle!")
- else:
- self.slack.send_message("It's not a pickle!")
- self.counter -= 1
- else:
- self.stop("Pickle or Not End")
-
-
-app = LightningApp(RootComponent())
diff --git a/examples/app/pickle_or_not/requirements.txt b/examples/app/pickle_or_not/requirements.txt
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/examples/app/server/app.py b/examples/app/server/app.py
deleted file mode 100644
index 97030179ffa78..0000000000000
--- a/examples/app/server/app.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# !pip install torchvision pydantic
-import base64
-import io
-
-import torch
-import torchvision
-from lightning.app import CloudCompute, LightningApp
-from lightning.app.components.serve import Image as InputImage
-from lightning.app.components.serve import PythonServer
-from PIL import Image
-from pydantic import BaseModel
-
-
-class PyTorchServer(PythonServer):
- def setup(self):
- self._model = torchvision.models.resnet18(pretrained=True)
- self._device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- self._model.to(self._device)
-
- def predict(self, request):
- image = base64.b64decode(request.image.encode("utf-8"))
- image = Image.open(io.BytesIO(image))
- transforms = torchvision.transforms.Compose([
- torchvision.transforms.Resize(224),
- torchvision.transforms.ToTensor(),
- torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
- ])
- image = transforms(image)
- image = image.to(self._device)
- prediction = self._model(image.unsqueeze(0))
- return {"prediction": prediction.argmax().item()}
-
-
-class OutputData(BaseModel):
- prediction: int
-
-
-component = PyTorchServer(input_type=InputImage, output_type=OutputData, cloud_compute=CloudCompute("gpu"))
-app = LightningApp(component)
diff --git a/examples/app/server_with_auto_scaler/app.py b/examples/app/server_with_auto_scaler/app.py
deleted file mode 100644
index 1320da6745fa6..0000000000000
--- a/examples/app/server_with_auto_scaler/app.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# ! pip install torch torchvision
-from typing import List
-
-import torch
-import torchvision
-from lightning.app import CloudCompute, LightningApp
-from pydantic import BaseModel
-
-
-class BatchRequestModel(BaseModel):
- inputs: List[app.components.Image]
-
-
-class BatchResponse(BaseModel):
- outputs: List[app.components.Number]
-
-
-class PyTorchServer(app.components.PythonServer):
- def __init__(self, *args, **kwargs):
- super().__init__(
- input_type=BatchRequestModel,
- output_type=BatchResponse,
- *args,
- **kwargs,
- )
-
- def setup(self):
- if torch.cuda.is_available():
- self._device = torch.device("cuda:0")
- elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
- self._device = torch.device("mps")
- else:
- self._device = torch.device("cpu")
- self._model = torchvision.models.resnet18(pretrained=True).to(self._device)
-
- def predict(self, requests: BatchRequestModel):
- transforms = torchvision.transforms.Compose([
- torchvision.transforms.Resize(224),
- torchvision.transforms.ToTensor(),
- torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
- ])
- images = []
- for request in requests.inputs:
- image = app.components.serve.types.image.Image.deserialize(request.image)
- image = transforms(image).unsqueeze(0)
- images.append(image)
- images = torch.cat(images)
- images = images.to(self._device)
- predictions = self._model(images)
- results = predictions.argmax(1).cpu().numpy().tolist()
- return BatchResponse(outputs=[{"prediction": pred} for pred in results])
-
-
-class MyAutoScaler(app.components.AutoScaler):
- def scale(self, replicas: int, metrics: dict) -> int:
- pending_requests = metrics["pending_requests"]
- active_or_pending_works = replicas + metrics["pending_works"]
-
- if active_or_pending_works == 0:
- return 1 if pending_requests > 0 else 0
-
- pending_requests_per_running_or_pending_work = pending_requests / active_or_pending_works
-
- # scale out if the number of pending requests exceeds max batch size.
- max_requests_per_work = self.max_batch_size
- if pending_requests_per_running_or_pending_work >= max_requests_per_work:
- return replicas + 1
-
- # scale in if the number of pending requests is below 25% of max_requests_per_work
- min_requests_per_work = max_requests_per_work * 0.25
- if pending_requests_per_running_or_pending_work < min_requests_per_work:
- return replicas - 1
-
- return replicas
-
-
-app = LightningApp(
- MyAutoScaler(
- # work class and args
- PyTorchServer,
- cloud_compute=CloudCompute("gpu"),
- # autoscaler specific args
- min_replicas=1,
- max_replicas=4,
- scale_out_interval=10,
- scale_in_interval=10,
- endpoint="predict",
- input_type=app.components.Image,
- output_type=app.components.Number,
- timeout_batching=1,
- max_batch_size=8,
- )
-)
diff --git a/examples/app/template_streamlit_ui/app.py b/examples/app/template_streamlit_ui/app.py
deleted file mode 100644
index 21a13036aa782..0000000000000
--- a/examples/app/template_streamlit_ui/app.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.frontend import StreamlitFrontend
-from lightning.app.utilities.state import AppState
-
-
-class StreamlitUI(LightningFlow):
- def __init__(self):
- super().__init__()
- self.message_to_print = "Hello World!"
- self.should_print = False
-
- def configure_layout(self):
- return StreamlitFrontend(render_fn=render_fn)
-
-
-def render_fn(state: AppState):
- import streamlit as st
-
- should_print = st.button("Should print to the terminal ?")
-
- if should_print:
- state.should_print = not state.should_print
-
- st.write("Currently printing." if state.should_print else "Currently waiting to print.")
-
-
-class HelloWorld(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
- self.streamlit_ui = StreamlitUI()
-
- def run(self):
- self.streamlit_ui.run()
- if self.streamlit_ui.should_print:
- print(f"{self.counter}: {self.streamlit_ui.message_to_print}")
- self.counter += 1
- self.streamlit_ui.should_print = False
-
- def configure_layout(self):
- return [{"name": "StreamLitUI", "content": self.streamlit_ui}]
-
-
-app = LightningApp(HelloWorld())
diff --git a/examples/app/template_streamlit_ui/requirements.txt b/examples/app/template_streamlit_ui/requirements.txt
deleted file mode 100644
index 12a4706528df6..0000000000000
--- a/examples/app/template_streamlit_ui/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-streamlit
diff --git a/examples/app/v0/.gitignore b/examples/app/v0/.gitignore
deleted file mode 100644
index 186149fa056fe..0000000000000
--- a/examples/app/v0/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.storage
-.lightning
diff --git a/examples/app/v0/README.md b/examples/app/v0/README.md
deleted file mode 100644
index 516283ae9cedd..0000000000000
--- a/examples/app/v0/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# v0 app
-
-This app is a flow-only app with nothing fancy.
-This is meant to present the basic functionalities of the lightning framework.
-
-## Starting it
-
-Local
-
-```bash
-lightning run app app.py
-```
-
-Cloud
-
-```bash
-lightning run app app.py --cloud
-```
diff --git a/examples/app/v0/app.py b/examples/app/v0/app.py
deleted file mode 100644
index d1cbb41c6dc10..0000000000000
--- a/examples/app/v0/app.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# v0_app.py
-import os
-from datetime import datetime
-from time import sleep
-
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.frontend import StaticWebFrontend
-
-
-class Word(LightningFlow):
- def __init__(self, letter):
- super().__init__()
- self.letter = letter
- self.repeats = letter
-
- def run(self):
- self.repeats += self.letter
-
- def configure_layout(self):
- return StaticWebFrontend(os.path.join(os.path.dirname(__file__), f"ui/{self.letter}"))
-
-
-class V0App(LightningFlow):
- def __init__(self):
- super().__init__()
- self.aas = Word("a")
- self.bbs = Word("b")
- self.counter = 0
-
- def run(self):
- now = datetime.now()
- now = now.strftime("%H:%M:%S")
- log = {"time": now, "a": self.aas.repeats, "b": self.bbs.repeats}
- print(log)
- self.aas.run()
- self.bbs.run()
-
- sleep(2.0)
- self.counter += 1
-
- def configure_layout(self):
- tab1 = {"name": "Tab_1", "content": self.aas}
- tab2 = {"name": "Tab_2", "content": self.bbs}
- tab3 = {"name": "Tab_3", "content": "https://tensorboard.dev/experiment/8m1aX0gcQ7aEmH0J7kbBtg/#scalars"}
-
- return [tab1, tab2, tab3]
-
-
-app = LightningApp(V0App(), log_level="debug")
diff --git a/examples/app/v0/emulate_ui.py b/examples/app/v0/emulate_ui.py
deleted file mode 100644
index 1d42c1cdf4c52..0000000000000
--- a/examples/app/v0/emulate_ui.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from time import sleep
-
-import requests
-from lightning.app.utilities.state import headers_for
-
-headers = headers_for({})
-headers["X-Lightning-Type"] = "DEFAULT"
-
-res = requests.get("http://127.0.0.1:7501/state", headers=headers)
-
-
-res = requests.post("http://127.0.0.1:7501/state", json={"stage": "running"}, headers=headers)
-print(res)
-
-sleep(10)
-
-res = requests.post("http://127.0.0.1:7501/state", json={"stage": "stopping"}, headers=headers)
-print(res)
diff --git a/examples/app/v0/requirements.txt b/examples/app/v0/requirements.txt
deleted file mode 100644
index edfce786a4d18..0000000000000
--- a/examples/app/v0/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-py
diff --git a/examples/app/v0/ui/a/index.html b/examples/app/v0/ui/a/index.html
deleted file mode 100644
index 6ddb9a5a1323c..0000000000000
--- a/examples/app/v0/ui/a/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Hello from component A
diff --git a/examples/app/v0/ui/b/index.html b/examples/app/v0/ui/b/index.html
deleted file mode 100644
index 3bfd9e24cb7f7..0000000000000
--- a/examples/app/v0/ui/b/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Hello from component B
diff --git a/examples/app/works_on_default_machine/app_v2.py b/examples/app/works_on_default_machine/app_v2.py
deleted file mode 100644
index 191070041b866..0000000000000
--- a/examples/app/works_on_default_machine/app_v2.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from fastapi import FastAPI
-from fastapi.middleware.cors import CORSMiddleware
-from lightning import CloudCompute, LightningApp, LightningFlow, LightningWork
-from uvicorn import run
-
-
-class Work(LightningWork):
- def __init__(self, **kwargs):
- super().__init__(parallel=True, **kwargs)
-
- def run(self):
- fastapi_service = FastAPI()
-
- fastapi_service.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
-
- @fastapi_service.get("/")
- def get_root():
- return {"Hello Word!"}
-
- run(fastapi_service, host=self.host, port=self.port)
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- # In the Cloud: All the works defined without passing explicitly a CloudCompute object
- # are running on the default machine.
- # This would apply to `work_a`, `work_b` and the dynamically created `work_d`.
-
- self.work_a = Work()
- self.work_b = Work()
-
- self.work_c = Work(cloud_compute=CloudCompute(name="cpu-small"))
-
- def run(self):
- if not hasattr(self, "work_d"):
- self.work_d = Work()
-
- for work in self.works():
- work.run()
-
- def configure_layout(self):
- return [{"name": w.name, "content": w} for i, w in enumerate(self.works())]
-
-
-app = LightningApp(Flow(), log_level="debug")
diff --git a/examples/app/works_on_default_machine/requirements.txt b/examples/app/works_on_default_machine/requirements.txt
deleted file mode 100644
index 12a4706528df6..0000000000000
--- a/examples/app/works_on_default_machine/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-streamlit
diff --git a/pyproject.toml b/pyproject.toml
index c24f27828fdd6..4189de8e9790a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -134,14 +134,7 @@ max-complexity = 10
files = [
"src/lightning",
]
-# This section is for folders with "-" as they are not valid python modules
-exclude = [
- "src/lightning/app/cli/app-template",
- "src/lightning/app/cli/component-template",
- "src/lightning/app/cli/pl-app-template",
- "src/lightning/app/cli/react-ui-template",
- "src/lightning/app/launcher/utils.py",
-]
+
install_types = "True"
non_interactive = "True"
disallow_untyped_defs = "True"
@@ -156,100 +149,6 @@ disable_error_code = "attr-defined"
# style choices
warn_no_return = "False"
-# Ignore mypy errors for these files
-# TODO: the goal is for this to be empty
-[[tool.mypy.overrides]]
-# the list can be generated with:
-# mypy --no-error-summary 2>&1 | tr ':' ' ' | awk '{print $1}' | sort | uniq | sed 's/\.py//g; s|src/||g; s|\/|\.|g' | xargs -I {} echo '"{}",'
-module = [
- "lightning.app.api.http_methods",
- "lightning.app.api.request_types",
- "lightning.app.cli.cmd_install",
- "lightning.app.cli.commands.app_commands",
- "lightning.app.cli.commands.cd",
- "lightning.app.cli.commands.cp",
- "lightning.app.cli.commands.ls",
- "lightning.app.cli.connect.app",
- "lightning.app.components.database.client",
- "lightning.app.components.database.server",
- "lightning.app.components.database.utilities",
- "lightning.app.components.multi_node.base",
- "lightning.app.components.multi_node.fabric",
- "lightning.app.components.multi_node.pytorch_spawn",
- "lightning.app.components.multi_node.trainer",
- "lightning.app.components.python.popen",
- "lightning.app.components.python.tracer",
- "lightning.app.components.serve.auto_scaler",
- "lightning.app.components.serve.gradio_server",
- "lightning.app.components.serve.python_server",
- "lightning.app.components.serve.serve",
- "lightning.app.components.serve.streamlit",
- "lightning.app.components.serve.types.image",
- "lightning.app.components.serve.types.type",
- "lightning.app.components.training",
- "lightning.app.frontend.panel.app_state_comm",
- "lightning.app.frontend.panel.app_state_watcher",
- "lightning.app.frontend.panel.panel_frontend",
- "lightning.app.frontend.panel.panel_serve_render_fn",
- "lightning.app.frontend.streamlit_base",
- "lightning.app.frontend.stream_lit",
- "lightning.app.frontend.utils",
- "lightning.app.frontend.web",
- "lightning.app.launcher.launcher",
- "lightning.app.launcher.lightning_backend",
- "lightning.app.launcher.lightning_hybrid_backend",
- "lightning.app.pdb.pdb",
- "lightning.app.runners.backends.backend",
- "lightning.app.runners.backends.cloud",
- "lightning.app.runners.backends.docker",
- "lightning.app.runners.backends.mp_process",
- "lightning.app.runners.cloud",
- "lightning.app.runners.multiprocess",
- "lightning.app.runners.runtime",
- "lightning.app.source_code.copytree",
- "lightning.app.source_code.hashing",
- "lightning.app.source_code.local",
- "lightning.app.source_code.tar",
- "lightning.app.source_code.uploader",
- "lightning.app.storage.copier",
- "lightning.app.storage.drive",
- "lightning.app.storage.filesystem",
- "lightning.app.storage.orchestrator",
- "lightning.app.storage.path",
- "lightning.app.storage.payload",
- "lightning.app.structures.dict",
- "lightning.app.structures.list",
- "lightning.app.testing.helpers",
- "lightning.app.testing.testing",
- "lightning.app.utilities.app_helpers",
- "lightning.app.utilities.app_logs",
- "lightning.app.utilities.cli_helpers",
- "lightning.app.utilities.cloud",
- "lightning.app.utilities.commands.base",
- "lightning.app.utilities.component",
- "lightning.app.utilities.enum",
- "lightning.app.utilities.exceptions",
- "lightning.app.utilities.git",
- "lightning.app.utilities.imports",
- "lightning.app.utilities.introspection",
- "lightning.app.utilities.layout",
- "lightning.app.utilities.load_app",
- "lightning.app.utilities.log_helpers",
- "lightning.app.utilities.login",
- "lightning.app.utilities.name_generator",
- "lightning.app.utilities.network",
- "lightning.app.utilities.openapi",
- "lightning.app.utilities.packaging.cloud_compute",
- "lightning.app.utilities.packaging.lightning_utils",
- "lightning.app.utilities.proxies",
- "lightning.app.utilities.scheduler",
- "lightning.app.utilities.state",
- "lightning.app.utilities.tracer",
- "lightning.app.utilities.tree",
- "lightning.store.utils",
-]
-ignore_errors = "True"
-
[tool.coverage.report]
exclude_lines = [
diff --git a/requirements.txt b/requirements.txt
index bcb63693fbbe3..4910c7fbe7fc0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
# the default package dependencies
--r ./requirements/app/app.txt
-r ./requirements/fabric/base.txt
-r ./requirements/pytorch/base.txt
diff --git a/requirements/app/app.txt b/requirements/app/app.txt
deleted file mode 100644
index 25c9bb893fe60..0000000000000
--- a/requirements/app/app.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-lightning-cloud == 0.5.70 # Must be pinned to ensure compatibility
-packaging
-typing-extensions >=4.4.0, <4.10.0
-deepdiff >=5.7.0, <6.6.0
-fsspec[http] >=2022.5.0, <2023.11.0
-croniter >=1.3.0, <1.5.0 # strict; TODO: for now until we find something more robust.
-traitlets >=5.3.0, <5.12.0
-arrow >=1.2.0, <1.3.0
-lightning-utilities >=0.10.0, <0.12.0
-beautifulsoup4 >=4.8.0, <4.13.0
-inquirer >=2.10.0, <3.2.0
-psutil <5.9.6
-click <8.2
-python-multipart >=0.0.5, <=0.0.6
-backoff >=2.2.1, <2.3.0
-
-fastapi >=0.92.0, <0.104.0
-starlette # https://fastapi.tiangolo.com/deployment/versions/#about-starlette
-pydantic >=1.7.4 # https://fastapi.tiangolo.com/deployment/versions/#about-pydantic
-
-dateutils <0.8.0
-Jinja2 <3.2.0
-PyYAML <=6.0.1
-requests <2.32.0
-rich >=12.3.0, <13.6.0
-urllib3 <2.0.0
-uvicorn <0.24.0
-websocket-client <1.7.0
-websockets <11.1.0
-numpy >=1.17.2, <2.0
-msgpack
diff --git a/requirements/app/cloud.txt b/requirements/app/cloud.txt
deleted file mode 100644
index ad5d2d583d17f..0000000000000
--- a/requirements/app/cloud.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-redis >=4.0.1, <5.1.0
-docker >=5.0.0, <6.1.4
-s3fs >=2022.5.0, <2023.6.1
-# setuptools==59.5.0
diff --git a/requirements/app/components.txt b/requirements/app/components.txt
deleted file mode 100644
index 78509b6b0269e..0000000000000
--- a/requirements/app/components.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# deps required by components in the lightning app repository (src/lightning/app/components)
-lightning_api_access >=0.0.3 # serve
-aiohttp >=3.8.0, <3.9.0 # auto_scaler
-lightning-fabric >=1.9.0 # multinode
-pytorch-lightning >=1.9.0 # multinode
diff --git a/requirements/app/docs.txt b/requirements/app/docs.txt
deleted file mode 100644
index f2db5000b9113..0000000000000
--- a/requirements/app/docs.txt
+++ /dev/null
@@ -1 +0,0 @@
--r ../docs.txt
diff --git a/requirements/app/test.txt b/requirements/app/test.txt
deleted file mode 100644
index fd9629649c89b..0000000000000
--- a/requirements/app/test.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-coverage ==7.3.1
-pytest ==7.4.0
-pytest-timeout ==2.1.0
-pytest-cov ==4.1.0
-pytest-doctestplus ==1.0.0
-pytest-asyncio ==0.21.1
-# pytest-random-order ==1.1.0
-pytest-rerunfailures ==12.0
-pytest-xdist ==3.3.1
-
-playwright ==1.38.0
-httpx ==0.25.0
-trio <0.22.0 # strict https://github.com/python-trio/trio/pull/2213
-pympler
-psutil <5.10.0
-setuptools <68.3.0
-requests-mock ==1.11.0
-pandas
diff --git a/requirements/app/ui.txt b/requirements/app/ui.txt
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/setup.py b/setup.py
index 698eaa5abe71e..bfc329bb8fe88 100755
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@
There are considered three main scenarios for installing this project:
-1. Using PyPI registry when you can install `pytorch-lightning`, `lightning-app`, etc. or `lightning` for all.
+1. Using PyPI registry when you can install `pytorch-lightning`, etc. or `lightning` for all.
2. Installation from source code after cloning repository.
In such case we recommend to use command `pip install .` or `pip install -e .` for development version
@@ -26,12 +26,11 @@
- for `pytorch-lightning` use `export PACKAGE_NAME=pytorch ; pip install .`
- for `lightning-fabric` use `export PACKAGE_NAME=fabric ; pip install .`
- - for `lightning-app` use `export PACKAGE_NAME=app ; pip install .`
3. Building packages as sdist or binary wheel and installing or publish to PyPI afterwords you use command
`python setup.py sdist` or `python setup.py bdist_wheel` accordingly.
In case you want to build just a particular package you want to set an environment variable:
- `PACKAGE_NAME=lightning|pytorch|app|fabric python setup.py sdist|bdist_wheel`
+ `PACKAGE_NAME=lightning|pytorch|fabric python setup.py sdist|bdist_wheel`
4. Automated releasing with GitHub action is natural extension of 3) is composed of three consecutive steps:
a) determine which packages shall be released based on version increment in `__version__.py` and eventually
@@ -57,7 +56,6 @@
_PACKAGE_MAPPING = {
"lightning": "lightning",
"pytorch": "pytorch_lightning",
- "app": "lightning_app",
"fabric": "lightning_fabric",
}
# https://packaging.python.org/guides/single-sourcing-package-version/
diff --git a/src/app-ui-version.info b/src/app-ui-version.info
deleted file mode 100644
index ae39fab35ff1f..0000000000000
--- a/src/app-ui-version.info
+++ /dev/null
@@ -1 +0,0 @@
-v0.0.0
diff --git a/src/lightning/__init__.py b/src/lightning/__init__.py
index c191334d2c218..1b054ed6715f7 100644
--- a/src/lightning/__init__.py
+++ b/src/lightning/__init__.py
@@ -1,7 +1,6 @@
"""Root package info."""
import logging
-import sys
# explicitly don't set root logger's propagation and leave this to subpackages to manage
_logger = logging.getLogger(__name__)
@@ -31,19 +30,3 @@
"Fabric",
"__version__",
]
-
-
-def _cli_entry_point() -> None:
- from lightning_utilities.core.imports import ModuleAvailableCache, RequirementCache
-
- if not (
- ModuleAvailableCache("lightning.app")
- if RequirementCache("lightning-utilities<0.10.0")
- else RequirementCache(module="lightning.app")
- ):
- print("The `lightning` command requires additional dependencies: `pip install lightning[app]`")
- sys.exit(1)
-
- from lightning.app.cli.lightning_cli import main
-
- main()
diff --git a/src/lightning/__main__.py b/src/lightning/__main__.py
deleted file mode 100644
index 57b27ab968c82..0000000000000
--- a/src/lightning/__main__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from lightning.app.cli.lightning_cli import main
-
-if __name__ == "__main__":
- main()
diff --git a/src/lightning/__setup__.py b/src/lightning/__setup__.py
index 81eae48545180..4bc4dff23be50 100644
--- a/src/lightning/__setup__.py
+++ b/src/lightning/__setup__.py
@@ -45,12 +45,8 @@ def _prepare_extras() -> Dict[str, Any]:
extras["fabric-dev"] = extras["fabric-all"] + extras["fabric-test"]
extras["pytorch-all"] = extras["pytorch-extra"] + extras["pytorch-strategies"] + extras["pytorch-examples"]
extras["pytorch-dev"] = extras["pytorch-all"] + extras["pytorch-test"]
- extras["app-extra"] = extras["app-app"] + extras["app-cloud"] + extras["app-ui"] + extras["app-components"]
- extras["app-all"] = extras["app-extra"]
- extras["app-dev"] = extras["app-all"] + extras["app-test"]
- extras["store-store"] = extras["app-app"] # todo: consider cutting/leaning this dependency
- # merge per-project extras of the same category, e.g. `app-test` + `fabric-test`
+ # merge per-project extras of the same category
for extra in list(extras):
name = "-".join(extra.split("-")[1:])
extras[name] = extras.get(name, []) + extras[extra]
@@ -74,17 +70,6 @@ def _setup_args() -> Dict[str, Any]:
_PROJECT_ROOT, homepage=about.__homepage__, version=version.version
)
- # TODO: remove this once lightning-ui package is ready as a dependency
- ui_ver_file = os.path.join(_SOURCE_ROOT, "app-ui-version.info")
- if os.path.isfile(ui_ver_file):
- with open(ui_ver_file, encoding="utf-8") as fo:
- ui_version = fo.readlines()[0].strip()
- download_fe_version = {"version": ui_version}
- else:
- print(f"Missing file with FE version: {ui_ver_file}")
- download_fe_version = {}
- _ASSISTANT._download_frontend(os.path.join(_PACKAGE_ROOT, "app"), **download_fe_version)
-
# TODO: consider invaliding some additional arguments from packages, for example if include data or safe to zip
install_requires = _ASSISTANT.load_requirements(
@@ -114,7 +99,6 @@ def _setup_args() -> Dict[str, Any]:
"console_scripts": [
"fabric = lightning.fabric.cli:_main",
"lightning = lightning.fabric.cli:_legacy_main",
- "lightning_app = lightning:_cli_entry_point",
],
},
"setup_requires": [],
diff --git a/src/lightning/app/CHANGELOG.md b/src/lightning/app/CHANGELOG.md
deleted file mode 100644
index d09c302b22067..0000000000000
--- a/src/lightning/app/CHANGELOG.md
+++ /dev/null
@@ -1,608 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
-
-## [2.2.0] - 2024-02-09
-
-## Changed
-
-- Renames the `lightning` cli to `lightning_app` ([#19440](https://github.com/Lightning-AI/pytorch-lightning/pull/19440))
-
-
-## [2.1.4] - 2024-01-31
-
-### Changed
-
-- Remove torch distributed for the Dataset Optimizer ([#19182](https://github.com/Lightning-AI/lightning/pull/19182))
-
-
-## [2.1.3] - 2023-12-21
-
-### Changed
-
-- Lightning App: Use the batch get endpoint ([#19180](https://github.com/Lightning-AI/lightning/pull/19180))
-- Drop starsessions from App's requirements ([#18470](https://github.com/Lightning-AI/lightning/pull/18470))
-- Optimize loading time for chunks to be there ([#19109](https://github.com/Lightning-AI/lightning/pull/19109))
-
-
-## [2.1.2] - 2023-11-15
-
-### Changed
-
-- Forced plugin server to use localhost ([#18976](https://github.com/Lightning-AI/lightning/pull/18976))
-- Enabled bundling additional files into app source ([#18980](https://github.com/Lightning-AI/lightning/pull/18980))
-- Limited rate of requests to http queue ([#18981](https://github.com/Lightning-AI/lightning/pull/18981))
-
-
-## [2.1.1] - 2023-11-06
-
-### Added
-
-- Added flow `fail()` ([#18883](https://github.com/Lightning-AI/lightning/pull/18883))
-
-### Fixed
-
-- Fixed failing lightning cli entry point ([#18821](https://github.com/Lightning-AI/lightning/pull/18821))
-
-
-## [2.1.0] - 2023-10-11
-
-### Added
-
-- Allow customizing `gradio` components with lightning colors ([#17054](https://github.com/Lightning-AI/lightning/pull/17054))
-
-### Changed
-
-- Changed `LocalSourceCodeDir` cache_location to not use home in some certain cases ([#17491](https://github.com/Lightning-AI/lightning/pull/17491))
-
-### Removed
-
-- Remove cluster commands from the CLI ([#18151](https://github.com/Lightning-AI/lightning/pull/18151))
-
-
-## [2.0.9] - 2023-09-14
-
-### Fixed
-
-- Replace LightningClient with import from lightning_cloud ([#18544](https://github.com/Lightning-AI/lightning/pull/18544))
-
-
-## [2.0.8] - 2023-08-29
-
-## Changed
-
-- Change top folder ([#18212](https://github.com/Lightning-AI/lightning/pull/18212))
-- Remove `_handle_is_headless` calls in app run loop ([#18362](https://github.com/Lightning-AI/lightning/pull/18362))
-
-
-## [2.0.7] - 2023-08-14
-
-### Changed
-
-- Removed the top-level import `lightning.pdb`; import `lightning.app.pdb` instead ([#18177](https://github.com/Lightning-AI/lightning/pull/18177))
-- Client retries forever ([#18065](https://github.com/Lightning-AI/lightning/pull/18065))
-
-### Fixed
-
-- Fixed an issue that would prevent the user to set the multiprocessing start method after importing lightning ([#18177](https://github.com/Lightning-AI/lightning/pull/18177))
-
-
-## [2.0.6] - 2023-07-20
-
-### Fixed
-
-- Fixed handling a `None` request in the file orchestration queue ([#18111](https://github.com/Lightning-AI/lightning/pull/18111))
-
-
-## [2.0.5] - 2023-07-07
-
-### Added
-
-- plugin: store source app ([#17892](https://github.com/Lightning-AI/lightning/pull/17892))
-- added colocation identifier ([#16796](https://github.com/Lightning-AI/lightning/pull/16796))
-- Added exponential backoff to HTTPQueue put ([#18013](https://github.com/Lightning-AI/lightning/pull/18013))
-- Content for plugins ([#17243](https://github.com/Lightning-AI/lightning/pull/17243))
-
-### Changed
-
-- Save a reference to created tasks, to avoid tasks disappearing ([#17946](https://github.com/Lightning-AI/lightning/pull/17946))
-
-
-## [2.0.4] - 2023-06-22
-
-### Fixed
-
-- bumped several dependencies to address security vulnerabilities.
-
-
-## [2.0.3] - 2023-06-07
-
-- Added the property `LightningWork.public_ip` that exposes the public IP of the `LightningWork` instance ([#17742](https://github.com/Lightning-AI/lightning/pull/17742))
-- Add missing python-multipart dependency ([#17244](https://github.com/Lightning-AI/lightning/pull/17244))
-
-### Changed
-
-- Made type hints public ([#17100](https://github.com/Lightning-AI/lightning/pull/17100))
-
-### Fixed
-
-- Fixed `LightningWork.internal_ip` that was mistakenly exposing the public IP instead; now exposes the private/internal IP address ([#17742](https://github.com/Lightning-AI/lightning/pull/17742))
-- Fixed resolution of latest version in CLI ([#17351](https://github.com/Lightning-AI/lightning/pull/17351))
-- Fixed property raised instead of returned ([#17595](https://github.com/Lightning-AI/lightning/pull/17595))
-- Fixed get project ([#17617](https://github.com/Lightning-AI/lightning/pull/17617), [#17666](https://github.com/Lightning-AI/lightning/pull/17666))
-
-
-## [2.0.2] - 2023-04-24
-
-### Fixed
-
-- Resolved Lightning App with remote storage ([#17426](https://github.com/Lightning-AI/lightning/pull/17426))
-- Fixed `AppState`, streamlit example ([#17452](https://github.com/Lightning-AI/lightning/pull/17452))
-
-
-## [2.0.1] - 2023-04-11
-
-### Fixed
-
-- Fix frontend hosts when running with multi-process in the cloud ([#17324](https://github.com/Lightning-AI/lightning/pull/17324))
-
-
-## [2.0.0] - 2023-03-15
-
-### Added
-
-- Added `--zip` option to the `lightning cp` command to copy content from the Cloud Platform Filesystem as a zipfile
-
-### Changed
-
-- Changed minimum supported version of `rich` from `10.14.0` to `12.13.0` ([#16798](https://github.com/Lightning-AI/lightning/pull/16798))
-
-### Removed
-
-- Removed support for Python 3.7 ([#16579](https://github.com/Lightning-AI/lightning/pull/16579))
-
-
-## [1.9.4] - 2023-03-01
-
-### Removed
-
-- Removed implicit ui testing with `testing.run_app_in_cloud` in favor of headless login and app selection ([#16741](https://github.com/Lightning-AI/lightning/pull/16741))
-
-
-## [1.9.3] - 2023-02-21
-
-### Fixed
-
-- Fixed `lightning open` command and improved redirects ([#16794](https://github.com/Lightning-AI/lightning/pull/16794))
-
-
-## [1.9.2] - 2023-02-15
-
-- Added Storage Commands ([#16740](https://github.com/Lightning-AI/lightning/pull/16740))
- * `rm`: Delete files from your Cloud Platform Filesystem
-- Added `lightning connect data` to register data connection to private s3 buckets ([#16738](https://github.com/Lightning-AI/lightning/pull/16738))
-
-
-## [1.9.1] - 2023-02-10
-
-### Added
-- Added `lightning open` command ([#16482](https://github.com/Lightning-AI/lightning/pull/16482))
-- Added experimental support for interruptible GPU in the cloud ([#16399](https://github.com/Lightning-AI/lightning/pull/16399))
-- Added FileSystem abstraction to simply manipulation of files ([#16581](https://github.com/Lightning-AI/lightning/pull/16581))
-- Added Storage Commands ([#16606](https://github.com/Lightning-AI/lightning/pull/16606))
- * `ls`: List files from your Cloud Platform Filesystem
- * `cd`: Change the current directory within your Cloud Platform filesystem (terminal session based)
- * `pwd`: Return the current folder in your Cloud Platform Filesystem
- * `cp`: Copy files between your Cloud Platform Filesystem and local filesystem
-- Prevent to `cd` into non existent folders ([#16645](https://github.com/Lightning-AI/lightning/pull/16645))
-- Enabled `cp` (upload) at project level ([#16631](https://github.com/Lightning-AI/lightning/pull/16631))
-- Enabled `ls` and `cp` (download) at project level ([#16622](https://github.com/Lightning-AI/lightning/pull/16622))
-- Added `lightning connect data` to register data connection to s3 buckets ([#16670](https://github.com/Lightning-AI/lightning/pull/16670))
-- Added support for running with multiprocessing in the cloud ([#16624](https://github.com/Lightning-AI/lightning/pull/16624))
-- Initial plugin server ([#16523](https://github.com/Lightning-AI/lightning/pull/16523))
-- Connect and Disconnect node ([#16700](https://github.com/Lightning-AI/lightning/pull/16700))
-
-### Changed
-
-- Changed the default `LightningClient(retry=False)` to `retry=True` ([#16382](https://github.com/Lightning-AI/lightning/pull/16382))
-- Add support for async predict method in PythonServer and remove torch context ([#16453](https://github.com/Lightning-AI/lightning/pull/16453))
-- Renamed `lightning.app.components.LiteMultiNode` to `lightning.app.components.FabricMultiNode` ([#16505](https://github.com/Lightning-AI/lightning/pull/16505))
-- Changed the command `lightning connect` to `lightning connect app` for consistency ([#16670](https://github.com/Lightning-AI/lightning/pull/16670))
-- Refactor cloud dispatch and update to new API ([#16456](https://github.com/Lightning-AI/lightning/pull/16456))
-- Updated app URLs to the latest format ([#16568](https://github.com/Lightning-AI/lightning/pull/16568))
-
-### Fixed
-
-- Fixed a deadlock causing apps not to exit properly when running locally ([#16623](https://github.com/Lightning-AI/lightning/pull/16623))
-- Fixed the Drive root_folder not parsed properly ([#16454](https://github.com/Lightning-AI/lightning/pull/16454))
-- Fixed malformed path when downloading files using `lightning cp` ([#16626](https://github.com/Lightning-AI/lightning/pull/16626))
-- Fixed app name in URL ([#16575](https://github.com/Lightning-AI/lightning/pull/16575))
-
-
-## [1.9.0] - 2023-01-17
-
-### Added
-
-- Added a possibility to set up basic authentication for Lightning apps ([#16105](https://github.com/Lightning-AI/lightning/pull/16105))
-
-### Changed
-
-- The LoadBalancer now uses internal ip + port instead of URL exposed ([#16119](https://github.com/Lightning-AI/lightning/pull/16119))
-- Added support for logging in different trainer stages with `DeviceStatsMonitor` ([#16002](https://github.com/Lightning-AI/lightning/pull/16002))
-- Changed `lightning.app.components.serve.gradio` to `lightning.app.components.serve.gradio_server` ([#16201](https://github.com/Lightning-AI/lightning/pull/16201))
-- Made cluster creation/deletion async by default ([#16185](https://github.com/Lightning-AI/lightning/pull/16185))
-- Expose `LightningFlow.stop` method to stop the flow similar to works ([##16378](https://github.com/Lightning-AI/lightning/pull/16378))
-
-### Fixed
-
-- Fixed not being able to run multiple lightning apps locally due to port collision ([#15819](https://github.com/Lightning-AI/lightning/pull/15819))
-- Avoid `relpath` bug on Windows ([#16164](https://github.com/Lightning-AI/lightning/pull/16164))
-- Avoid using the deprecated `LooseVersion` ([#16162](https://github.com/Lightning-AI/lightning/pull/16162))
-- Porting fixes to autoscaler component ([#16249](https://github.com/Lightning-AI/lightning/pull/16249))
-- Fixed a bug where `lightning login` with env variables would not correctly save the credentials ([#16339](https://github.com/Lightning-AI/lightning/pull/16339))
-
-
-## [1.8.6] - 2022-12-21
-
-### Added
-
-- Added partial support for fastapi `Request` annotation in `configure_api` handlers ([#16047](https://github.com/Lightning-AI/lightning/pull/16047))
-- Added a nicer UI with URL and examples for the autoscaler component ([#16063](https://github.com/Lightning-AI/lightning/pull/16063))
-- Enabled users to have more control over scaling out/in interval ([#16093](https://github.com/Lightning-AI/lightning/pull/16093))
-- Added more datatypes to serving component ([#16018](https://github.com/Lightning-AI/lightning/pull/16018))
-- Added `work.delete` method to delete the work ([#16103](https://github.com/Lightning-AI/lightning/pull/16103))
-- Added `display_name` property to LightningWork for the cloud ([#16095](https://github.com/Lightning-AI/lightning/pull/16095))
-- Added `ColdStartProxy` to the AutoScaler ([#16094](https://github.com/Lightning-AI/lightning/pull/16094))
-- Added status endpoint, enable `ready` ([#16075](https://github.com/Lightning-AI/lightning/pull/16075))
-- Implemented `ready` for components ([#16129](https://github.com/Lightning-AI/lightning/pull/16129))
-
-### Changed
-
-- The default `start_method` for creating Work processes locally on MacOS is now 'spawn' (previously 'fork') ([#16089](https://github.com/Lightning-AI/lightning/pull/16089))
-- The utility `lightning.app.utilities.cloud.is_running_in_cloud` now returns `True` during loading of the app locally when running with `--cloud` ([#16045](https://github.com/Lightning-AI/lightning/pull/16045))
-- Updated Multinode Warning ([#16091](https://github.com/Lightning-AI/lightning/pull/16091))
-- Updated app testing ([#16000](https://github.com/Lightning-AI/lightning/pull/16000))
-- Changed overwrite to `True` ([#16009](https://github.com/Lightning-AI/lightning/pull/16009))
-- Simplified messaging in cloud dispatch ([#16160](https://github.com/Lightning-AI/lightning/pull/16160))
-- Added annotations endpoint ([#16159](https://github.com/Lightning-AI/lightning/pull/16159))
-
-### Fixed
-
-- Fixed `PythonServer` messaging "Your app has started" ([#15989](https://github.com/Lightning-AI/lightning/pull/15989))
-- Fixed auto-batching to enable batching for requests coming even after batch interval but is in the queue ([#16110](https://github.com/Lightning-AI/lightning/pull/16110))
-- Fixed a bug where `AutoScaler` would fail with min_replica=0 ([#16092](https://github.com/Lightning-AI/lightning/pull/16092)
-- Fixed a non-thread safe deepcopy in the scheduler ([#16114](https://github.com/Lightning-AI/lightning/pull/16114))
-- Fixed Http Queue sleeping for 1 sec by default if no delta were found ([#16114](https://github.com/Lightning-AI/lightning/pull/16114))
-- Fixed the endpoint info tab not showing up in `AutoScaler` UI ([#16128](https://github.com/Lightning-AI/lightning/pull/16128))
-- Fixed an issue where an exception would be raised in the logs when using a recent version of streamlit ([#16139](https://github.com/Lightning-AI/lightning/pull/16139))
-- Fixed e2e tests ([#16146](https://github.com/Lightning-AI/lightning/pull/16146))
-
-
-## [1.8.5] - 2022-12-15
-
-### Added
-
-- Added `Lightning{Flow,Work}.lightningignores` attributes to programmatically ignore files before uploading to the cloud ([#15818](https://github.com/Lightning-AI/lightning/pull/15818))
-- Added a progress bar while connecting to an app through the CLI ([#16035](https://github.com/Lightning-AI/lightning/pull/16035))
-- Support running on multiple clusters ([#16016](https://github.com/Lightning-AI/lightning/pull/16016))
-- Added guards to cluster deletion from cli ([#16053](https://github.com/Lightning-AI/lightning/pull/16053))
-
-### Changed
-
-- Cleanup cluster waiting ([#16054](https://github.com/Lightning-AI/lightning/pull/16054))
-
-### Fixed
-
-- Fixed `DDPStrategy` import in app framework ([#16029](https://github.com/Lightning-AI/lightning/pull/16029))
-- Fixed `AutoScaler` raising an exception when non-default cloud compute is specified ([#15991](https://github.com/Lightning-AI/lightning/pull/15991))
-- Fixed and improvements of login flow ([#16052](https://github.com/Lightning-AI/lightning/pull/16052))
-- Fixed the debugger detection mechanism for lightning App in VSCode ([#16068](https://github.com/Lightning-AI/lightning/pull/16068))
-- Fixed bug where components that are re-instantiated several times failed to initialize if they were modifying `self.lightningignore` ([#16080](https://github.com/Lightning-AI/lightning/pull/16080))
-- Fixed a bug where apps that had previously been deleted could not be run again from the CLI ([#16082](https://github.com/Lightning-AI/lightning/pull/16082))
-- Fixed install/upgrade - removing single quote ([#16079](https://github.com/Lightning-AI/lightning/pull/16079))
-
-
-## [1.8.4] - 2022-12-08
-
-### Added
-
-- Add `code_dir` argument to tracer run ([#15771](https://github.com/Lightning-AI/lightning/pull/15771))
-- Added the CLI command `lightning run model` to launch a `LightningLite` accelerated script ([#15506](https://github.com/Lightning-AI/lightning/pull/15506))
-- Added the CLI command `lightning delete app` to delete a lightning app on the cloud ([#15783](https://github.com/Lightning-AI/lightning/pull/15783))
-- 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))
-- Utility for pickling work object safely even from a child process ([#15836](https://github.com/Lightning-AI/lightning/pull/15836))
-- Added `AutoScaler` component (
- [#15769](https://github.com/Lightning-AI/lightning/pull/15769),
- [#15971](https://github.com/Lightning-AI/lightning/pull/15971),
- [#15966](https://github.com/Lightning-AI/lightning/pull/15966)
-)
-- 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))
-- Added private work attributed `_start_method` to customize how to start the works ([#15923](https://github.com/Lightning-AI/lightning/pull/15923))
-- Added a `configure_layout` method to the `LightningWork` which can be used to control how the work is handled in the layout of a parent flow ([#15926](https://github.com/Lightning-AI/lightning/pull/15926))
-- Added the ability to run a Lightning App or Component directly from the Gallery using `lightning run app organization/name` ([#15941](https://github.com/Lightning-AI/lightning/pull/15941))
-- Added automatic conversion of list and dict of works and flows to structures ([#15961](https://github.com/Lightning-AI/lightning/pull/15961))
-
-### Changed
-
-- The `MultiNode` components now warn the user when running with `num_nodes > 1` locally ([#15806](https://github.com/Lightning-AI/lightning/pull/15806))
-- Cluster creation and deletion now waits by default [#15458](https://github.com/Lightning-AI/lightning/pull/15458)
-- Running an app without a UI locally no longer opens the browser ([#15875](https://github.com/Lightning-AI/lightning/pull/15875))
-- 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))
-- 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))
-- Dropped name column from cluster list ([#15721](https://github.com/Lightning-AI/lightning/pull/15721))
-- Apps without UIs no longer activate the "Open App" button when running in the cloud ([#15875](https://github.com/Lightning-AI/lightning/pull/15875))
-- Wait for full file to be transferred in Path / Payload ([#15934](https://github.com/Lightning-AI/lightning/pull/15934))
-
-### Removed
-
-- Removed the `SingleProcessRuntime` ([#15933](https://github.com/Lightning-AI/lightning/pull/15933))
-
-### Fixed
-
-- Fixed SSH CLI command listing stopped components ([#15810](https://github.com/Lightning-AI/lightning/pull/15810))
-- Fixed bug when launching apps on multiple clusters ([#15484](https://github.com/Lightning-AI/lightning/pull/15484))
-- Fixed Sigterm Handler causing thread lock which caused KeyboardInterrupt to hang ([#15881](https://github.com/Lightning-AI/lightning/pull/15881))
-- Fixed MPS error for multinode component (defaults to cpu on mps devices now as distributed operations are not supported by pytorch on mps) ([#15748](https://github.com/Lightning-AI/lightning/pull/15748))
-- Fixed the work not stopped when successful when passed directly to the LightningApp ([#15801](https://github.com/Lightning-AI/lightning/pull/15801))
-- Fixed the PyTorch Inference locally on GPU ([#15813](https://github.com/Lightning-AI/lightning/pull/15813))
-- Fixed the `enable_spawn` method of the `WorkRunExecutor` ([#15812](https://github.com/Lightning-AI/lightning/pull/15812))
-- Fixed require/import decorator ([#15849](https://github.com/Lightning-AI/lightning/pull/15849))
-- Fixed a bug where using `L.app.structures` would cause multiple apps to be opened and fail with an error in the cloud ([#15911](https://github.com/Lightning-AI/lightning/pull/15911))
-- Fixed PythonServer generating noise on M1 ([#15949](https://github.com/Lightning-AI/lightning/pull/15949))
-- Fixed multiprocessing breakpoint ([#15950](https://github.com/Lightning-AI/lightning/pull/15950))
-- Fixed detection of a Lightning App running in debug mode ([#15951](https://github.com/Lightning-AI/lightning/pull/15951))
-- Fixed `ImportError` on Multinode if package not present ([#15963](https://github.com/Lightning-AI/lightning/pull/15963))
-- Fixed MultiNode Component to use separate cloud computes ([#15965](https://github.com/Lightning-AI/lightning/pull/15965))
-- Fixed Registration for CloudComputes of Works in `L.app.structures` ([#15964](https://github.com/Lightning-AI/lightning/pull/15964))
-- Fixed a bug where auto-upgrading to the latest lightning via the CLI could get stuck in a loop ([#15984](https://github.com/Lightning-AI/lightning/pull/15984))
-
-
-## [1.8.3] - 2022-11-22
-
-### Changed
-
-- Deduplicate top level lightning CLI command groups ([#15761](https://github.com/Lightning-AI/lightning/pull/15761))
- * `lightning add ssh-key` CLI command has been transitioned to `lightning create ssh-key`
- * `lightning remove ssh-key` CLI command has been transitioned to `lightning delete ssh-key`
-- Set Torch inference mode for prediction ([#15719](https://github.com/Lightning-AI/lightning/pull/15719))
-- Improved `LightningTrainerScript` start-up time ([#15751](https://github.com/Lightning-AI/lightning/pull/15751))
-- Disable XSRF protection in `StreamlitFrontend` to support upload in localhost ([#15684](https://github.com/Lightning-AI/lightning/pull/15684))
-
-### Fixed
-
-- Fixed debugging with VSCode IDE ([#15747](https://github.com/Lightning-AI/lightning/pull/15747))
-- Fixed setting property to the `LightningFlow` ([#15750](https://github.com/Lightning-AI/lightning/pull/15750))
-- Fixed the PyTorch Inference locally on GPU ([#15813](https://github.com/Lightning-AI/lightning/pull/15813))
-
-
-## [1.8.2] - 2022-11-17
-
-### Added
-
-- Added title and description to ServeGradio ([#15639](https://github.com/Lightning-AI/lightning/pull/15639))
-- Added a friendly error message when attempting to run the default cloud compute with a custom base image configured ([#14929](https://github.com/Lightning-AI/lightning/pull/14929))
-
-### Changed
-
-- Improved support for running apps when dependencies aren't installed ([#15711](https://github.com/Lightning-AI/lightning/pull/15711))
-- Changed the root directory of the app (which gets uploaded) to be the folder containing the app file, rather than any parent folder containing a `.lightning` file ([#15654](https://github.com/Lightning-AI/lightning/pull/15654))
-- Enabled MultiNode Components to support state broadcasting ([#15607](https://github.com/Lightning-AI/lightning/pull/15607))
-- Prevent artefactual "running from outside your current environment" error ([#15647](https://github.com/Lightning-AI/lightning/pull/15647))
-- Rename failed -> error in tables ([#15608](https://github.com/Lightning-AI/lightning/pull/15608))
-
-### Fixed
-
-- Fixed race condition to over-write the frontend with app infos ([#15398](https://github.com/Lightning-AI/lightning/pull/15398))
-- Fixed bi-directional queues sending delta with Drive Component name changes ([#15642](https://github.com/Lightning-AI/lightning/pull/15642))
-- Fixed CloudRuntime works collection with structures and accelerated multi node startup time ([#15650](https://github.com/Lightning-AI/lightning/pull/15650))
-- Fixed catimage import ([#15712](https://github.com/Lightning-AI/lightning/pull/15712))
-- Parse all lines in app file looking for shebangs to run commands ([#15714](https://github.com/Lightning-AI/lightning/pull/15714))
-
-
-## [1.8.1] - 2022-11-10
-
-### Added
-
-- Added the `start` method to the work ([#15523](https://github.com/Lightning-AI/lightning/pull/15523))
-- Added a `MultiNode` Component to run with distributed computation with any frameworks ([#15524](https://github.com/Lightning-AI/lightning/pull/15524))
-- Expose `RunWorkExecutor` to the work and provides default ones for the `MultiNode` Component ([#15561](https://github.com/Lightning-AI/lightning/pull/15561))
-- Added a `start_with_flow` flag to the `LightningWork` which can be disabled to prevent the work from starting at the same time as the flow ([#15591](https://github.com/Lightning-AI/lightning/pull/15591))
-- Added support for running Lightning App with VSCode IDE debugger ([#15590](https://github.com/Lightning-AI/lightning/pull/15590))
-- Added `bi-directional` delta updates between the flow and the works ([#15582](https://github.com/Lightning-AI/lightning/pull/15582))
-- Added `--setup` flag to `lightning run app` CLI command allowing for dependency installation via app comments ([#15577](https://github.com/Lightning-AI/lightning/pull/15577))
-- Auto-upgrade / detect environment mismatch from the CLI ([#15434](https://github.com/Lightning-AI/lightning/pull/15434))
-- Added Serve component ([#15609](https://github.com/Lightning-AI/lightning/pull/15609))
-
-
-### Changed
-
-- Changed the `flow.flows` to be recursive won't to align the behavior with the `flow.works` ([#15466](https://github.com/Lightning-AI/lightning/pull/15466))
-- The `params` argument in `TracerPythonScript.run` no longer prepends `--` automatically to parameters ([#15518](https://github.com/Lightning-AI/lightning/pull/15518))
-- Only check versions / env when not in the cloud ([#15504](https://github.com/Lightning-AI/lightning/pull/15504))
-- Periodically sync database to the drive ([#15441](https://github.com/Lightning-AI/lightning/pull/15441))
-- Slightly safer multi node ([#15538](https://github.com/Lightning-AI/lightning/pull/15538))
-- Reuse existing commands when running connect more than once ([#15471](https://github.com/Lightning-AI/lightning/pull/15471))
-
-### Fixed
-
-- Fixed writing app name and id in connect.txt file for the command CLI ([#15443](https://github.com/Lightning-AI/lightning/pull/15443))
-- Fixed missing root flow among the flows of the app ([#15531](https://github.com/Lightning-AI/lightning/pull/15531))
-- Fixed bug with Multi Node Component and add some examples ([#15557](https://github.com/Lightning-AI/lightning/pull/15557))
-- Fixed a bug where payload would take a very long time locally ([#15557](https://github.com/Lightning-AI/lightning/pull/15557))
-- Fixed an issue with the `lightning` CLI taking a long time to error out when the cloud is not reachable ([#15412](https://github.com/Lightning-AI/lightning/pull/15412))
-
-
-## [1.8.0] - 2022-11-01
-
-### Added
-
-- Added `load_state_dict` and `state_dict` hooks for `LightningFlow` components ([#14100](https://github.com/Lightning-AI/lightning/pull/14100))
-- Added a `--secret` option to CLI to allow binding secrets to app environment variables when running in the cloud ([#14612](https://github.com/Lightning-AI/lightning/pull/14612))
-- Added support for running the works without cloud compute in the default container ([#14819](https://github.com/Lightning-AI/lightning/pull/14819))
-- Added an HTTPQueue as an optional replacement for the default redis queue ([#14978](https://github.com/Lightning-AI/lightning/pull/14978)
-- Added support for configuring flow cloud compute ([#14831](https://github.com/Lightning-AI/lightning/pull/14831))
-- Added support for adding descriptions to commands either through a docstring or the `DESCRIPTION` attribute ([#15193](https://github.com/Lightning-AI/lightning/pull/15193)
-- Added a try / catch mechanism around request processing to avoid killing the flow ([#15187](https://github.com/Lightning-AI/lightning/pull/15187)
-- Added an Database Component ([#14995](https://github.com/Lightning-AI/lightning/pull/14995)
-- Added authentication to HTTP queue ([#15202](https://github.com/Lightning-AI/lightning/pull/15202))
-- Added support to pass a `LightningWork` to the `LightningApp` ([#15215](https://github.com/Lightning-AI/lightning/pull/15215)
-- Added support getting CLI help for connected apps even if the app isn't running ([#15196](https://github.com/Lightning-AI/lightning/pull/15196)
-- Added support for adding requirements to commands and installing them when missing when running an app command ([#15198](https://github.com/Lightning-AI/lightning/pull/15198)
-- Added Lightning CLI Connection to be terminal session instead of global ([#15241](https://github.com/Lightning-AI/lightning/pull/15241)
-- Added support for managing SSH-keys via CLI ([#15291](https://github.com/Lightning-AI/lightning/pull/15291))
-- Add a `JustPyFrontend` to ease UI creation with `https://github.com/justpy-org/justpy` ([#15002](https://github.com/Lightning-AI/lightning/pull/15002))
-- Added a layout endpoint to the Rest API and enable to disable pulling or pushing to the state ([#15367](https://github.com/Lightning-AI/lightning/pull/15367)
-- Added support for functions for `configure_api` and `configure_commands` to be executed in the Rest API process ([#15098](https://github.com/Lightning-AI/lightning/pull/15098)
-- Added support for accessing Lightning Apps via SSH ([#15310](https://github.com/Lightning-AI/lightning/pull/15310))
-- Added support to start lightning app on cloud without needing to install dependencies locally ([#15019](https://github.com/Lightning-AI/lightning/pull/15019)
-
-### Changed
-
-- Improved the show logs command to be standalone and reusable ([#15343](https://github.com/Lightning-AI/lightning/pull/15343)
-- Removed the `--instance-types` option when creating clusters ([#15314](https://github.com/Lightning-AI/lightning/pull/15314))
-
-### Fixed
-
-- Fixed an issue when using the CLI without arguments ([#14877](https://github.com/Lightning-AI/lightning/pull/14877))
-- Fixed a bug where the upload files endpoint would raise an error when running locally ([#14924](https://github.com/Lightning-AI/lightning/pull/14924))
-- Fixed BYOC cluster region selector -> hiding it from help since only us-east-1 has been tested and is recommended ([#15277]https://github.com/Lightning-AI/lightning/pull/15277)
-- Fixed a bug when launching an app on multiple clusters ([#15226](https://github.com/Lightning-AI/lightning/pull/15226))
-- Fixed a bug with a default CloudCompute for Lightning flows ([#15371](https://github.com/Lightning-AI/lightning/pull/15371))
-
-## [0.6.2] - 2022-09-21
-
-### Changed
-
-- Improved Lightning App connect logic by disconnecting automatically ([#14532](https://github.com/Lightning-AI/lightning/pull/14532))
-- Improved the error message when the `LightningWork` is missing the `run` method ([#14759](https://github.com/Lightning-AI/lightning/pull/14759))
-- Improved the error message when the root `LightningFlow` passed to `LightningApp` is missing the `run` method ([#14760](https://github.com/Lightning-AI/lightning/pull/14760))
-
-### Fixed
-
-- Fixed a bug where the uploaded command file wasn't properly parsed ([#14532](https://github.com/Lightning-AI/lightning/pull/14532))
-- Fixed an issue where custom property setters were not being used `LightningWork` class ([#14259](https://github.com/Lightning-AI/lightning/pull/14259))
-- Fixed an issue where some terminals would display broken icons in the PL app CLI ([#14226](https://github.com/Lightning-AI/lightning/pull/14226))
-
-
-## [0.6.1] - 2022-09-19
-
-### Added
-
-- Add support to upload files to the Drive through an asynchronous `upload_file` endpoint ([#14703](https://github.com/Lightning-AI/lightning/pull/14703))
-
-### Changed
-
-- Application storage prefix moved from `app_id` to `project_id/app_id` ([#14583](https://github.com/Lightning-AI/lightning/pull/14583))
-- LightningCloud client calls to use keyword arguments instead of positional arguments ([#14685](https://github.com/Lightning-AI/lightning/pull/14685))
-
-### Fixed
-
-- Making `threadpool` non-default from LightningCloud client ([#14757](https://github.com/Lightning-AI/lightning/pull/14757))
-- Resolved a bug where the state change detection using DeepDiff won't work with Path, Drive objects ([#14465](https://github.com/Lightning-AI/lightning/pull/14465))
-- Resolved a bug where the wrong client was passed to collect cloud logs ([#14684](https://github.com/Lightning-AI/lightning/pull/14684))
-- Resolved the memory leak issue with the Lightning Cloud package and bumped the requirements to use the latest version ([#14697](https://github.com/Lightning-AI/lightning/pull/14697))
-- Fixing 5000 log line limitation for Lightning AI BYOC cluster logs ([#14458](https://github.com/Lightning-AI/lightning/pull/14458))
-- Fixed a bug where the uploaded command file wasn't properly parsed ([#14532](https://github.com/Lightning-AI/lightning/pull/14532))
-- Resolved `LightningApp(..., debug=True)` ([#14464](https://github.com/Lightning-AI/lightning/pull/14464))
-
-
-## [0.6.0] - 2022-09-08
-
-### Added
-
-- Introduce lightning connect ([#14452](https://github.com/Lightning-AI/lightning/pull/14452))
-- Adds `PanelFrontend` to easily create complex UI in Python ([#13531](https://github.com/Lightning-AI/lightning/pull/13531))
-- Add support for `Lightning App Commands` through the `configure_commands` hook on the Lightning Flow and the `ClientCommand` ([#13602](https://github.com/Lightning-AI/lightning/pull/13602))
-- Add support for Lightning AI BYOC cluster management ([#13835](https://github.com/Lightning-AI/lightning/pull/13835))
-- Add support to see Lightning AI BYOC cluster logs ([#14334](https://github.com/Lightning-AI/lightning/pull/14334))
-- Add support to run Lightning apps on Lightning AI BYOC clusters ([#13894](https://github.com/Lightning-AI/lightning/pull/13894))
-- Add support for listing Lightning AI apps ([#13987](https://github.com/Lightning-AI/lightning/pull/13987))
-- Adds `LightningTrainerScript`. `LightningTrainerScript` orchestrates multi-node training in the cloud ([#13830](https://github.com/Lightning-AI/lightning/pull/13830))
-- Add support for printing application logs using CLI `lightning show logs [components]` ([#13634](https://github.com/Lightning-AI/lightning/pull/13634))
-- Add support for `Lightning API` through the `configure_api` hook on the Lightning Flow and the `Post`, `Get`, `Delete`, `Put` HttpMethods ([#13945](https://github.com/Lightning-AI/lightning/pull/13945))
-- Added a warning when `configure_layout` returns URLs configured with http instead of https ([#14233](https://github.com/Lightning-AI/lightning/pull/14233))
-- Add `--app_args` support from the CLI ([#13625](https://github.com/Lightning-AI/lightning/pull/13625))
-
-### Changed
-
-- Default values and parameter names for Lightning AI BYOC cluster management ([#14132](https://github.com/Lightning-AI/lightning/pull/14132))
-- Run the flow only if the state has changed from the previous execution ([#14076](https://github.com/Lightning-AI/lightning/pull/14076))
-- Increased DeepDiff's verbose level to properly handle dict changes ([#13960](https://github.com/Lightning-AI/lightning/pull/13960))
-- Setup: added requirement freeze for next major version ([#14480](https://github.com/Lightning-AI/lightning/pull/14480))
-
-### Fixed
-
-- Unification of app template: moved `app.py` to root dir for `lightning init app ` template ([#13853](https://github.com/Lightning-AI/lightning/pull/13853))
-- Fixed an issue with `lightning --version` command ([#14433](https://github.com/Lightning-AI/lightning/pull/14433))
-- Fixed imports of collections.abc for py3.10 ([#14345](https://github.com/Lightning-AI/lightning/pull/14345))
-
-## [0.5.7] - 2022-08-22
-
-### Changed
-
-- Release LAI docs as stable ([#14250](https://github.com/Lightning-AI/lightning/pull/14250))
-- Compatibility for Python 3.10
-
-### Fixed
-
-- Pinning starsessions to 1.x ([#14333](https://github.com/Lightning-AI/lightning/pull/14333))
-- Parsed local package versions ([#13933](https://github.com/Lightning-AI/lightning/pull/13933))
-
-
-## [0.5.6] - 2022-08-16
-
-### Fixed
-
-- Resolved a bug where the `install` command was not installing the latest version of an app/component by default ([#14181](https://github.com/Lightning-AI/lightning/pull/14181))
-
-
-- Fixed the `examples/app_dag` example ([#14359](https://github.com/Lightning-AI/lightning/pull/14359))
-
-
-## [0.5.5] - 2022-08-9
-
-### Deprecated
-
-- Deprecate sheety API ([#14004](https://github.com/Lightning-AI/lightning/pull/14004))
-
-### Fixed
-
-- Resolved a bug where the work statuses will grow quickly and be duplicated ([#13970](https://github.com/Lightning-AI/lightning/pull/13970))
-- Resolved a bug about a race condition when sending the work state through the caller_queue ([#14074](https://github.com/Lightning-AI/lightning/pull/14074))
-- Fixed Start Lightning App on Cloud if Repo Begins With Name "Lightning" ([#14025](https://github.com/Lightning-AI/lightning/pull/14025))
-
-
-## [0.5.4] - 2022-08-01
-
-### Changed
-
-- Wrapped imports for traceability ([#13924](https://github.com/Lightning-AI/lightning/pull/13924))
-- Set version as today ([#13906](https://github.com/Lightning-AI/lightning/pull/13906))
-
-### Fixed
-
-- Included app templates to the lightning and app packages ([#13731](https://github.com/Lightning-AI/lightning/pull/13731))
-- Added UI for install all ([#13732](https://github.com/Lightning-AI/lightning/pull/13732))
-- Fixed build meta pkg flow ([#13926](https://github.com/Lightning-AI/lightning/pull/13926))
-
-## [0.5.3] - 2022-07-25
-
-### Changed
-
-- Pruned requirements duplicity ([#13739](https://github.com/Lightning-AI/lightning/pull/13739))
-
-### Fixed
-
-- Use correct python version in lightning component template ([#13790](https://github.com/Lightning-AI/lightning/pull/13790))
-
-## [0.5.2] - 2022-07-18
-
-### Added
-
-- Update the Lightning App docs ([#13537](https://github.com/Lightning-AI/lightning/pull/13537))
-
-### Changed
-
-- Added `LIGHTNING_` prefix to Platform AWS credentials ([#13703](https://github.com/Lightning-AI/lightning/pull/13703))
diff --git a/src/lightning/app/__init__.py b/src/lightning/app/__init__.py
deleted file mode 100644
index 5c904cc4a908c..0000000000000
--- a/src/lightning/app/__init__.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Root package info."""
-
-import logging
-import os
-
-from lightning_utilities.core.imports import module_available, package_available
-
-_root_logger = logging.getLogger()
-_logger = logging.getLogger(__name__)
-_logger.setLevel(logging.INFO)
-
-_console = logging.StreamHandler()
-_console.setLevel(logging.INFO)
-
-formatter = logging.Formatter("%(levelname)s: %(message)s")
-_console.setFormatter(formatter)
-
-# if root logger has handlers, propagate messages up and let root logger process them,
-# otherwise use our own handler
-if not _root_logger.hasHandlers():
- _logger.addHandler(_console)
- _logger.propagate = False
-
-
-if os.path.isfile(os.path.join(os.path.dirname(__file__), "__about__.py")):
- from lightning.app.__about__ import * # noqa: F403
-if "__version__" not in locals():
- if os.path.isfile(os.path.join(os.path.dirname(__file__), "__version__.py")):
- from lightning.app.__version__ import version as __version__
- elif package_available("lightning"):
- from lightning import __version__ # noqa: F401
-
-from lightning.app.core.app import LightningApp # noqa: E402
-from lightning.app.core.flow import LightningFlow # noqa: E402
-from lightning.app.core.work import LightningWork # noqa: E402
-from lightning.app.plugin.plugin import LightningPlugin # noqa: E402
-from lightning.app.utilities.packaging.build_config import BuildConfig # noqa: E402
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute # noqa: E402
-
-if module_available("lightning.app.components.demo"):
- from lightning.app.components import demo # noqa: F401
-
-__package_name__ = "lightning.app".split(".")[0]
-
-_PACKAGE_ROOT = os.path.dirname(__file__)
-_PROJECT_ROOT = os.path.dirname(os.path.dirname(_PACKAGE_ROOT))
-if __package_name__ == "lightning":
- _PACKAGE_ROOT = os.path.dirname(_PACKAGE_ROOT)
- _PROJECT_ROOT = os.path.dirname(_PROJECT_ROOT)
-
-__all__ = ["LightningApp", "LightningFlow", "LightningWork", "LightningPlugin", "BuildConfig", "CloudCompute"]
diff --git a/src/lightning/app/api/__init__.py b/src/lightning/app/api/__init__.py
deleted file mode 100644
index d850e874da5a2..0000000000000
--- a/src/lightning/app/api/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from lightning.app.api.http_methods import Delete, Get, Post, Put
-
-__all__ = [
- "Delete",
- "Get",
- "Post",
- "Put",
-]
diff --git a/src/lightning/app/api/http_methods.py b/src/lightning/app/api/http_methods.py
deleted file mode 100644
index aa9e68528e487..0000000000000
--- a/src/lightning/app/api/http_methods.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import inspect
-import time
-from copy import deepcopy
-from dataclasses import dataclass
-from functools import wraps
-from multiprocessing import Queue
-from typing import Any, Callable, Dict, List, Optional
-from uuid import uuid4
-
-from fastapi import FastAPI, HTTPException, Request, status
-from lightning_utilities.core.apply_func import apply_to_collection
-
-from lightning.app.api.request_types import _APIRequest, _CommandRequest, _RequestResponse
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-def _signature_proxy_function():
- pass
-
-
-@dataclass
-class _FastApiMockRequest:
- """This class is meant to mock FastAPI Request class that isn't pickle-able.
-
- If a user relies on FastAPI Request annotation, the Lightning framework
- patches the annotation before pickling and replace them right after.
-
- Finally, the FastAPI request is converted back to the _FastApiMockRequest
- before being delivered to the users.
-
- Example:
-
- from lightning.app import LightningFlow
- from fastapi import Request
- from lightning.app.api import Post
-
- class Flow(LightningFlow):
-
- def request(self, request: Request) -> OutputRequestModel:
- ...
-
- def configure_api(self):
- return [Post("/api/v1/request", self.request)]
-
- """
-
- _body: Optional[str] = None
- _json: Optional[str] = None
- _method: Optional[str] = None
- _headers: Optional[Dict] = None
-
- @property
- def receive(self):
- raise NotImplementedError
-
- @property
- def method(self):
- return self._method
-
- @property
- def headers(self):
- return self._headers
-
- def body(self):
- return self._body
-
- def json(self):
- return self._json
-
- def stream(self):
- raise NotImplementedError
-
- def form(self):
- raise NotImplementedError
-
- def close(self):
- raise NotImplementedError
-
- def is_disconnected(self):
- raise NotImplementedError
-
-
-async def _mock_fastapi_request(request: Request):
- # TODO: Add more requests parameters.
- return _FastApiMockRequest(
- _body=await request.body(),
- _json=await request.json(),
- _headers=request.headers,
- _method=request.method,
- )
-
-
-class _HttpMethod:
- def __init__(
- self, route: str, method: Callable, method_name: Optional[str] = None, timeout: int = 30, **kwargs: Any
- ):
- """This class is used to inject user defined methods within the App Rest API.
-
- Arguments:
- route: The path used to route the requests
- method: The associated flow method
- timeout: The time in seconds taken before raising a timeout exception.
-
- """
- self.route = route
- self.attached_to_flow = hasattr(method, "__self__")
- self.method_name = method_name or method.__name__
- self.method_annotations = method.__annotations__
- # TODO: Validate the signature contains only pydantic models.
- self.method_signature = inspect.signature(method)
-
- if not self.attached_to_flow:
- self.component_name = method.__name__
- self.method = method
- else:
- self.component_name = method.__self__.name
-
- self.timeout = timeout
- self.kwargs = kwargs
-
- # Enable the users to rely on FastAPI annotation typing with Request.
- # Note: Only a part of the Request functionatilities are supported.
- self._patch_fast_api_request()
-
- def add_route(self, app: FastAPI, request_queue: Queue, responses_store: Dict[str, Any]) -> None:
- # 1: Get the route associated with the http method.
- route = getattr(app, self.__class__.__name__.lower())
-
- self._unpatch_fast_api_request()
-
- # 2: Create a proxy function with the signature of the wrapped method.
- fn = deepcopy(_signature_proxy_function)
- fn.__annotations__ = self.method_annotations
- fn.__name__ = self.method_name
- setattr(fn, "__signature__", self.method_signature)
-
- # Note: Handle requests differently if attached to a flow.
- if not self.attached_to_flow:
- # 3: Define the request handler.
- @wraps(_signature_proxy_function)
- async def _handle_request(*args: Any, **kwargs: Any):
- if inspect.iscoroutinefunction(self.method):
- return await self.method(*args, **kwargs)
- return self.method(*args, **kwargs)
-
- else:
- request_cls = _CommandRequest if self.route.startswith("/command/") else _APIRequest
-
- # 3: Define the request handler.
- @wraps(_signature_proxy_function)
- async def _handle_request(*args: Any, **kwargs: Any):
- async def fn(*args: Any, **kwargs: Any):
- args, kwargs = apply_to_collection((args, kwargs), Request, _mock_fastapi_request)
- for k, v in kwargs.items():
- if hasattr(v, "__await__"):
- kwargs[k] = await v
-
- request_id = str(uuid4()).split("-")[0]
- logger.debug(f"Processing request {request_id} for route: {self.route}")
- request_queue.put(
- request_cls(
- name=self.component_name,
- method_name=self.method_name,
- args=args,
- kwargs=kwargs,
- id=request_id,
- )
- )
-
- t0 = time.time()
- while request_id not in responses_store:
- await asyncio.sleep(0.01)
- if (time.time() - t0) > self.timeout:
- raise HTTPException(
- status.HTTP_500_INTERNAL_SERVER_ERROR,
- detail="The response was never received.",
- )
-
- logger.debug(f"Processed request {request_id} for route: {self.route}")
-
- return responses_store.pop(request_id)
-
- response: _RequestResponse = await asyncio.create_task(fn(*args, **kwargs))
-
- if response.status_code != 200:
- raise HTTPException(response.status_code, detail=response.content)
-
- return response.content
-
- # 4: Register the user provided route to the Rest API.
- route(self.route, **self.kwargs)(_handle_request)
-
- def _patch_fast_api_request(self):
- """This function replaces signature annotation for Request with its mock."""
- for k, v in self.method_annotations.items():
- if v == Request:
- self.method_annotations[k] = _FastApiMockRequest
-
- for v in self.method_signature.parameters.values():
- if v._annotation == Request:
- v._annotation = _FastApiMockRequest
-
- def _unpatch_fast_api_request(self):
- """This function replaces back signature annotation to fastapi Request."""
- for k, v in self.method_annotations.items():
- if v == _FastApiMockRequest:
- self.method_annotations[k] = Request
-
- for v in self.method_signature.parameters.values():
- if v._annotation == _FastApiMockRequest:
- v._annotation = Request
-
-
-class Post(_HttpMethod):
- pass
-
-
-class Get(_HttpMethod):
- pass
-
-
-class Put(_HttpMethod):
- pass
-
-
-class Delete(_HttpMethod):
- pass
-
-
-def _add_tags_to_api(apis: List[_HttpMethod], tags: List[str]) -> None:
- for api in apis:
- if not api.kwargs.get("tag"):
- api.kwargs["tags"] = tags
-
-
-def _validate_api(apis: List[_HttpMethod]) -> None:
- for api in apis:
- if not isinstance(api, _HttpMethod):
- raise Exception(f"The provided api should be either [{Delete}, {Get}, {Post}, {Put}]")
- if api.route.startswith("/command"):
- raise Exception("The route `/command` is reserved for commands. Please, use something else.")
diff --git a/src/lightning/app/api/request_types.py b/src/lightning/app/api/request_types.py
deleted file mode 100644
index def50e3a20e10..0000000000000
--- a/src/lightning/app/api/request_types.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dataclasses import asdict, dataclass
-from typing import Any, Optional
-
-from deepdiff import Delta
-
-
-@dataclass
-class _BaseRequest:
- def to_dict(self):
- return asdict(self)
-
-
-@dataclass
-class _DeltaRequest(_BaseRequest):
- delta: Delta
-
- def to_dict(self):
- return self.delta.to_dict()
-
-
-@dataclass
-class _CommandRequest(_BaseRequest):
- id: str
- name: str
- method_name: str
- args: Any
- kwargs: Any
-
-
-@dataclass
-class _APIRequest(_BaseRequest):
- id: str
- name: str
- method_name: str
- args: Any
- kwargs: Any
-
-
-@dataclass
-class _RequestResponse(_BaseRequest):
- status_code: int
- content: Optional[str] = None
diff --git a/src/lightning/app/cli/__init__.py b/src/lightning/app/cli/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/app-template/.gitignore b/src/lightning/app/cli/app-template/.gitignore
deleted file mode 100644
index 70ba25888435f..0000000000000
--- a/src/lightning/app/cli/app-template/.gitignore
+++ /dev/null
@@ -1,157 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-*install-app*
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-
-*.egg
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Sphinx documentation
-docs/_build/
-docs/source/api/
-docs/source/*.md
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.local_env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# PyCharm
-.idea/
-
-# Lightning logs
-lightning_logs
-*.gz
-.DS_Store
-.*_submit.py
-.vscode
-
-MNIST
-*.pt
-.storage/
-.shared/
-infra
-data
-coverage.*
-# Frontend build artifacts
-*lightning/app/ui*
-gradio_cached_examples
-/docs/source/api_reference/generated/*
-examples/my_own_leaderboard/submissions/*
-docs/source/api_reference/generated/*
-*.ckpt
-redis-stable
-node_modules
-*.rdb
-*.webm
-*hars
-examples/quick_start/*
-examples/quick_start
-examples/template_react_ui/*
-examples/template_react_ui
-# Ignore external components
-lightning/app/components/*
-!lightning/app/components/python
-!lightning/app/components/serve
-!lightning/app/components/__init__.py
-!lightning/app/components/README.md
-train_script.py
-*return_values*
-scratch
-storage
diff --git a/src/lightning/app/cli/app-template/LICENSE b/src/lightning/app/cli/app-template/LICENSE
deleted file mode 100644
index 261eeb9e9f8b2..0000000000000
--- a/src/lightning/app/cli/app-template/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/src/lightning/app/cli/app-template/README.md b/src/lightning/app/cli/app-template/README.md
deleted file mode 100644
index 76c88e6cedb38..0000000000000
--- a/src/lightning/app/cli/app-template/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# placeholdername app
-
-This ⚡ [Lightning app](https://lightning.ai/) ⚡ was generated automatically with:
-
-```bash
-lightning_app init app placeholdername
-```
-
-## To run placeholdername
-
-First, install placeholdername (warning: this app has not been officially approved on the lightning gallery):
-
-```bash
-lightning_app install app https://github.com/theUser/placeholdername
-```
-
-Once the app is installed, run it locally with:
-
-```bash
-lightning_app run app placeholdername/app.py
-```
-
-Run it on the [lightning cloud](https://lightning.ai/) with:
-
-```bash
-lightning_app run app placeholdername/app.py --cloud
-```
-
-## to test and link
-
-Run flake to make sure all your styling is consistent (it keeps your team from going insane)
-
-```bash
-flake8 .
-```
-
-To test, follow the README.md instructions in the tests folder.
diff --git a/src/lightning/app/cli/app-template/app.py b/src/lightning/app/cli/app-template/app.py
deleted file mode 100644
index 4b86551324ccc..0000000000000
--- a/src/lightning/app/cli/app-template/app.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from lightning.app import LightningApp, LightningFlow
-from placeholdername import ComponentA, ComponentB
-
-
-class LitApp(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.component_a = ComponentA()
- self.component_b = ComponentB()
-
- def run(self):
- self.component_a.run()
- self.component_b.run()
-
-
-app = LightningApp(LitApp())
diff --git a/src/lightning/app/cli/app-template/placeholdername/__init__.py b/src/lightning/app/cli/app-template/placeholdername/__init__.py
deleted file mode 100644
index cf954823e0315..0000000000000
--- a/src/lightning/app/cli/app-template/placeholdername/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from placeholdername.components.component_a import ComponentA
-from placeholdername.components.component_b import ComponentB
-
-__all__ = ["ComponentA", "ComponentB"]
diff --git a/src/lightning/app/cli/app-template/placeholdername/components/component_a/__init__.py b/src/lightning/app/cli/app-template/placeholdername/components/component_a/__init__.py
deleted file mode 100644
index 82753954e0e03..0000000000000
--- a/src/lightning/app/cli/app-template/placeholdername/components/component_a/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from placeholdername.components.component_a.component_a import ComponentA
-
-__all__ = ["ComponentA"]
diff --git a/src/lightning/app/cli/app-template/placeholdername/components/component_a/component_a.py b/src/lightning/app/cli/app-template/placeholdername/components/component_a/component_a.py
deleted file mode 100644
index e11ff40c299db..0000000000000
--- a/src/lightning/app/cli/app-template/placeholdername/components/component_a/component_a.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from lightning.app import LightningFlow
-
-
-class ComponentA(LightningFlow):
- def run(self):
- print("hello from component A")
diff --git a/src/lightning/app/cli/app-template/placeholdername/components/component_b/__init__.py b/src/lightning/app/cli/app-template/placeholdername/components/component_b/__init__.py
deleted file mode 100644
index 876454576ad90..0000000000000
--- a/src/lightning/app/cli/app-template/placeholdername/components/component_b/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from placeholdername.components.component_b.component_a import ComponentB
-
-__all__ = ["ComponentB"]
diff --git a/src/lightning/app/cli/app-template/placeholdername/components/component_b/component_a.py b/src/lightning/app/cli/app-template/placeholdername/components/component_b/component_a.py
deleted file mode 100644
index d80505d986026..0000000000000
--- a/src/lightning/app/cli/app-template/placeholdername/components/component_b/component_a.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from lightning.app import LightningFlow
-
-
-class ComponentB(LightningFlow):
- def run(self):
- print("hello from component B")
diff --git a/src/lightning/app/cli/app-template/requirements.txt b/src/lightning/app/cli/app-template/requirements.txt
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/app-template/setup.py b/src/lightning/app/cli/app-template/setup.py
deleted file mode 100644
index c398ca985f759..0000000000000
--- a/src/lightning/app/cli/app-template/setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python
-
-from setuptools import find_packages, setup
-
-setup(
- name="placeholdername",
- version="0.0.0",
- description="⚡ Lightning app ⚡ generated with command: lightning init app",
- author="",
- author_email="",
- # REPLACE WITH YOUR OWN GITHUB PROJECT LINK
- url="https://github.com/Lightning-AI/lightning-app-template",
- install_requires=[],
- packages=find_packages(),
-)
diff --git a/src/lightning/app/cli/app-template/tests/README.md b/src/lightning/app/cli/app-template/tests/README.md
deleted file mode 100644
index 85e8c7faa08f9..0000000000000
--- a/src/lightning/app/cli/app-template/tests/README.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Run tests
-
-To run the tests:
-
-```bash
-# go to your app folder
-cd placeholdername
-
-# go to tests folder
-cd tests
-
-# install testing deps
-pip install -r requirements.txt
-
-# run tests
-pytest .
-```
diff --git a/src/lightning/app/cli/app-template/tests/__init__.py b/src/lightning/app/cli/app-template/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/app-template/tests/requirements.txt b/src/lightning/app/cli/app-template/tests/requirements.txt
deleted file mode 100644
index 3185d1c44f033..0000000000000
--- a/src/lightning/app/cli/app-template/tests/requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-coverage
-codecov>=2.1
-pytest>=5.0.0
-pytest-cov
-pytest-flake8
-flake8
-check-manifest
-twine==4.0.1
diff --git a/src/lightning/app/cli/app-template/tests/test_placeholdername_app.py b/src/lightning/app/cli/app-template/tests/test_placeholdername_app.py
deleted file mode 100644
index 6c7743b93ce1e..0000000000000
--- a/src/lightning/app/cli/app-template/tests/test_placeholdername_app.py
+++ /dev/null
@@ -1,44 +0,0 @@
-r"""
-To test a lightning app:
-1. Use LightningTestApp which is a subclass of LightningApp.
-2. Subclass run_once in LightningTestApp.
-3. in run_once, come up with a way to verify the behavior you wanted.
-
-run_once runs your app through one cycle of the event loop and then terminates
-"""
-
-import io
-import os
-from contextlib import redirect_stdout
-
-from lightning.app.testing.testing import LightningTestApp, application_testing
-
-
-class LightningAppTestInt(LightningTestApp):
- def run_once(self) -> bool:
- f = io.StringIO()
- with redirect_stdout(f):
- super().run_once()
- out = f.getvalue()
- assert out == "hello from component A\nhello from component B\n"
- return True
-
-
-def test_templatename_app():
- start_dir = os.getcwd()
- os.chdir("..")
-
- cwd = os.getcwd()
- cwd = os.path.join(cwd, "placeholdername/app.py")
- command_line = [
- cwd,
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ]
- result = application_testing(LightningAppTestInt, command_line)
- assert result.exit_code == 0
-
- # reset dir
- os.chdir(start_dir)
diff --git a/src/lightning/app/cli/cmd_apps.py b/src/lightning/app/cli/cmd_apps.py
deleted file mode 100644
index d8d7deace2bb4..0000000000000
--- a/src/lightning/app/cli/cmd_apps.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import json
-from datetime import datetime
-from typing import List, Optional
-
-from lightning_cloud.openapi import (
- Externalv1LightningappInstance,
- Externalv1Lightningwork,
- V1LightningappInstanceState,
- V1LightningappInstanceStatus,
-)
-from rich.console import Console
-from rich.table import Table
-from rich.text import Text
-
-from lightning.app.cli.core import Formatable
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.network import LightningClient
-
-
-class _AppManager:
- """_AppManager implements API calls specific to Lightning AI BYOC apps."""
-
- def __init__(self) -> None:
- self.api_client = LightningClient(retry=False)
-
- def get_app(self, app_id: str) -> Externalv1LightningappInstance:
- project = _get_project(self.api_client)
- return self.api_client.lightningapp_instance_service_get_lightningapp_instance(
- project_id=project.project_id, id=app_id
- )
-
- def list_apps(self, limit: int = 100, phase_in: Optional[List[str]] = None) -> List[Externalv1LightningappInstance]:
- phase_in = phase_in or []
- project = _get_project(self.api_client)
-
- kwargs = {
- "project_id": project.project_id,
- "limit": limit,
- "phase_in": phase_in,
- }
-
- resp = self.api_client.lightningapp_instance_service_list_lightningapp_instances(**kwargs)
- apps = resp.lightningapps
- while resp.next_page_token is not None and resp.next_page_token != "":
- kwargs["page_token"] = resp.next_page_token
- resp = self.api_client.lightningapp_instance_service_list_lightningapp_instances(**kwargs)
- apps = apps + resp.lightningapps
- return apps
-
- def list_components(self, app_id: str, phase_in: Optional[List[str]] = None) -> List[Externalv1Lightningwork]:
- phase_in = phase_in or []
- project = _get_project(self.api_client)
- resp = self.api_client.lightningwork_service_list_lightningwork(
- project_id=project.project_id,
- app_id=app_id,
- phase_in=phase_in,
- )
- return resp.lightningworks
-
- def list(self, limit: int = 100) -> None:
- console = Console()
- console.print(_AppList(self.list_apps(limit=limit)).as_table())
-
- def delete(self, app_id: str) -> None:
- project = _get_project(self.api_client)
- self.api_client.lightningapp_instance_service_delete_lightningapp_instance(
- project_id=project.project_id,
- id=app_id,
- )
-
-
-class _AppList(Formatable):
- def __init__(self, apps: List[Externalv1LightningappInstance]) -> None:
- self.apps = apps
-
- @staticmethod
- def _textualize_state_transitions(
- desired_state: V1LightningappInstanceState, current_state: V1LightningappInstanceStatus
- ) -> Text:
- phases = {
- V1LightningappInstanceState.IMAGE_BUILDING: Text("building image", style="bold yellow"),
- V1LightningappInstanceState.PENDING: Text("pending", style="bold yellow"),
- V1LightningappInstanceState.RUNNING: Text("running", style="bold green"),
- V1LightningappInstanceState.FAILED: Text("failed", style="bold red"),
- V1LightningappInstanceState.STOPPED: Text("stopped"),
- V1LightningappInstanceState.NOT_STARTED: Text("not started"),
- V1LightningappInstanceState.DELETED: Text("deleted", style="bold red"),
- V1LightningappInstanceState.UNSPECIFIED: Text("unspecified", style="bold red"),
- }
-
- if current_state.phase == V1LightningappInstanceState.UNSPECIFIED and current_state.start_timestamp is None:
- return Text("not yet started", style="bold yellow")
-
- if (
- desired_state == V1LightningappInstanceState.DELETED
- and current_state.phase != V1LightningappInstanceState.DELETED
- ):
- return Text("terminating", style="bold red")
-
- if (
- any(
- phase == current_state.phase
- for phase in [V1LightningappInstanceState.PENDING, V1LightningappInstanceState.STOPPED]
- )
- and desired_state == V1LightningappInstanceState.RUNNING
- ):
- return Text("restarting", style="bold yellow")
-
- return phases[current_state.phase]
-
- def as_json(self) -> str:
- return json.dumps(self.apps)
-
- def as_table(self) -> Table:
- table = Table("id", "name", "status", "created", show_header=True, header_style="bold green")
-
- for app in self.apps:
- status = self._textualize_state_transitions(desired_state=app.spec.desired_state, current_state=app.status)
-
- # this guard is necessary only until 0.3.93 releases which includes the `created_at`
- # field to the external API
- created_at = datetime.now()
- if hasattr(app, "created_at"):
- created_at = app.created_at
-
- table.add_row(
- app.id,
- app.name,
- status,
- created_at.strftime("%Y-%m-%d") if created_at else "",
- )
- return table
diff --git a/src/lightning/app/cli/cmd_init.py b/src/lightning/app/cli/cmd_init.py
deleted file mode 100644
index db83fd41e47d9..0000000000000
--- a/src/lightning/app/cli/cmd_init.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import re
-import shutil
-from typing import List, Optional, Tuple
-
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-def app(app_name: str) -> None:
- if app_name is None:
- app_name = _capture_valid_app_component_name(resource_type="app")
-
- # generate resource template
- new_resource_name, _ = _make_resource(resource_dir="app-template", resource_name=app_name)
-
- m = f"""
- ⚡ Lightning app template created! ⚡
- {new_resource_name}
-
- run your app with:
- lightning run app {app_name}/app.py
-
- run it on the cloud to share with your collaborators:
- lightning run app {app_name}/app.py --cloud
- """
- logger.info(m)
-
-
-def _make_resource(resource_dir: str, resource_name: str) -> Tuple[str, str]:
- path = os.path.dirname(os.path.abspath(__file__))
- template_dir = os.path.join(path, resource_dir)
- name_for_files = re.sub("-", "_", resource_name)
-
- new_resource_name = os.path.join(os.getcwd(), resource_name)
-
- # lay out scaffolding
- logger.info(f"laying out component template at {new_resource_name}")
- shutil.copytree(template_dir, new_resource_name)
-
- # rename main folder
- os.rename(os.path.join(new_resource_name, "placeholdername"), os.path.join(new_resource_name, name_for_files))
-
- # for each file, rename the word
- trouble_names = {".DS_Store"}
- files = _ls_recursively(new_resource_name)
- for bad_file in files:
- if bad_file.split("/")[-1] in trouble_names:
- continue
- # find the words and replace
- with open(bad_file) as fo:
- content = fo.read().replace("placeholdername", name_for_files)
- with open(bad_file, "w") as fw:
- fw.write(content)
-
- # rename files
- for file_name in files:
- new_file = re.sub("placeholdername", name_for_files, file_name)
- os.rename(file_name, new_file)
-
- return new_resource_name, name_for_files
-
-
-def _ls_recursively(dir_name: str) -> List[str]:
- fname = []
- for root, d_names, f_names in os.walk(dir_name):
- for f in f_names:
- if "__pycache__" not in root:
- fname.append(os.path.join(root, f))
-
- return fname
-
-
-def _capture_valid_app_component_name(value: Optional[str] = None, resource_type: str = "app") -> str:
- prompt = f"""
- ⚡ Creating Lightning {resource_type} ⚡
- """
- logger.info(prompt)
-
- try:
- if value is None:
- value = input(f"\nName your Lightning {resource_type} (example: the-{resource_type}-name) > ")
- value = value.strip().lower()
- unsafe_chars = set(re.findall(r"[^a-z0-9\-]", value))
- if len(unsafe_chars) > 0:
- m = f"""
- Error: your Lightning {resource_type} name:
- {value}
-
- contains the following unsupported characters:
- {unsafe_chars}
-
- A Lightning {resource_type} name can only contain letters (a-z) numbers (0-9) and the '-' character
-
- valid example:
- lightning-{resource_type}
- """
- raise SystemExit(m)
-
- except KeyboardInterrupt:
- raise SystemExit(
- f"""
- ⚡ {resource_type} init aborted! ⚡
- """
- )
-
- return value
-
-
-def component(component_name: str) -> None:
- if component_name is None:
- component_name = _capture_valid_app_component_name(resource_type="component")
-
- # generate resource template
- new_resource_name, name_for_files = _make_resource(resource_dir="component-template", resource_name=component_name)
-
- m = f"""
- ⚡ Lightning component template created! ⚡
- {new_resource_name}
-
- ⚡ To use your component, first pip install it (with these 3 commands): ⚡
- cd {component_name}
- pip install -r requirements.txt
- pip install -e .
-
- ⚡ Use the component inside an app: ⚡
-
- from {name_for_files} import TemplateComponent
- import lightning.app as la
-
- class LitApp(la.LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.{name_for_files} = TemplateComponent()
-
- def run(self):
- print('this is a simple Lightning app to verify your component is working as expected')
- self.{name_for_files}.run()
-
- app = la.LightningApp(LitApp())
-
- ⚡ Checkout the demo app with your {component_name} component: ⚡
- lightning run app {component_name}/app.py
-
- ⚡ Tip: Publish your component to the Lightning Gallery to enable users to install it like so:
- lightning install component YourLightningUserName/{component_name}
-
- so the Lightning community can use it like:
- from {name_for_files} import TemplateComponent
-
- """
- logger.info(m)
diff --git a/src/lightning/app/cli/cmd_install.py b/src/lightning/app/cli/cmd_install.py
deleted file mode 100644
index b43aa3f88fac9..0000000000000
--- a/src/lightning/app/cli/cmd_install.py
+++ /dev/null
@@ -1,657 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import re
-import shutil
-import subprocess
-import sys
-from typing import Dict, Optional, Tuple
-
-import click
-import requests
-from packaging.version import Version
-
-from lightning.app.core.constants import LIGHTNING_APPS_PUBLIC_REGISTRY, LIGHTNING_COMPONENT_PUBLIC_REGISTRY
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-@click.group(name="install")
-def install() -> None:
- """Install Lightning AI selfresources."""
- pass
-
-
-@install.command("app")
-@click.argument("name", type=str)
-@click.option(
- "--yes",
- "-y",
- is_flag=True,
- help="disables prompt to ask permission to create env and run install cmds",
-)
-@click.option(
- "--version",
- "-v",
- type=str,
- help="Specify the version to install. By default it uses 'latest'",
- default="latest",
- show_default=True,
-)
-@click.option(
- "--overwrite",
- "-f",
- is_flag=True,
- default=False,
- help="When set, overwrite the app directory without asking if it already exists.",
-)
-def install_app(name: str, yes: bool, version: str, overwrite: bool = False) -> None:
- _install_app_command(name, yes, version, overwrite=overwrite)
-
-
-@install.command("component")
-@click.argument("name", type=str)
-@click.option(
- "--yes",
- "-y",
- is_flag=True,
- help="disables prompt to ask permission to create env and run install cmds",
-)
-@click.option(
- "--version",
- "-v",
- type=str,
- help="Specify the version to install. By default it uses 'latest'",
- default="latest",
- show_default=True,
-)
-def install_component(name: str, yes: bool, version: str) -> None:
- _install_component_command(name, yes, version)
-
-
-def _install_app_command(name: str, yes: bool, version: str, overwrite: bool = False) -> None:
- if "github.com" in name:
- if version != "latest":
- logger.warn(
- "When installing from GitHub, only the 'latest' version is supported. "
- f"The provided version ({version}) will be ignored."
- )
- return non_gallery_app(name, yes, overwrite=overwrite)
-
- return gallery_app(name, yes, version, overwrite=overwrite)
-
-
-def _install_component_command(name: str, yes: bool, version: str, overwrite: bool = False) -> None:
- if "github.com" in name:
- if version != "latest":
- logger.warn(
- "When installing from GitHub, only the 'latest' version is supported. "
- f"The provided version ({version}) will be ignored."
- )
- return non_gallery_component(name, yes)
-
- return gallery_component(name, yes, version)
-
-
-def gallery_apps_and_components(
- name: str, yes_arg: bool, version_arg: str, cwd: Optional[str] = None, overwrite: bool = False
-) -> Optional[str]:
- try:
- org, app_or_component = name.split("/")
- except Exception:
- return None
-
- entry, kind = _resolve_entry(name, version_arg)
-
- if kind == "app":
- # give the user the chance to do a manual install
- source_url, git_url, folder_name, git_sha = _show_install_app_prompt(
- entry, app_or_component, org, yes_arg, resource_type="app"
- )
- # run installation if requested
- _install_app_from_source(source_url, git_url, folder_name, cwd=cwd, overwrite=overwrite, git_sha=git_sha)
-
- return os.path.join(os.getcwd(), *entry["appEntrypointFile"].split("/"))
-
- if kind == "component":
- # give the user the chance to do a manual install
- source_url, git_url, folder_name, git_sha = _show_install_app_prompt(
- entry, app_or_component, org, yes_arg, resource_type="component"
- )
- if "@" in git_url:
- git_url = git_url.split("git+")[1].split("@")[0]
- # run installation if requested
- _install_app_from_source(source_url, git_url, folder_name, cwd=cwd, overwrite=overwrite, git_sha=git_sha)
-
- return os.path.join(os.getcwd(), *entry["entrypointFile"].split("/"))
-
- return None
-
-
-def gallery_component(name: str, yes_arg: bool, version_arg: str, cwd: Optional[str] = None) -> str:
- # make sure org/component-name name is correct
- org, component = _validate_name(name, resource_type="component", example="lightning/LAI-slack-component")
-
- # resolve registry (orgs can have a private registry through their environment variables)
- registry_url = _resolve_component_registry()
-
- # load the component resource
- component_entry = _resolve_resource(registry_url, name=name, version_arg=version_arg, resource_type="component")
-
- # give the user the chance to do a manual install
- git_url = _show_install_component_prompt(component_entry, component, org, yes_arg)
-
- # run installation if requested
- _install_component_from_source(git_url)
-
- return os.path.join(os.getcwd(), component_entry["entrypointFile"])
-
-
-def non_gallery_component(gh_url: str, yes_arg: bool, cwd: Optional[str] = None) -> None:
- # give the user the chance to do a manual install
- git_url = _show_non_gallery_install_component_prompt(gh_url, yes_arg)
-
- # run installation if requested
- _install_component_from_source(git_url)
-
-
-def gallery_app(name: str, yes_arg: bool, version_arg: str, cwd: Optional[str] = None, overwrite: bool = False) -> str:
- # make sure org/app-name syntax is correct
- org, app = _validate_name(name, resource_type="app", example="lightning/quick-start")
-
- # resolve registry (orgs can have a private registry through their environment variables)
- registry_url = _resolve_app_registry()
-
- # load the app resource
- app_entry = _resolve_resource(registry_url, name=name, version_arg=version_arg, resource_type="app")
-
- # give the user the chance to do a manual install
- source_url, git_url, folder_name, git_sha = _show_install_app_prompt(
- app_entry, app, org, yes_arg, resource_type="app"
- )
-
- # run installation if requested
- _install_app_from_source(source_url, git_url, folder_name, cwd=cwd, overwrite=overwrite, git_sha=git_sha)
-
- return os.path.join(os.getcwd(), folder_name, app_entry["appEntrypointFile"])
-
-
-def non_gallery_app(gh_url: str, yes_arg: bool, cwd: Optional[str] = None, overwrite: bool = False) -> None:
- # give the user the chance to do a manual install
- repo_url, folder_name = _show_non_gallery_install_app_prompt(gh_url, yes_arg)
-
- # run installation if requested
- _install_app_from_source(repo_url, repo_url, folder_name, cwd=cwd, overwrite=overwrite)
-
-
-def _show_install_component_prompt(entry: Dict[str, str], component: str, org: str, yes_arg: bool) -> str:
- git_url = entry["gitUrl"]
-
- # yes arg does not prompt the user for permission to install anything
- # automatically creates env and sets up the project
- if yes_arg:
- return git_url
-
- prompt = f"""
- ⚡ Installing Lightning component ⚡
-
- component name : {component}
- developer : {org}
-
- Installation runs the following command for you:
-
- pip install {git_url}
- """
- logger.info(prompt)
-
- try:
- value = input("\nPress enter to continue: ")
- value = value.strip().lower()
- should_install = len(value) == 0 or value in {"y", "yes", 1}
- if not should_install:
- raise KeyboardInterrupt()
-
- return git_url
- except KeyboardInterrupt:
- repo = entry["sourceUrl"]
- raise SystemExit(
- f"""
- ⚡ Installation aborted! ⚡
-
- Install the component yourself by visiting:
- {repo}
- """
- )
-
-
-def _show_non_gallery_install_component_prompt(gh_url: str, yes_arg: bool) -> str:
- if ".git@" not in gh_url:
- m = """
- Error, your github url must be in the following format:
- git+https://github.com/OrgName/repo-name.git@ALongCommitSHAString
-
- Example:
- git+https://github.com/Lightning-AI/LAI-slack-messenger.git@14f333456ffb6758bd19458e6fa0bf12cf5575e1
- """
- raise SystemExit(m)
-
- developer = gh_url.split("/")[3]
- component_name = gh_url.split("/")[4].split(".git")[0]
- repo_url = re.search(r"git\+(.*).git", gh_url).group(1) # type: ignore
-
- # yes arg does not prompt the user for permission to install anything
- # automatically creates env and sets up the project
- if yes_arg:
- return gh_url
-
- prompt = f"""
- ⚡ Installing Lightning component ⚡
-
- component name : {component_name}
- developer : {developer}
-
- ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
- WARNING: this is NOT an official Lightning Gallery component
- Install at your own risk
- ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
-
- Installation runs the following command for you:
-
- pip install {gh_url}
- """
- logger.info(prompt)
-
- try:
- value = input("\nPress enter to continue: ")
- value = value.strip().lower()
- should_install = len(value) == 0 or value in {"y", "yes", 1}
- if not should_install:
- raise KeyboardInterrupt()
-
- return gh_url
- except KeyboardInterrupt:
- raise SystemExit(
- f"""
- ⚡ Installation aborted! ⚡
-
- Install the component yourself by visiting:
- {repo_url}
- """
- )
-
-
-def _show_install_app_prompt(
- entry: Dict[str, str], app: str, org: str, yes_arg: bool, resource_type: str
-) -> Tuple[str, str, str, Optional[str]]:
- source_url = entry["sourceUrl"] # This URL is used only to display the repo and extract folder name
- full_git_url = entry["gitUrl"] # Used to clone the repo (can include tokens for private repos)
- git_url_parts = full_git_url.split("#ref=")
- git_url = git_url_parts[0]
- git_sha = git_url_parts[1] if len(git_url_parts) == 2 else None
-
- folder_name = source_url.split("/")[-1]
-
- # yes arg does not prompt the user for permission to install anything
- # automatically creates env and sets up the project
- if yes_arg:
- return source_url, git_url, folder_name, git_sha
-
- prompt = f"""
- ⚡ Installing Lightning {resource_type} ⚡
-
- {resource_type} name : {app}
- developer: {org}
-
- Installation creates and runs the following commands for you:
-
- git clone {source_url}
- cd {folder_name}
- pip install -r requirements.txt
- pip install -e .
- """
- logger.info(prompt)
-
- try:
- value = input("\nPress enter to continue: ")
- value = value.strip().lower()
- should_install = len(value) == 0 or value in {"y", "yes", 1}
- if not should_install:
- raise KeyboardInterrupt()
-
- return source_url, git_url, folder_name, git_sha
- except KeyboardInterrupt:
- repo = entry["sourceUrl"]
- raise SystemExit(
- f"""
- ⚡ Installation aborted! ⚡
-
- Install the {resource_type} yourself by visiting:
- {repo}
- """
- )
-
-
-def _show_non_gallery_install_app_prompt(gh_url: str, yes_arg: bool) -> Tuple[str, str]:
- try:
- if gh_url.endswith(".git"):
- # folder_name when it's a GH url with .git
- folder_name = gh_url.split("/")[-1]
- folder_name = folder_name[:-4]
- else:
- # the last part of the url is the folder name otherwise
- folder_name = gh_url.split("/")[-1]
-
- org = re.search(r"github.com\/(.*)\/", gh_url).group(1) # type: ignore
- except Exception:
- raise SystemExit(
- """
- Your github url is not supported. Here's the supported format:
- https://github.com/YourOrgName/your-repo-name
-
- Example:
- https://github.com/Lightning-AI/lightning
- """
- )
-
- # yes arg does not prompt the user for permission to install anything
- # automatically creates env and sets up the project
- if yes_arg:
- return gh_url, folder_name
-
- prompt = f"""
- ⚡ Installing Lightning app ⚡
-
- app source : {gh_url}
- developer : {org}
-
- ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
- WARNING: this is NOT an official Lightning Gallery app
- Install at your own risk
- ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
-
- Installation creates and runs the following commands for you:
-
- git clone {gh_url}
- cd {folder_name}
- pip install -r requirements.txt
- pip install -e .
- """
- logger.info(prompt)
-
- try:
- value = input("\nPress enter to continue: ")
- value = value.strip().lower()
- should_install = len(value) == 0 or value in {"y", "yes", 1}
- if not should_install:
- raise KeyboardInterrupt()
-
- return gh_url, folder_name
- except KeyboardInterrupt:
- raise SystemExit(
- f"""
- ⚡ Installation aborted! ⚡
-
- Install the app yourself by visiting {gh_url}
- """
- )
-
-
-def _validate_name(name: str, resource_type: str, example: str) -> Tuple[str, str]:
- # ensure resource identifier is properly formatted
- try:
- org, resource = name.split("/")
- except Exception:
- raise SystemExit(
- f"""
- {resource_type} name format must have organization/{resource_type}-name
-
- Examples:
- {example}
- user/{resource_type}-name
-
- You passed in: {name}
- """
- )
- return org, resource
-
-
-def _resolve_entry(name, version_arg) -> Tuple[Optional[Dict], Optional[str]]:
- entry = None
- kind = None
-
- # resolve registry (orgs can have a private registry through their environment variables)
- registry_url = _resolve_app_registry()
-
- # load the app resource
- entry = _resolve_resource(registry_url, name=name, version_arg=version_arg, resource_type="app", raise_error=False)
-
- if not entry:
- registry_url = _resolve_component_registry()
-
- # load the component resource
- entry = _resolve_resource(
- registry_url, name=name, version_arg=version_arg, resource_type="component", raise_error=False
- )
- kind = "component" if entry else None
-
- else:
- kind = "app"
-
- return entry, kind
-
-
-def _resolve_resource(
- registry_url: str, name: str, version_arg: str, resource_type: str, raise_error: bool = True
-) -> Dict[str, str]:
- gallery_entries = []
- try:
- response = requests.get(registry_url)
- data = response.json()
-
- if resource_type == "app":
- gallery_entries = [a for a in data["apps"] if a["canDownloadSourceCode"]]
-
- elif resource_type == "component":
- gallery_entries = data["components"]
- except requests.ConnectionError:
- sys.tracebacklimit = 0
- raise SystemError(
- f"""
- Network connection error, could not load list of available Lightning {resource_type}s.
-
- Try again when you have a network connection!
- """
- )
-
- entries = []
- all_versions = []
- for x in gallery_entries:
- if name == x["name"]:
- entries.append(x)
- all_versions.append(x["version"])
-
- if len(entries) == 0:
- if raise_error:
- raise SystemExit(f"{resource_type}: '{name}' is not available on ⚡ Lightning AI ⚡")
-
- return None
-
- entry = None
- if version_arg == "latest":
- entry = max(entries, key=lambda app: Version(app["version"]))
- else:
- for e in entries:
- if e["version"] == version_arg:
- entry = e
- break
- if entry is None and raise_error:
- if raise_error:
- raise Exception(
- f"{resource_type}: 'Version {version_arg} for {name}' is not available on ⚡ Lightning AI ⚡. "
- f"Here is the list of all availables versions:{os.linesep}{os.linesep.join(all_versions)}"
- )
- return None
-
- return entry
-
-
-def _install_with_env(repo_url: str, folder_name: str, cwd: Optional[str] = None) -> None:
- if not cwd:
- cwd = os.getcwd()
-
- # clone repo
- logger.info(f"⚡ RUN: git clone {repo_url}")
- subprocess.call(["git", "clone", repo_url])
-
- # step into the repo folder
- os.chdir(f"{folder_name}")
- cwd = os.getcwd()
-
- # create env
- logger.info(f"⚡ CREATE: virtual env at {cwd}")
- subprocess.call(["python", "-m", "venv", cwd])
-
- # activate and install reqs
- # TODO: remove shell=True... but need to run command in venv
- logger.info("⚡ RUN: install requirements (pip install -r requirements.txt)")
- subprocess.call("source bin/activate && pip install -r requirements.txt", shell=True)
-
- # install project
- # TODO: remove shell=True... but need to run command in venv
- logger.info("⚡ RUN: setting up project (pip install -e .)")
- subprocess.call("source bin/activate && pip install -e .", shell=True)
-
- m = f"""
- ⚡ Installed! ⚡ to use your app
- go into the folder: cd {folder_name}
- activate the environment: source bin/activate
- run the app: lightning run app [the_app_file.py]
- """
- logger.info(m)
-
-
-def _install_app_from_source(
- source_url: str,
- git_url: str,
- folder_name: str,
- cwd: Optional[str] = None,
- overwrite: bool = False,
- git_sha: Optional[str] = None,
-) -> None:
- """Installing lighting app from the `git_url`
-
- Args:
- source_url:
- source repo url without any tokens and params, this param is used only for displaying
- git_url:
- repo url that is used to clone, this can contain tokens
- folder_name:
- where to clone the repo ?
- cwd:
- Working director. If not specified, current working directory is used.
- overwrite:
- If true, overwrite the app directory without asking if it already exists
- git_sha:
- The git_sha for checking out the git repo of the app.
-
- """
-
- if not cwd:
- cwd = os.getcwd()
-
- destination = os.path.join(cwd, folder_name)
- if os.path.exists(destination):
- if not overwrite:
- raise SystemExit(
- f"Folder {folder_name} exists, please delete it and try again, "
- f"or force to overwrite the existing folder by passing `--overwrite`.",
- )
- shutil.rmtree(destination)
- # clone repo
- logger.info(f"⚡ RUN: git clone {source_url}")
- try:
- subprocess.check_output(["git", "clone", git_url], stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as ex:
- if "Repository not found" in str(ex.output):
- raise SystemExit(
- f"""
- Looks like the github url was not found or doesn't exist. Do you have a typo?
- {source_url}
- """
- )
- raise Exception(ex)
-
- # step into the repo folder
- os.chdir(f"{folder_name}")
- cwd = os.getcwd()
-
- try:
- if git_sha:
- subprocess.check_output(["git", "checkout", git_sha], stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as ex:
- if "did not match any" in str(ex.output):
- raise SystemExit("Looks like the git SHA is not valid or doesn't exist in app repo.")
- raise Exception(ex)
-
- # activate and install reqs
- # TODO: remove shell=True... but need to run command in venv
- logger.info("⚡ RUN: install requirements (pip install -r requirements.txt)")
- subprocess.call("pip install -r requirements.txt", shell=True)
-
- # install project
- # TODO: remove shell=True... but need to run command in venv
- logger.info("⚡ RUN: setting up project (pip install -e .)")
- subprocess.call("pip install -e .", shell=True)
-
- m = f"""
- ⚡ Installed! ⚡ to use your app:
-
- cd {folder_name}
- lightning run app app.py
- """
- logger.info(m)
-
-
-def _install_component_from_source(git_url: str) -> None:
- logger.info("⚡ RUN: pip install")
-
- out = subprocess.check_output(["pip", "install", git_url])
- possible_success_message = [x for x in str(out).split("\\n") if "Successfully installed" in x]
- if len(possible_success_message) > 0:
- uninstall_step = possible_success_message[0]
- uninstall_step = re.sub("Successfully installed", "", uninstall_step).strip()
- uninstall_step = re.sub("-0.0.0", "", uninstall_step).strip()
- m = """
- ⚡ Installed! ⚡
-
- to use your component:
- from the_component import TheClass
-
- make sure to add this entry to your Lightning APP requirements.txt file:
- {git_url}
-
- if you want to uninstall, run this command:
- pip uninstall {uninstall_step}
- """
- logger.info(m)
-
-
-def _resolve_app_registry() -> str:
- return os.environ.get("LIGHTNING_APP_REGISTRY", LIGHTNING_APPS_PUBLIC_REGISTRY)
-
-
-def _resolve_component_registry() -> str:
- return os.environ.get("LIGHTNING_COMPONENT_REGISTRY", LIGHTNING_COMPONENT_PUBLIC_REGISTRY)
diff --git a/src/lightning/app/cli/cmd_pl_init.py b/src/lightning/app/cli/cmd_pl_init.py
deleted file mode 100644
index 2436c28179ef2..0000000000000
--- a/src/lightning/app/cli/cmd_pl_init.py
+++ /dev/null
@@ -1,187 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import pathlib
-import re
-import shutil
-import subprocess
-import sys
-import tarfile
-import urllib.request
-from pathlib import Path
-from tempfile import TemporaryDirectory
-from typing import Any, Dict, List, Optional
-
-import click
-from jinja2 import Environment, FileSystemLoader
-from rich import print
-from rich.panel import Panel
-from rich.status import Status
-from rich.text import Text
-from rich.tree import Tree
-
-import lightning.app
-
-_REPORT_HELP_TEXTS = {
- "core": "Important files for the app such as various components",
- "source": "A copy of all your source code, including the PL script ⚡",
- "tests": "This app comes with tests!",
- "ui": "Source and build files for the user interface",
- "app.py": "This is the main app file!",
- "requirements.txt": "Lists the dependencies required to be installed before running the app",
-}
-
-_REPORT_IGNORE_PATTERNS = [
- r"__pycache__",
- r"__init__\.py",
- r".*egg-info",
- r"\..*",
-]
-
-
-def pl_app(source_dir: str, script_path: str, name: str, overwrite: bool) -> None:
- source_dir = Path(source_dir).resolve()
- script_path = Path(script_path).resolve()
-
- if not source_dir.is_dir():
- click.echo(f"The given source directory does not exist: {source_dir}", err=True)
- raise SystemExit(1)
-
- if not script_path.exists():
- click.echo(f"The given script path does not exist: {script_path}", err=True)
- raise SystemExit(1)
-
- if not script_path.is_file():
- click.echo(f"The given script path must be a file, you passed: {script_path}", err=True)
- raise SystemExit(1)
-
- if source_dir not in script_path.parents:
- click.echo(
- "The given script path must be a subpath of the source directory. Example:"
- " lightning init pl-app ./code ./code/scripts/train.py",
- err=True,
- )
- raise SystemExit(1)
-
- rel_script_path = script_path.relative_to(source_dir)
- cwd = Path.cwd()
- destination = cwd / name
-
- if destination.exists():
- if not overwrite:
- click.echo(
- f"There is already an app with the name {name} in the current working directory. Choose a different"
- f" name with `--name` or force to overwrite the existing folder by passing `--overwrite`.",
- err=True,
- )
- raise SystemExit(1)
-
- shutil.rmtree(destination)
-
- template_dir = Path(lightning.app.cli.__file__).parent / "pl-app-template"
-
- with Status("[bold green]Copying app files"):
- shutil.copytree(template_dir, destination, ignore=shutil.ignore_patterns("node_modules", "build"))
- if (template_dir / "ui" / "build").exists():
- shutil.copytree(template_dir / "ui" / "build", destination / "ui" / "build")
- else:
- download_frontend(destination / "ui" / "build")
-
- with Status("[bold green]Copying source files"):
- shutil.copytree(source_dir, destination / "source", ignore=shutil.ignore_patterns(name))
- project_file_from_template(template_dir, destination, "app.py", script_path=str(rel_script_path))
- project_file_from_template(template_dir, destination, "setup.py", app_name=name)
-
- with Status("[bold green]Installing"):
- subprocess.call(["pip", "install", "--quiet", "-e", str(destination)])
- # TODO: download the ui files
-
- print_pretty_report(
- destination,
- ignore_patterns=_REPORT_IGNORE_PATTERNS,
- help_texts=_REPORT_HELP_TEXTS,
- )
-
-
-def download_frontend(destination: Path) -> None:
- # TODO: Update the URL to the release in GitHub once the PL app repo is public
- url = "https://storage.googleapis.com/grid-packages/pytorch-lightning-app/v0.0.0/build.tar.gz"
- build_dir_name = "build"
- with TemporaryDirectory() as download_dir:
- response = urllib.request.urlopen(url) # noqa: S310
- file = tarfile.open(fileobj=response, mode="r|gz")
- file.extractall(path=download_dir) # noqa: S202
- shutil.move(str(Path(download_dir, build_dir_name)), destination)
-
-
-def project_file_from_template(template_dir: Path, destination_dir: Path, template_name: str, **kwargs: Any) -> None:
- env = Environment(loader=FileSystemLoader(template_dir)) # noqa: S701
- template = env.get_template(template_name)
- rendered_template = template.render(**kwargs)
- with open(destination_dir / template_name, "w") as file:
- file.write(rendered_template)
-
-
-def print_pretty_report(
- directory: pathlib.Path,
- ignore_patterns: Optional[List[str]] = None,
- help_texts: Optional[Dict[str, str]] = None,
-) -> None:
- """Prints a report for the generated app."""
- tree = Tree(
- f":open_file_folder: [link file://{directory}]{directory}",
- guide_style="bold bright_blue",
- )
-
- help_texts = {} if help_texts is None else help_texts
-
- paths = sorted(
- directory.glob("*"),
- key=lambda p: (p.is_file(), p.name.lower()),
- )
- max_witdth = max(len(p.name) for p in paths)
-
- patterns_to_ignore = [] if ignore_patterns is None else ignore_patterns
- for path in paths:
- if any(re.match(pattern, path.name) for pattern in patterns_to_ignore):
- # Only display relevant files
- continue
-
- help_text = help_texts.get(path.name, "")
- padding = " " * (max_witdth - len(path.name))
-
- text_pathname = Text(path.name, "green")
- text_pathname.highlight_regex(r"\..*$", "bold red")
- text_pathname.stylize(f"link file://{path}")
- text_pathname.append(f" {padding} {help_text}", "blue")
-
- icon = "📂 " if path.is_dir() else "📄 "
- icon = icon if _can_encode_icon(icon) else ""
-
- tree.add(Text(icon) + text_pathname)
-
- print("\n")
- print("Done. The app is ready here:\n")
- print(tree)
- print("\nRun it:\n")
- print(Panel(f"[red]lightning run app {directory.relative_to(Path.cwd()) / 'app.py'}"))
-
-
-def _can_encode_icon(icon: str) -> bool:
- """Helper function to check whether an icon can be encoded."""
- try:
- icon.encode(sys.stdout.encoding)
- return True
- except UnicodeEncodeError:
- return False
diff --git a/src/lightning/app/cli/cmd_react_ui_init.py b/src/lightning/app/cli/cmd_react_ui_init.py
deleted file mode 100644
index 22e668433e233..0000000000000
--- a/src/lightning/app/cli/cmd_react_ui_init.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import re
-import shutil
-import subprocess
-from typing import Optional
-
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-def react_ui(dest_dir: Optional[str] = None) -> None:
- # verify all the prereqs for install are met
- _check_react_prerequisites()
-
- # copy template files to the dir
- _copy_and_setup_react_ui(dest_dir)
-
-
-def _copy_and_setup_react_ui(dest_dir: Optional[str] = None) -> None:
- logger.info("⚡ setting up react-ui template")
- path = os.path.dirname(os.path.abspath(__file__))
- template_dir = os.path.join(path, "react-ui-template")
-
- if dest_dir is None:
- dest_dir = os.path.join(os.getcwd(), "react-ui")
-
- shutil.copytree(template_dir, dest_dir)
-
- logger.info("⚡ install react project deps")
- ui_path = os.path.join(dest_dir, "ui")
- subprocess.run(f"cd {ui_path} && yarn install", shell=True)
-
- logger.info("⚡ building react project")
- subprocess.run(f"cd {ui_path} && yarn build", shell=True)
-
- m = f"""
- ⚡⚡ react-ui created! ⚡⚡
-
- ⚡ Connect it to your component using `configure_layout`:
-
- # Use a LightningFlow or LightningWork
- class YourComponent(la.LightningFlow):
- def configure_layout(self):
- return la.frontend.StaticWebFrontend(Path(__file__).parent / "react-ui/src/dist")
-
- ⚡ run the example_app.py to see it live!
- lightning_app run app {dest_dir}/example_app.py
-
- """
- logger.info(m)
-
-
-def _check_react_prerequisites() -> None:
- """Args are for test purposes only."""
- missing_msgs = []
- version_regex = r"\d{1,2}\.\d{1,2}\.\d{1,3}"
-
- logger.info("Checking pre-requisites for react")
-
- # make sure npm is installed
- npm_version = subprocess.check_output(["npm", "--version"])
- has_npm = bool(re.search(version_regex, str(npm_version)))
- npm_version = re.search(version_regex, str(npm_version))
- npm_version = None if npm_version is None else npm_version.group(0)
-
- if not has_npm:
- m = """
- This machine is missing 'npm'. Please install npm and rerun 'lightning_app init react-ui' again.
-
- Install instructions: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
- """
- missing_msgs.append(m)
-
- # make sure node is installed
- node_version = subprocess.check_output(["node", "--version"])
- has_node = bool(re.search(version_regex, str(node_version)))
- node_version = re.search(version_regex, str(node_version))
- node_version = None if node_version is None else node_version.group(0)
-
- if not has_node:
- m = """
- This machine is missing 'node'. Please install node and rerun 'lightning_app init react-ui' again.
-
- Install instructions: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
- """
- missing_msgs.append(m)
-
- # make sure yarn is installed
- yarn_version = subprocess.check_output(["yarn", "--version"])
- has_yarn = bool(re.search(version_regex, str(yarn_version)))
- yarn_version = re.search(version_regex, str(yarn_version))
- yarn_version = None if yarn_version is None else yarn_version.group(0)
-
- if not has_yarn:
- m = """
- This machine is missing 'yarn'. Please install npm+node first, then run
-
- npm install --global yarn
-
- Full install instructions: https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable
- """
- missing_msgs.append(m)
-
- # exit or show success message
- if len(missing_msgs) > 0:
- missing_msg = "\n".join(missing_msgs)
- raise SystemExit(missing_msg)
- logger.info(
- f"""
- found npm version: {npm_version}
- found node version: {node_version}
- found yarn version: {yarn_version}
-
- Pre-requisites met!
- """
- )
diff --git a/src/lightning/app/cli/commands/__init__.py b/src/lightning/app/cli/commands/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/commands/app_commands.py b/src/lightning/app/cli/commands/app_commands.py
deleted file mode 100644
index bbecceabb6e28..0000000000000
--- a/src/lightning/app/cli/commands/app_commands.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import sys
-from typing import Dict, Optional
-
-import requests
-
-from lightning.app.cli.connect.app import (
- _clean_lightning_connection,
- _install_missing_requirements,
- _resolve_command_path,
-)
-from lightning.app.utilities.cli_helpers import _LightningAppOpenAPIRetriever
-from lightning.app.utilities.commands.base import _download_command
-from lightning.app.utilities.enum import OpenAPITags
-
-
-def _is_running_help(argv) -> bool:
- return argv[-1] in ["--help", "-"] if argv else False
-
-
-def _run_app_command(app_name: str, app_id: Optional[str]):
- """Execute a function in a running App from its name."""
- # 1: Collect the url and comments from the running application
- _clean_lightning_connection()
-
- running_help = _is_running_help(sys.argv)
-
- retriever = _LightningAppOpenAPIRetriever(app_id, use_cache=running_help)
-
- if not running_help and (retriever.url is None or retriever.api_commands is None):
- if app_name == "localhost":
- print("The command couldn't be executed as your local Lightning App isn't running.")
- else:
- print(f"The command couldn't be executed as your cloud Lightning App `{app_name}` isn't running.")
- sys.exit(0)
-
- if not retriever.api_commands:
- raise Exception("This application doesn't expose any commands yet.")
-
- full_command = "_".join(sys.argv)
-
- has_found = False
- for command in list(retriever.api_commands):
- if command in full_command:
- has_found = True
- for value in sys.argv:
- if value == command and "_" in value:
- print(
- f"The command `{value}` was provided with an underscore and it isn't allowed."
- f"Instead, use `lightning_app {value.replace('_', ' ')}`."
- )
- sys.exit(0)
- break
-
- if not has_found:
- raise Exception(f"The provided command isn't available in {list(retriever.api_commands)}")
-
- # 2: Send the command from the user
- metadata = retriever.api_commands[command]
-
- try:
- # 3: Execute the command
- if metadata["tag"] == OpenAPITags.APP_COMMAND:
- _handle_command_without_client(command, metadata, retriever.url)
- else:
- _handle_command_with_client(command, metadata, app_name, app_id, retriever.url)
- except ModuleNotFoundError:
- _install_missing_requirements(retriever, fail_if_missing=True)
-
- if running_help:
- print("Your command execution was successful.")
-
-
-def _handle_command_without_client(command: str, metadata: Dict, url: str) -> None:
- supported_params = list(metadata["parameters"])
- if _is_running_help(sys.argv):
- print(f"Usage: lightning_app {command} [ARGS]...")
- print(" ")
- print("Options")
- for param in supported_params:
- print(f" {param}: Add description")
- return
-
- provided_params = [param.replace("--", "") for param in sys.argv[1 + len(command.split("_")) :]]
-
- # TODO: Add support for more argument types.
- if any("=" not in param for param in provided_params):
- raise Exception("Please, use --x=y syntax when providing the command arguments.")
-
- if any(param.split("=")[0] not in supported_params for param in provided_params):
- raise Exception(f"Some arguments need to be provided. The keys are {supported_params}.")
-
- # TODO: Encode the parameters and validate their type.
- query_parameters = "&".join(provided_params)
- resp = requests.post(url + f"/command/{command}?{query_parameters}")
- assert resp.status_code == 200, resp.json()
- print(resp.json())
-
-
-def _handle_command_with_client(command: str, metadata: Dict, app_name: str, app_id: Optional[str], url: str):
- debug_mode = bool(int(os.getenv("DEBUG", "0")))
-
- if app_name == "localhost":
- target_file = metadata["cls_path"]
- else:
- target_file = _resolve_command_path(command) if debug_mode else _resolve_command_path(command)
-
- if debug_mode:
- print(target_file)
-
- client_command = _download_command(
- command,
- metadata["cls_path"],
- metadata["cls_name"],
- app_id,
- debug_mode=debug_mode,
- target_file=target_file if debug_mode else _resolve_command_path(command),
- )
- client_command._setup(command_name=command, app_url=url)
- sys.argv = sys.argv[len(command.split("_")) :]
- client_command.run()
diff --git a/src/lightning/app/cli/commands/cd.py b/src/lightning/app/cli/commands/cd.py
deleted file mode 100644
index 7f84b894bf155..0000000000000
--- a/src/lightning/app/cli/commands/cd.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from typing import Optional, Tuple, Union
-
-import click
-from rich.live import Live
-from rich.spinner import Spinner
-from rich.text import Text
-
-from lightning.app.cli.commands import ls
-from lightning.app.cli.connect.app import _LIGHTNING_CONNECTION_FOLDER
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cli_helpers import _error_and_exit
-
-logger = Logger(__name__)
-
-_HOME = os.path.expanduser("~")
-_CD_FILE = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "cd.txt")
-
-
-@click.argument("path", nargs=-1)
-def cd(path: Optional[Union[Tuple[str], str]], verify: bool = True) -> None:
- """Change the current directory within the Lightning Cloud filesystem."""
- with Live(Spinner("point", text=Text("pending...", style="white")), transient=True) as live:
- root = "/"
-
- if isinstance(path, Tuple) and len(path) > 0:
- path = " ".join(path)
-
- # handle ~/
- if isinstance(path, str) and path.startswith(_HOME):
- path = "/" + path.replace(_HOME, "")
-
- # handle no path -> /
- if path is None or len(path) == 0:
- path = "/"
-
- if not os.path.exists(_LIGHTNING_CONNECTION_FOLDER):
- os.makedirs(_LIGHTNING_CONNECTION_FOLDER)
-
- if not os.path.exists(_CD_FILE):
- # Start from the root
- if path.startswith(".."):
- root = _apply_double_dots(root, path)
-
- with open(_CD_FILE, "w") as f:
- f.write(root + "\n")
-
- live.stop()
-
- print(f"cd {root}")
-
- return root
-
- # read from saved cd
- with open(_CD_FILE) as f:
- lines = f.readlines()
- root = lines[0].replace("\n", "")
-
- if verify:
- if path.startswith("/"):
- paths = [os.path.join(path, p) for p in ls.ls(path, print=False, use_live=False)]
- else:
- paths = [os.path.join(root, p) for p in ls.ls(root, print=False, use_live=False)]
-
- # generate new root
- if root == "/":
- if path == "/":
- root = "/"
- elif not path.startswith(".."):
- if not path.startswith("/"):
- path = "/" + path
- root = path
- else:
- root = _apply_double_dots(root, path)
- else:
- if path.startswith(".."):
- root = _apply_double_dots(root, path)
- elif path.startswith("~"):
- root = path[2:]
- else:
- root = os.path.join(root, path)
-
- if verify and root != "/" and not any(p.startswith(root) or root.startswith(p) for p in paths):
- _error_and_exit(f"no such file or directory: {path}")
-
- os.remove(_CD_FILE)
-
- # store new root
- with open(_CD_FILE, "w") as f:
- f.write(root + "\n")
-
- live.stop()
-
- print(f"cd {root}")
-
- return root
-
-
-def _apply_double_dots(root: str, path: str) -> str:
- splits = [split for split in path.split("/") if split != ""]
- for split in splits:
- root = "/" + os.path.join(*root.split("/")[:-1]) if split == ".." else os.path.join(root, split)
- return root
diff --git a/src/lightning/app/cli/commands/cp.py b/src/lightning/app/cli/commands/cp.py
deleted file mode 100644
index 0b11a874b216d..0000000000000
--- a/src/lightning/app/cli/commands/cp.py
+++ /dev/null
@@ -1,350 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import concurrent
-import contextlib
-import os
-import sys
-from functools import partial
-from multiprocessing.pool import ApplyResult
-from pathlib import Path
-from textwrap import dedent
-from typing import Any, Optional, Tuple, Union
-
-import click
-import requests
-import urllib3
-from lightning_cloud.openapi import (
- Externalv1Cluster,
- Externalv1LightningappInstance,
- ProjectIdStorageBody,
- V1CloudSpace,
-)
-from rich.live import Live
-from rich.progress import BarColumn, DownloadColumn, Progress, TaskID, TextColumn
-from rich.spinner import Spinner
-from rich.text import Text
-
-from lightning.app.cli.commands.ls import _collect_artifacts, _get_prefix
-from lightning.app.cli.commands.pwd import _pwd
-from lightning.app.source_code import FileUploader
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.auth import _AuthTokenGetter
-from lightning.app.utilities.cli_helpers import _error_and_exit
-from lightning.app.utilities.network import LightningClient
-
-logger = Logger(__name__)
-
-
-@click.argument("src_path", required=True)
-@click.argument("dst_path", required=True)
-@click.option("-r", required=False, hidden=True)
-@click.option("--recursive", required=False, hidden=True)
-@click.option("--zip", required=False, is_flag=True, default=False)
-def cp(src_path: str, dst_path: str, r: bool = False, recursive: bool = False, zip: bool = False) -> None:
- """Copy files between your local filesystem and the Lightning Cloud filesystem."""
- if sys.platform == "win32":
- print("`cp` isn't supported on windows. Open an issue on Github.")
- sys.exit(0)
-
- with Live(Spinner("point", text=Text("pending...", style="white")), transient=True) as live:
- pwd = _pwd()
-
- client = LightningClient(retry=False)
-
- src_path, src_remote = _sanitize_path(src_path, pwd)
- dst_path, dst_remote = _sanitize_path(dst_path, pwd)
-
- if src_remote and dst_remote:
- return _error_and_exit("Moving files remotely isn't supported yet. Please, open a Github issue.")
-
- if not src_remote and dst_remote:
- if dst_path == "/" or len(dst_path.split("/")) == 1:
- return _error_and_exit("Uploading files at the project level isn't allowed yet.")
- if zip:
- return _error_and_exit("Zipping uploads isn't supported yet. Please, open a Github issue.")
- _upload_files(live, client, src_path, dst_path, pwd)
- return None
- if src_remote and not dst_remote:
- if zip:
- return _zip_files(live, src_path, dst_path)
- _download_files(live, client, src_path, dst_path, pwd)
- return None
-
- return _error_and_exit("Moving files locally isn't supported yet. Please, open a Github issue.")
-
-
-def _upload_files(live, client: LightningClient, local_src: str, remote_dst: str, pwd: str) -> str:
- remote_splits = [split for split in remote_dst.split("/") if split != ""]
- remote_dst = os.path.join(*remote_splits)
-
- if not os.path.exists(local_src):
- return _error_and_exit(f"The provided source path {local_src} doesn't exist.")
-
- lit_resource = None
-
- if len(remote_splits) > 1:
- project_id, lit_resource = _get_project_id_and_resource(pwd)
- else:
- project_id = _get_project_id_from_name(remote_dst)
-
- if len(remote_splits) > 2:
- remote_dst = os.path.join(*remote_splits[2:])
-
- local_src = Path(local_src).resolve()
- upload_paths = []
-
- if os.path.isdir(local_src):
- for root_dir, _, paths in os.walk(local_src):
- for path in paths:
- upload_paths.append(os.path.join(root_dir, path))
- else:
- upload_paths = [local_src]
-
- _upload_urls = []
-
- clusters = client.projects_service_list_project_cluster_bindings(project_id)
-
- live.stop()
-
- for upload_path in upload_paths:
- for cluster in clusters.clusters:
- filename = str(upload_path).replace(str(os.getcwd()), "")[1:]
- filename = _get_prefix(os.path.join(remote_dst, filename), lit_resource) if lit_resource else "/" + filename
-
- response = client.lightningapp_instance_service_upload_project_artifact(
- project_id=project_id,
- body=ProjectIdStorageBody(cluster_id=cluster.cluster_id, filename=filename),
- async_req=True,
- )
- _upload_urls.append(response)
-
- upload_urls = []
- for upload_url in _upload_urls:
- upload_urls.extend(upload_url.get().urls)
-
- live.stop()
-
- if not upload_paths:
- print("There were no files to upload.")
- return None
-
- progress = _get_progress_bar()
-
- total_size = sum([Path(path).stat().st_size for path in upload_paths]) // max(len(clusters.clusters), 1)
- task_id = progress.add_task("upload", filename="", total=total_size)
-
- progress.start()
-
- _upload_partial = partial(_upload, progress=progress, task_id=task_id)
-
- with concurrent.futures.ThreadPoolExecutor(4) as executor:
- results = executor.map(_upload_partial, upload_paths, upload_urls)
-
- progress.stop()
-
- # Raise the first exception found
- exception = next((e for e in results if isinstance(e, Exception)), None)
- if exception:
- _error_and_exit("We detected errors in uploading your files.")
- return None
- return None
-
-
-def _upload(source_file: str, presigned_url: ApplyResult, progress: Progress, task_id: TaskID) -> Optional[Exception]:
- source_file = Path(source_file)
- file_uploader = FileUploader(
- presigned_url,
- source_file,
- total_size=None,
- name=str(source_file),
- )
- file_uploader.progress = progress
- file_uploader.task_id = task_id
- file_uploader.upload()
-
-
-def _zip_files(live: Live, remote_src: str, local_dst: str) -> None:
- if len(remote_src.split("/")) < 3:
- return _error_and_exit(
- dedent(
- f"""
- The source path must be at least two levels deep (e.g. r:/my-project/my-lit-resource).
-
- The path provided was: r:{remote_src}
- """
- )
- )
-
- if os.path.isdir(local_dst):
- local_dst = os.path.join(local_dst, os.path.basename(remote_src) + ".zip")
-
- project_id, lit_resource = _get_project_id_and_resource(remote_src)
-
- # /my-project/my-lit-resource/artfact-path -> cloudspace/my-lit-resource-id/artifact-path
- artifact = "/".join(remote_src.split("/")[3:])
- prefix = _get_prefix(artifact, lit_resource)
-
- token = _AuthTokenGetter(LightningClient().api_client)._get_api_token()
- endpoint = f"/v1/projects/{project_id}/artifacts/download?prefix={prefix}&token={token}"
-
- cluster = _cluster_from_lit_resource(lit_resource)
- url = _storage_host(cluster) + endpoint
-
- live.stop()
- progress = _get_progress_bar(transient=True)
- progress.start()
- task_id = progress.add_task("download zip", total=None)
-
- _download_file(local_dst, url, progress, task_id)
- progress.stop()
-
- click.echo(f"Downloaded to {local_dst}")
- return None
-
-
-def _download_files(live, client, remote_src: str, local_dst: str, pwd: str):
- project_id, lit_resource = _get_project_id_and_resource(pwd)
-
- download_paths = []
- download_urls = []
- total_size = []
-
- prefix = _get_prefix("/".join(pwd.split("/")[3:]), lit_resource) + "/"
-
- for artifact in _collect_artifacts(client, project_id, prefix, include_download_url=True):
- path = os.path.join(local_dst, artifact.filename.replace(remote_src, ""))
- path = Path(path).resolve()
- os.makedirs(path.parent, exist_ok=True)
- download_paths.append(path)
- download_urls.append(artifact.url)
- total_size.append(int(artifact.size_bytes))
-
- live.stop()
-
- if not download_paths:
- print("There were no files to download.")
- return
-
- progress = progress = _get_progress_bar()
-
- progress.start()
-
- task_id = progress.add_task("download", filename="", total=sum(total_size))
-
- _download_file_fn = partial(_download_file, progress=progress, task_id=task_id)
-
- with concurrent.futures.ThreadPoolExecutor(4) as executor:
- results = executor.map(_download_file_fn, download_paths, download_urls)
-
- progress.stop()
-
- # Raise the first exception found
- exception = next((e for e in results if isinstance(e, Exception)), None)
- if exception:
- _error_and_exit("There was an error downloading your files.")
-
-
-def _download_file(path: str, url: str, progress: Progress, task_id: TaskID) -> None:
- # Disable warning about making an insecure request
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
-
- with contextlib.suppress(ConnectionError):
- request = requests.get(url, stream=True, verify=False) # noqa: S501
-
- chunk_size = 1024
-
- with open(path, "wb") as fp:
- for chunk in request.iter_content(chunk_size=chunk_size):
- fp.write(chunk) # type: ignore
- progress.update(task_id, advance=len(chunk))
-
-
-def _sanitize_path(path: str, pwd: str) -> Tuple[str, bool]:
- is_remote = _is_remote(path)
- if is_remote:
- path = _remove_remote(path)
- path = pwd if path == "." else os.path.join(pwd, path)
- return path, is_remote
-
-
-def _is_remote(path: str) -> bool:
- return path.startswith("r:") or path.startswith("remote:")
-
-
-def _remove_remote(path: str) -> str:
- return path.replace("r:", "").replace("remote:", "")
-
-
-def _get_project_id_and_resource(pwd: str) -> Tuple[str, Union[Externalv1LightningappInstance, V1CloudSpace]]:
- """Convert a root path to a project id and app id."""
- # TODO: Handle project level
- project_name, resource_name, *_ = pwd.split("/")[1:3]
-
- # 1. Collect the projects of the user
- client = LightningClient()
- projects = client.projects_service_list_memberships()
- project_id = [project.project_id for project in projects.memberships if project.name == project_name][0]
-
- # 2. Collect resources
- lit_apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id).lightningapps
-
- lit_cloud_spaces = client.cloud_space_service_list_cloud_spaces(project_id=project_id).cloudspaces
-
- lit_ressources = [lit_resource for lit_resource in lit_cloud_spaces if lit_resource.name == resource_name]
-
- if len(lit_ressources) == 0:
- lit_ressources = [lit_resource for lit_resource in lit_apps if lit_resource.name == resource_name]
-
- if len(lit_ressources) == 0:
- print(f"ERROR: There isn't any Lightning Ressource matching the name {resource_name}.")
- sys.exit(0)
-
- return project_id, lit_ressources[0]
-
-
-def _get_project_id_from_name(project_name: str) -> str:
- # 1. Collect the projects of the user
- client = LightningClient()
- projects = client.projects_service_list_memberships()
- return [project.project_id for project in projects.memberships if project.name == project_name][0]
-
-
-def _get_progress_bar(**kwargs: Any) -> Progress:
- return Progress(
- TextColumn("[bold blue]{task.description}", justify="left"),
- BarColumn(bar_width=None),
- "[self.progress.percentage]{task.percentage:>3.1f}%",
- DownloadColumn(),
- **kwargs,
- )
-
-
-def _storage_host(cluster: Externalv1Cluster) -> str:
- dev_host = os.environ.get("LIGHTNING_STORAGE_HOST")
- if dev_host:
- return dev_host
- return f"https://storage.{cluster.spec.driver.kubernetes.root_domain_name}"
-
-
-def _cluster_from_lit_resource(lit_resource: Union[Externalv1LightningappInstance, V1CloudSpace]) -> Externalv1Cluster:
- client = LightningClient()
- if isinstance(lit_resource, Externalv1LightningappInstance):
- return client.cluster_service_get_cluster(lit_resource.spec.cluster_id)
-
- clusters = client.cluster_service_list_clusters()
- for cluster in clusters.clusters:
- if cluster.id == clusters.default_cluster:
- return cluster
- return None
diff --git a/src/lightning/app/cli/commands/logs.py b/src/lightning/app/cli/commands/logs.py
deleted file mode 100644
index 4587987ae5f17..0000000000000
--- a/src/lightning/app/cli/commands/logs.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import List
-
-import click
-import rich
-from rich.color import ANSI_COLOR_NAMES
-
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.app_logs import _app_logs_reader
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.logs_socket_api import _LightningLogsSocketAPI
-from lightning.app.utilities.network import LightningClient
-
-logger = Logger(__name__)
-
-
-@click.argument("app_name", required=False)
-@click.argument("components", nargs=-1, required=False)
-@click.option("-f", "--follow", required=False, is_flag=True, help="Wait for new logs, to exit use CTRL+C.")
-def logs(app_name: str, components: List[str], follow: bool) -> None:
- """Show cloud application logs. By default, prints logs for all currently available components.
-
- Example uses:
-
- Print all application logs:
-
- $ lightning show logs my-application
-
- Print logs only from the flow (no work):
-
- $ lightning show logs my-application flow
-
- Print logs only from selected works:
-
- $ lightning show logs my-application root.work_a root.work_b
-
- """
- _show_logs(app_name, components, follow)
-
-
-def _show_logs(app_name: str, components: List[str], follow: bool) -> None:
- client = LightningClient(retry=False)
- project = _get_project(client)
-
- apps = {
- getattr(app, "display_name", None) or app.name: app
- for app in client.lightningapp_instance_service_list_lightningapp_instances(
- project_id=project.project_id
- ).lightningapps
- }
-
- if not apps:
- raise click.ClickException(
- "You don't have any application in the cloud. Please, run an application first with `--cloud`."
- )
-
- if not app_name:
- raise click.ClickException(
- f"You have not specified any Lightning App. Please select one of the following: [{', '.join(apps.keys())}]."
- )
-
- if app_name not in apps:
- raise click.ClickException(
- f"The Lightning App '{app_name}' does not exist. "
- f"Please select one of the following: [{', '.join(apps.keys())}]."
- )
-
- # Fetch all lightning works from given application
- # 'Flow' component is somewhat implicit, only one for whole app,
- # and not listed in lightningwork API - so we add it directly to the list
- works = client.lightningwork_service_list_lightningwork(
- project_id=project.project_id, app_id=apps[app_name].id
- ).lightningworks
-
- app_component_names = ["flow"] + [f.name for f in apps[app_name].spec.flow_servers] + [w.name for w in works]
-
- if not components:
- components = app_component_names
-
- else:
-
- def add_prefix(c: str) -> str:
- if c == "flow":
- return c
- if not c.startswith("root."):
- return "root." + c
- return c
-
- components = [add_prefix(c) for c in components]
-
- for component in components:
- if component not in app_component_names:
- raise click.ClickException(f"Component '{component}' does not exist in app {app_name}.")
-
- log_reader = _app_logs_reader(
- logs_api_client=_LightningLogsSocketAPI(client.api_client),
- project_id=project.project_id,
- app_id=apps[app_name].id,
- component_names=components,
- follow=follow,
- )
-
- rich_colors = list(ANSI_COLOR_NAMES)
- colors = {c: rich_colors[i + 1] for i, c in enumerate(components)}
-
- for log_event in log_reader:
- date = log_event.timestamp.strftime("%m/%d/%Y %H:%M:%S")
- color = colors[log_event.component_name]
- rich.print(f"[{color}]{log_event.component_name}[/{color}] {date} {log_event.message}")
diff --git a/src/lightning/app/cli/commands/ls.py b/src/lightning/app/cli/commands/ls.py
deleted file mode 100644
index e16a354f66d8c..0000000000000
--- a/src/lightning/app/cli/commands/ls.py
+++ /dev/null
@@ -1,268 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import contextlib
-import os
-import sys
-from contextlib import nullcontext
-from typing import Generator, List, Optional
-
-import click
-import lightning_cloud
-import rich
-from lightning_cloud.openapi import Externalv1LightningappInstance
-from rich.console import Console
-from rich.live import Live
-from rich.spinner import Spinner
-from rich.text import Text
-
-from lightning.app.cli.connect.app import _LIGHTNING_CONNECTION_FOLDER
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cli_helpers import _error_and_exit
-from lightning.app.utilities.network import LightningClient
-
-_FOLDER_COLOR = "sky_blue1"
-_FILE_COLOR = "white"
-
-logger = Logger(__name__)
-
-
-@click.argument("path", required=False)
-def ls(path: Optional[str] = None, print: bool = True, use_live: bool = True) -> List[str]:
- """List the contents of a folder in the Lightning Cloud Filesystem."""
- from lightning.app.cli.commands.cd import _CD_FILE
-
- if sys.platform == "win32":
- _error_and_exit("`ls` isn't supported on windows. Open an issue on Github.")
-
- root = "/"
-
- context = (
- Live(Spinner("point", text=Text("pending...", style="white")), transient=True) if use_live else nullcontext()
- )
-
- with context:
- if not os.path.exists(_LIGHTNING_CONNECTION_FOLDER):
- os.makedirs(_LIGHTNING_CONNECTION_FOLDER)
-
- if not os.path.exists(_CD_FILE):
- with open(_CD_FILE, "w") as f:
- f.write(root + "\n")
- else:
- with open(_CD_FILE) as f:
- lines = f.readlines()
- root = lines[0].replace("\n", "")
-
- client = LightningClient(retry=False)
- projects = client.projects_service_list_memberships()
-
- if root == "/":
- project_names = [project.name for project in projects.memberships]
- if print:
- _print_names_with_colors(project_names, [_FOLDER_COLOR] * len(project_names))
- return project_names
-
- # Note: Root format has the following structure:
- # /{PROJECT_NAME}/{APP_NAME}/{ARTIFACTS_PATHS}
- splits = root.split("/")[1:]
-
- project = [project for project in projects.memberships if project.name == splits[0]]
-
- # This happens if the user changes cluster and the project doesn't exit.
- if len(project) == 0:
- return _error_and_exit(
- f"There isn't any Lightning Project matching the name {splits[0]}." " HINT: Use `lightning_app cd`."
- )
-
- project_id = project[0].project_id
-
- # Parallelise calls
- lit_apps = client.lightningapp_instance_service_list_lightningapp_instances(
- project_id=project_id, async_req=True
- )
- lit_cloud_spaces = client.cloud_space_service_list_cloud_spaces(project_id=project_id, async_req=True)
-
- lit_apps = lit_apps.get().lightningapps
- lit_cloud_spaces = lit_cloud_spaces.get().cloudspaces
-
- if len(splits) == 1:
- apps = [lit_app.name for lit_app in lit_apps]
- cloud_spaces = [lit_cloud_space.name for lit_cloud_space in lit_cloud_spaces]
- ressource_names = sorted(set(cloud_spaces + apps))
- if print:
- _print_names_with_colors(ressource_names, [_FOLDER_COLOR] * len(ressource_names))
- return ressource_names
-
- lit_ressources = [lit_resource for lit_resource in lit_cloud_spaces if lit_resource.name == splits[1]]
-
- if len(lit_ressources) == 0:
- lit_ressources = [lit_resource for lit_resource in lit_apps if lit_resource.name == splits[1]]
-
- if len(lit_ressources) == 0:
- _error_and_exit(f"There isn't any Lightning Ressource matching the name {splits[1]}.")
-
- lit_resource = lit_ressources[0]
-
- app_paths = []
- app_colors = []
-
- cloud_spaces_paths = []
- cloud_spaces_colors = []
-
- depth = len(splits)
-
- prefix = "/".join(splits[2:])
- prefix = _get_prefix(prefix, lit_resource)
-
- for artifact in _collect_artifacts(client=client, project_id=project_id, prefix=prefix):
- if str(artifact.filename).startswith("/"):
- artifact.filename = artifact.filename[1:]
-
- path = os.path.join(project_id, prefix[1:], artifact.filename)
-
- artifact_splits = path.split("/")
-
- if len(artifact_splits) <= depth + 1:
- continue
-
- path = artifact_splits[depth + 1]
-
- paths = app_paths if isinstance(lit_resource, Externalv1LightningappInstance) else cloud_spaces_paths
- colors = app_colors if isinstance(lit_resource, Externalv1LightningappInstance) else cloud_spaces_colors
-
- if path not in paths:
- paths.append(path)
-
- # display files otherwise folders
- colors.append(_FILE_COLOR if len(artifact_splits) == depth + 1 else _FOLDER_COLOR)
-
- if print:
- if app_paths and cloud_spaces_paths:
- if app_paths:
- rich.print("Lightning App")
- _print_names_with_colors(app_paths, app_colors)
-
- if cloud_spaces_paths:
- rich.print("Lightning CloudSpaces")
- _print_names_with_colors(cloud_spaces_paths, cloud_spaces_colors)
- else:
- _print_names_with_colors(app_paths + cloud_spaces_paths, app_colors + cloud_spaces_colors)
-
- return app_paths + cloud_spaces_paths
-
-
-def _add_colors(filename: str, color: Optional[str] = None) -> str:
- return f"[{color}]{filename}[/{color}]"
-
-
-def _print_names_with_colors(names: List[str], colors: List[str], padding: int = 5) -> None:
- console = Console()
- width = console.width
-
- max_L = max([len(name) for name in names] + [0]) + padding
-
- use_spacing = False
-
- if max_L * len(names) < width:
- use_spacing = True
-
- num_cols = width // max_L
-
- columns = {}
- for index, (name, color) in enumerate(zip(names, colors)):
- row = index // num_cols
- if row not in columns:
- columns[row] = []
- columns[row].append((name, color))
-
- for row_index in sorted(columns):
- row = ""
- for name, color in columns[row_index]:
- spacing = padding if use_spacing else max_L - len(name)
- spaces = " " * spacing
- row += _add_colors(name, color) + spaces
- rich.print(row)
-
-
-def _collect_artifacts(
- client: LightningClient,
- project_id: str,
- prefix: str = "",
- page_token: Optional[str] = "",
- cluster_id: Optional[str] = None,
- page_size: int = 100_000,
- tokens=None,
- include_download_url: bool = False,
-) -> Generator:
- if tokens is None:
- tokens = []
-
- if cluster_id is None:
- clusters = client.projects_service_list_project_cluster_bindings(project_id)
- for cluster in clusters.clusters:
- yield from _collect_artifacts(
- client,
- project_id,
- prefix=prefix,
- cluster_id=cluster.cluster_id,
- page_token=page_token,
- tokens=tokens,
- page_size=page_size,
- include_download_url=include_download_url,
- )
- else:
- if page_token in tokens:
- return
-
- # Note: This is triggered when the request is wrong.
- # This is currently happening due to looping through the user clusters.
- with contextlib.suppress(lightning_cloud.openapi.rest.ApiException):
- response = client.lightningapp_instance_service_list_project_artifacts(
- project_id,
- prefix=prefix,
- cluster_id=cluster_id,
- page_token=page_token,
- include_download_url=include_download_url,
- page_size=str(page_size),
- )
- for artifact in response.artifacts:
- if ".lightning-app-sync" in artifact.filename:
- continue
- yield artifact
-
- if response.next_page_token:
- tokens.append(page_token)
- yield from _collect_artifacts(
- client,
- project_id,
- prefix=prefix,
- cluster_id=cluster_id,
- page_token=response.next_page_token,
- tokens=tokens,
- )
-
-
-def _add_resource_prefix(prefix: str, resource_path: str):
- if resource_path in prefix:
- return prefix
- prefix = os.path.join(resource_path, prefix)
- if not prefix.startswith("/"):
- prefix = "/" + prefix
- return prefix
-
-
-def _get_prefix(prefix: str, lit_resource) -> str:
- if isinstance(lit_resource, Externalv1LightningappInstance):
- return _add_resource_prefix(prefix, f"lightningapps/{lit_resource.id}")
-
- return _add_resource_prefix(prefix, f"cloudspaces/{lit_resource.id}")
diff --git a/src/lightning/app/cli/commands/pwd.py b/src/lightning/app/cli/commands/pwd.py
deleted file mode 100644
index 7768309e4e6bb..0000000000000
--- a/src/lightning/app/cli/commands/pwd.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import sys
-
-from rich.live import Live
-from rich.spinner import Spinner
-from rich.text import Text
-
-from lightning.app.cli.commands.cd import _CD_FILE
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-def pwd() -> str:
- """Print your current working directory in the Lightning Cloud filesystem."""
- if sys.platform == "win32":
- print("`pwd` isn't supported on windows. Open an issue on Github.")
- sys.exit(0)
-
- with Live(Spinner("point", text=Text("pending...", style="white")), transient=True):
- root = _pwd()
-
- print(root)
-
- return root
-
-
-def _pwd() -> str:
- root = "/"
-
- if not os.path.exists(_CD_FILE):
- with open(_CD_FILE, "w") as f:
- f.write(root + "\n")
- else:
- with open(_CD_FILE) as f:
- lines = f.readlines()
- root = lines[0].replace("\n", "")
-
- return root
diff --git a/src/lightning/app/cli/commands/rm.py b/src/lightning/app/cli/commands/rm.py
deleted file mode 100644
index 587cc50469131..0000000000000
--- a/src/lightning/app/cli/commands/rm.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import contextlib
-import os
-
-import click
-import lightning_cloud
-import rich
-
-from lightning.app.cli.commands.ls import _add_colors, _get_prefix
-from lightning.app.cli.commands.pwd import _pwd
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cli_helpers import _error_and_exit
-from lightning.app.utilities.network import LightningClient
-
-logger = Logger(__name__)
-
-
-@click.argument("rm_path", required=True)
-@click.option("-r", required=False, hidden=True)
-@click.option("--recursive", required=False, hidden=True)
-def rm(rm_path: str, r: bool = False, recursive: bool = False) -> None:
- """Delete files on the Lightning Cloud filesystem."""
- root = _pwd()
-
- if rm_path in (".", ".."):
- return _error_and_exit('rm "." and ".." may not be removed')
-
- if ".." in rm_path:
- return _error_and_exit('rm ".." or higher may not be removed')
-
- root = os.path.join(root, rm_path)
- splits = [split for split in root.split("/") if split != ""]
-
- if root == "/" or len(splits) == 1:
- return _error_and_exit("rm at the project level isn't supported")
-
- client = LightningClient(retry=False)
- projects = client.projects_service_list_memberships()
-
- project = [project for project in projects.memberships if project.name == splits[0]]
-
- # This happens if the user changes cluster and the project doesn't exist.
- if len(project) == 0:
- return _error_and_exit(
- f"There isn't any Lightning Project matching the name {splits[0]}." " HINT: Use `lightning cd`."
- )
-
- project_id = project[0].project_id
-
- # Parallelise calls
- lit_apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id, async_req=True)
- lit_cloud_spaces = client.cloud_space_service_list_cloud_spaces(project_id=project_id, async_req=True)
-
- lit_apps = lit_apps.get().lightningapps
- lit_cloud_spaces = lit_cloud_spaces.get().cloudspaces
-
- lit_ressources = [lit_resource for lit_resource in lit_cloud_spaces if lit_resource.name == splits[1]]
-
- if len(lit_ressources) == 0:
- lit_ressources = [lit_resource for lit_resource in lit_apps if lit_resource.name == splits[1]]
-
- if len(lit_ressources) == 0:
- _error_and_exit(f"There isn't any Lightning Ressource matching the name {splits[1]}.")
-
- lit_resource = lit_ressources[0]
-
- prefix = "/".join(splits[2:])
- prefix = _get_prefix(prefix, lit_resource)
-
- clusters = client.projects_service_list_project_cluster_bindings(project_id)
- succeeded = False
-
- for cluster in clusters.clusters:
- with contextlib.suppress(lightning_cloud.openapi.rest.ApiException):
- client.lightningapp_instance_service_delete_project_artifact(
- project_id=project_id,
- cluster_id=cluster.cluster_id,
- filename=prefix,
- )
- succeeded = True
- break
-
- prefix = os.path.join(*splits)
-
- if succeeded:
- rich.print(_add_colors(f"Successfuly deleted `{prefix}`.", color="green"))
- return None
-
- return _error_and_exit(f"No file or folder named `{prefix}` was found.")
diff --git a/src/lightning/app/cli/component-template/.github/workflows/ci-testing.yml b/src/lightning/app/cli/component-template/.github/workflows/ci-testing.yml
deleted file mode 100644
index 16abb8f418b89..0000000000000
--- a/src/lightning/app/cli/component-template/.github/workflows/ci-testing.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-name: CI testing
-
-# see: https://help.github.com/en/actions/reference/events-that-trigger-workflows
-on:
- # Trigger the workflow on push or pull request, but only for the master branch
- push:
- branches: [main]
- pull_request:
- branches: [main]
-
-jobs:
- pytest:
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-20.04, macOS-11, windows-2019]
- python-version: [3.8]
-
- # Timeout: https://stackoverflow.com/a/59076067/4521646
- timeout-minutes: 35
-
- steps:
- - uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python-version }}
-
- # Github Actions: Run step on specific OS: https://stackoverflow.com/a/57948488/4521646
- - name: Setup macOS
- if: runner.os == 'macOS'
- run: |
- brew install libomp # https://github.com/pytorch/pytorch/issues/20030
-
- - name: Get pip cache dir
- id: pip-cache
- run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
-
- - name: Cache pip
- uses: actions/cache@v2
- with:
- path: ${{ steps.pip-cache.outputs.dir }}
- key: ${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}
- restore-keys: |
- ${{ runner.os }}-py${{ matrix.python-version }}-
-
- - name: Clone Template React UI Repo
- uses: actions/checkout@v3
- with:
- repository: Lightning-AI/lightning
- token: ${{ secrets.PAT_GHOST }}
- ref: "master"
- path: lightning
-
- - name: Install Lightning
- run: |
- cd lightning
- pip install -r requirements.txt
- pip install -e .
- shell: bash
-
- - name: Install dependencies
- run: |
- python --version
- pip --version
- pip install --requirement requirements.txt --upgrade --quiet --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
- pip install --requirement tests/requirements.txt --quiet
- pip list
- shell: bash
-
- - name: Tests
- run: |
- coverage run --source placeholdername -m py.test placeholdername tests -v --junitxml=junit/test-results-${{ runner.os }}-${{ matrix.python-version }}.xml
-
- - name: Statistics
- if: success()
- run: |
- coverage report
diff --git a/src/lightning/app/cli/component-template/.gitignore b/src/lightning/app/cli/component-template/.gitignore
deleted file mode 100644
index 70ba25888435f..0000000000000
--- a/src/lightning/app/cli/component-template/.gitignore
+++ /dev/null
@@ -1,157 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-*install-app*
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-
-*.egg
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Sphinx documentation
-docs/_build/
-docs/source/api/
-docs/source/*.md
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.local_env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# PyCharm
-.idea/
-
-# Lightning logs
-lightning_logs
-*.gz
-.DS_Store
-.*_submit.py
-.vscode
-
-MNIST
-*.pt
-.storage/
-.shared/
-infra
-data
-coverage.*
-# Frontend build artifacts
-*lightning/app/ui*
-gradio_cached_examples
-/docs/source/api_reference/generated/*
-examples/my_own_leaderboard/submissions/*
-docs/source/api_reference/generated/*
-*.ckpt
-redis-stable
-node_modules
-*.rdb
-*.webm
-*hars
-examples/quick_start/*
-examples/quick_start
-examples/template_react_ui/*
-examples/template_react_ui
-# Ignore external components
-lightning/app/components/*
-!lightning/app/components/python
-!lightning/app/components/serve
-!lightning/app/components/__init__.py
-!lightning/app/components/README.md
-train_script.py
-*return_values*
-scratch
-storage
diff --git a/src/lightning/app/cli/component-template/LICENSE b/src/lightning/app/cli/component-template/LICENSE
deleted file mode 100644
index 261eeb9e9f8b2..0000000000000
--- a/src/lightning/app/cli/component-template/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/src/lightning/app/cli/component-template/README.md b/src/lightning/app/cli/component-template/README.md
deleted file mode 100644
index 1d700e286461b..0000000000000
--- a/src/lightning/app/cli/component-template/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# placeholdername component
-
-This ⚡ [Lightning component](https://lightning.ai/) ⚡ was generated automatically with:
-
-```bash
-lightning_app init component placeholdername
-```
-
-## To run placeholdername
-
-First, install placeholdername (warning: this component has not been officially approved on the lightning gallery):
-
-```bash
-lightning_app install component https://github.com/theUser/placeholdername
-```
-
-Once the app is installed, use it in an app:
-
-```python
-from placeholdername import TemplateComponent
-import lightning as L
-
-
-class LitApp(L.LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.placeholdername = TemplateComponent()
-
- def run(self):
- print("this is a simple Lightning app to verify your component is working as expected")
- self.placeholdername.run()
-
-
-app = L.LightningApp(LitApp())
-```
diff --git a/src/lightning/app/cli/component-template/app.py b/src/lightning/app/cli/component-template/app.py
deleted file mode 100644
index 0a10532204043..0000000000000
--- a/src/lightning/app/cli/component-template/app.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from lightning.app import LightningApp, LightningFlow
-from placeholdername import TemplateComponent
-
-
-class LitApp(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.placeholdername = TemplateComponent()
-
- def run(self):
- print("this is a simple Lightning app to verify your component is working as expected")
- self.placeholdername.run()
-
-
-app = LightningApp(LitApp())
diff --git a/src/lightning/app/cli/component-template/placeholdername/__init__.py b/src/lightning/app/cli/component-template/placeholdername/__init__.py
deleted file mode 100644
index 92b4ef47d8062..0000000000000
--- a/src/lightning/app/cli/component-template/placeholdername/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from placeholdername.component import TemplateComponent
-
-__all__ = ["TemplateComponent"]
diff --git a/src/lightning/app/cli/component-template/placeholdername/component.py b/src/lightning/app/cli/component-template/placeholdername/component.py
deleted file mode 100644
index 251a4e10c6a9f..0000000000000
--- a/src/lightning/app/cli/component-template/placeholdername/component.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from lightning.app import LightningWork
-
-
-class TemplateComponent(LightningWork):
- def __init__(self) -> None:
- super().__init__()
- self.value = 0
-
- def run(self):
- self.value += 1
- print("welcome to your work component")
- print("this is running inside a work")
diff --git a/src/lightning/app/cli/component-template/requirements.txt b/src/lightning/app/cli/component-template/requirements.txt
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/component-template/setup.py b/src/lightning/app/cli/component-template/setup.py
deleted file mode 100644
index 78631901190b2..0000000000000
--- a/src/lightning/app/cli/component-template/setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python
-
-from setuptools import find_packages, setup
-
-setup(
- name="placeholdername",
- version="0.0.0",
- description="⚡ Lightning component ⚡ generated with command: lightning_app init component",
- author="",
- author_email="",
- # REPLACE WITH YOUR OWN GITHUB PROJECT LINK
- url="https://github.com/Lightning-AI/lightning-component-template",
- install_requires=[],
- packages=find_packages(),
-)
diff --git a/src/lightning/app/cli/component-template/tests/README.md b/src/lightning/app/cli/component-template/tests/README.md
deleted file mode 100644
index bef681691185d..0000000000000
--- a/src/lightning/app/cli/component-template/tests/README.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Run tests
-
-To run the tests:
-
-```bash
-# go to your component folder
-cd placeholdername
-
-# go to tests folder
-cd tests
-
-# install testing deps
-pip install -r requirements.txt
-
-# run tests
-pytest .
-```
diff --git a/src/lightning/app/cli/component-template/tests/__init__.py b/src/lightning/app/cli/component-template/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/component-template/tests/requirements.txt b/src/lightning/app/cli/component-template/tests/requirements.txt
deleted file mode 100644
index 3185d1c44f033..0000000000000
--- a/src/lightning/app/cli/component-template/tests/requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-coverage
-codecov>=2.1
-pytest>=5.0.0
-pytest-cov
-pytest-flake8
-flake8
-check-manifest
-twine==4.0.1
diff --git a/src/lightning/app/cli/component-template/tests/test_placeholdername_component.py b/src/lightning/app/cli/component-template/tests/test_placeholdername_component.py
deleted file mode 100644
index 6b9c28845749c..0000000000000
--- a/src/lightning/app/cli/component-template/tests/test_placeholdername_component.py
+++ /dev/null
@@ -1,14 +0,0 @@
-r"""To test a lightning component:
-
-1. Init the component.
-2. call .run()
-
-"""
-
-from placeholdername.component import TemplateComponent
-
-
-def test_placeholder_component():
- messenger = TemplateComponent()
- messenger.run()
- assert messenger.value == 1
diff --git a/src/lightning/app/cli/connect/__init__.py b/src/lightning/app/cli/connect/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/connect/app.py b/src/lightning/app/cli/connect/app.py
deleted file mode 100644
index e3f0d0b151bb5..0000000000000
--- a/src/lightning/app/cli/connect/app.py
+++ /dev/null
@@ -1,387 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import json
-import os
-import shutil
-import sys
-from subprocess import Popen
-from typing import List, Optional, Tuple
-
-import click
-import psutil
-from lightning_utilities.core.imports import package_available
-from rich.progress import Progress
-
-from lightning.app.utilities.cli_helpers import _get_app_display_name, _LightningAppOpenAPIRetriever
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.enum import OpenAPITags
-from lightning.app.utilities.log import get_logfile
-from lightning.app.utilities.network import LightningClient
-
-_HOME = os.path.expanduser("~")
-_PPID = os.getenv("LIGHTNING_CONNECT_PPID", str(psutil.Process(os.getpid()).ppid()))
-_LIGHTNING_CONNECTION = os.path.join(_HOME, ".lightning", "lightning_connection")
-_LIGHTNING_CONNECTION_FOLDER = os.path.join(_LIGHTNING_CONNECTION, _PPID)
-
-
-@click.argument("app_name_or_id", required=True)
-def connect_app(app_name_or_id: str):
- """Connect your local terminal to a running lightning app.
-
- After connecting, the lightning CLI will respond to commands exposed by the app.
-
- Example:
-
- \b
- # connect to an app named pizza-cooker-123
- lightning connect pizza-cooker-123
- \b
- # this will now show the commands exposed by pizza-cooker-123
- lightning --help
- \b
- # while connected, you can run the cook-pizza command exposed
- # by pizza-cooker-123.BTW, this should arguably generate an exception :-)
- lightning cook-pizza --flavor pineapple
- \b
- # once done, disconnect and go back to the standard lightning CLI commands
- lightning disconnect
-
- """
- from lightning.app.utilities.commands.base import _download_command
-
- _clean_lightning_connection()
-
- if not os.path.exists(_LIGHTNING_CONNECTION_FOLDER):
- os.makedirs(_LIGHTNING_CONNECTION_FOLDER)
-
- connected_file = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "connect.txt")
-
- matched_connection_path = _scan_lightning_connections(app_name_or_id)
-
- if os.path.exists(connected_file):
- with open(connected_file) as f:
- result = f.readlines()[0].replace("\n", "")
-
- if result == app_name_or_id:
- if app_name_or_id == "localhost":
- click.echo("You are connected to the local Lightning App.")
- else:
- click.echo(f"You are already connected to the cloud Lightning App: {app_name_or_id}.")
- else:
- disconnect_app()
- connect_app(app_name_or_id)
-
- elif app_name_or_id.startswith("localhost"):
- with Progress() as progress_bar:
- connecting = progress_bar.add_task("[magenta]Setting things up for you...", total=1.0)
-
- if app_name_or_id != "localhost":
- raise Exception("You need to pass localhost to connect to the local Lightning App.")
-
- retriever = _LightningAppOpenAPIRetriever(None)
-
- if retriever.api_commands is None:
- raise Exception(f"Connection wasn't successful. Is your app {app_name_or_id} running?")
-
- increment = 1 / (1 + len(retriever.api_commands))
-
- progress_bar.update(connecting, advance=increment)
-
- commands_folder = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "commands")
- if not os.path.exists(commands_folder):
- os.makedirs(commands_folder)
-
- _write_commands_metadata(retriever.api_commands)
-
- with open(os.path.join(commands_folder, "openapi.json"), "w") as f:
- json.dump(retriever.openapi, f)
-
- _install_missing_requirements(retriever)
-
- for command_name, metadata in retriever.api_commands.items():
- if "cls_path" in metadata:
- target_file = os.path.join(commands_folder, f"{command_name.replace(' ', '_')}.py")
- _download_command(
- command_name,
- metadata["cls_path"],
- metadata["cls_name"],
- None,
- target_file=target_file,
- )
- else:
- with open(os.path.join(commands_folder, f"{command_name}.txt"), "w") as f:
- f.write(command_name)
-
- progress_bar.update(connecting, advance=increment)
-
- with open(connected_file, "w") as f:
- f.write(app_name_or_id + "\n")
-
- click.echo("The lightning App CLI now responds to app commands. Use 'lightning_app --help' to see them.")
- click.echo(" ")
-
- Popen(
- f"LIGHTNING_CONNECT_PPID={_PPID} {sys.executable} -m lightning_app --help",
- shell=True,
- stdout=sys.stdout,
- stderr=sys.stderr,
- ).wait()
-
- elif matched_connection_path:
- matched_connected_file = os.path.join(matched_connection_path, "connect.txt")
- matched_commands = os.path.join(matched_connection_path, "commands")
- if os.path.isdir(matched_commands):
- commands = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "commands")
- shutil.copytree(matched_commands, commands)
- shutil.copy(matched_connected_file, connected_file)
-
- click.echo("The lightning App CLI now responds to app commands. Use 'lightning_app --help' to see them.")
- click.echo(" ")
-
- Popen(
- f"LIGHTNING_CONNECT_PPID={_PPID} {sys.executable} -m lightning_app --help",
- shell=True,
- stdout=sys.stdout,
- stderr=sys.stderr,
- ).wait()
-
- else:
- with Progress() as progress_bar:
- connecting = progress_bar.add_task("[magenta]Setting things up for you...", total=1.0)
-
- retriever = _LightningAppOpenAPIRetriever(app_name_or_id)
-
- if not retriever.api_commands:
- client = LightningClient(retry=False)
- project = _get_project(client)
- apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project.project_id)
- click.echo(
- "We didn't find a matching App. Here are the available Apps that you can "
- f"connect to {[_get_app_display_name(app) for app in apps.lightningapps]}."
- )
- return
-
- increment = 1 / (1 + len(retriever.api_commands))
-
- progress_bar.update(connecting, advance=increment)
-
- _install_missing_requirements(retriever)
-
- commands_folder = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "commands")
- if not os.path.exists(commands_folder):
- os.makedirs(commands_folder)
-
- _write_commands_metadata(retriever.api_commands)
-
- for command_name, metadata in retriever.api_commands.items():
- if "cls_path" in metadata:
- target_file = os.path.join(commands_folder, f"{command_name}.py")
- _download_command(
- command_name,
- metadata["cls_path"],
- metadata["cls_name"],
- retriever.app_id,
- target_file=target_file,
- )
- else:
- with open(os.path.join(commands_folder, f"{command_name}.txt"), "w") as f:
- f.write(command_name)
-
- progress_bar.update(connecting, advance=increment)
-
- with open(connected_file, "w") as f:
- f.write(retriever.app_name + "\n")
- f.write(retriever.app_id + "\n")
-
- click.echo("The lightning App CLI now responds to app commands. Use 'lightning_app --help' to see them.")
- click.echo(" ")
-
- Popen(
- f"LIGHTNING_CONNECT_PPID={_PPID} {sys.executable} -m lightning_app --help",
- shell=True,
- stdout=sys.stdout,
- stderr=sys.stderr,
- ).wait()
-
-
-def disconnect_app(logout: bool = False):
- """Disconnect from an App."""
- _clean_lightning_connection()
-
- connected_file = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "connect.txt")
- if os.path.exists(connected_file):
- with open(connected_file) as f:
- result = f.readlines()[0].replace("\n", "")
-
- os.remove(connected_file)
- commands_folder = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "commands")
- if os.path.exists(commands_folder):
- shutil.rmtree(commands_folder)
-
- if result == "localhost":
- click.echo("You are disconnected from the local Lightning App.")
- else:
- click.echo(f"You are disconnected from the cloud Lightning App: {result}.")
- else:
- if not logout:
- click.echo(
- "You aren't connected to any Lightning App. "
- "Please use `lightning_app connect app_name_or_id` to connect to one."
- )
-
-
-def _read_connected_file(connected_file):
- if os.path.exists(connected_file):
- with open(connected_file) as f:
- lines = [line.replace("\n", "") for line in f.readlines()]
- if len(lines) == 2:
- return lines[0], lines[1]
- return lines[0], None
- return None, None
-
-
-def _retrieve_connection_to_an_app() -> Tuple[Optional[str], Optional[str]]:
- connected_file = os.path.join(_LIGHTNING_CONNECTION_FOLDER, "connect.txt")
- return _read_connected_file(connected_file)
-
-
-def _get_commands_folder() -> str:
- return os.path.join(_LIGHTNING_CONNECTION_FOLDER, "commands")
-
-
-def _write_commands_metadata(api_commands):
- metadata = dict(api_commands.items())
- metadata_path = os.path.join(_get_commands_folder(), ".meta.json")
- with open(metadata_path, "w") as f:
- json.dump(metadata, f)
-
-
-def _get_commands_metadata():
- metadata_path = os.path.join(_get_commands_folder(), ".meta.json")
- with open(metadata_path) as f:
- return json.load(f)
-
-
-def _resolve_command_path(command: str) -> str:
- return os.path.join(_get_commands_folder(), f"{command}.py")
-
-
-def _list_app_commands(echo: bool = True) -> List[str]:
- metadata = _get_commands_metadata()
- metadata = {key.replace("_", " "): value for key, value in metadata.items()}
-
- command_names = sorted(metadata.keys())
- if not command_names:
- click.echo("The current Lightning App doesn't have commands.")
- return []
-
- app_info = metadata[command_names[0]].get("app_info", None)
-
- title, description, on_connect_end = "Lightning", None, None
- if app_info:
- title = app_info.get("title")
- description = app_info.get("description")
- on_connect_end = app_info.get("on_connect_end")
-
- if echo:
- click.echo(f"{title} App")
- if description:
- click.echo("")
- click.echo("Description:")
- if description.endswith("\n"):
- description = description[:-2]
- click.echo(f" {description}")
- click.echo("")
- click.echo("Commands:")
- max_length = max(len(n) for n in command_names)
- for command_name in command_names:
- padding = (max_length + 1 - len(command_name)) * " "
- click.echo(f" {command_name}{padding}{metadata[command_name].get('description', '')}")
- if "LIGHTNING_CONNECT_PPID" in os.environ and on_connect_end:
- if on_connect_end.endswith("\n"):
- on_connect_end = on_connect_end[:-2]
- click.echo(on_connect_end)
- return command_names
-
-
-def _install_missing_requirements(
- retriever: _LightningAppOpenAPIRetriever,
- fail_if_missing: bool = False,
-):
- requirements = set()
- for metadata in retriever.api_commands.values():
- if metadata["tag"] == OpenAPITags.APP_CLIENT_COMMAND:
- for req in metadata.get("requirements", []) or []:
- requirements.add(req)
-
- if requirements:
- missing_requirements = []
- for req in requirements:
- if not (package_available(req) or package_available(req.replace("-", "_"))):
- missing_requirements.append(req)
-
- if missing_requirements:
- if fail_if_missing:
- missing_requirements = " ".join(missing_requirements)
- print(f"The command failed as you are missing the following requirements: `{missing_requirements}`.")
- sys.exit(0)
-
- for req in missing_requirements:
- std_out_out = get_logfile("output.log")
- with open(std_out_out, "wb") as stdout:
- Popen(
- f"{sys.executable} -m pip install {req}",
- shell=True,
- stdout=stdout,
- stderr=stdout,
- ).wait()
- os.remove(std_out_out)
-
-
-def _clean_lightning_connection():
- if not os.path.exists(_LIGHTNING_CONNECTION):
- return
-
- for ppid in os.listdir(_LIGHTNING_CONNECTION):
- try:
- psutil.Process(int(ppid))
- except (psutil.NoSuchProcess, ValueError):
- connection = os.path.join(_LIGHTNING_CONNECTION, str(ppid))
- if os.path.exists(connection):
- shutil.rmtree(connection)
-
-
-def _scan_lightning_connections(app_name_or_id):
- if not os.path.exists(_LIGHTNING_CONNECTION):
- return None
-
- for ppid in os.listdir(_LIGHTNING_CONNECTION):
- try:
- psutil.Process(int(ppid))
- except (psutil.NoSuchProcess, ValueError):
- continue
-
- connection_path = os.path.join(_LIGHTNING_CONNECTION, str(ppid))
-
- connected_file = os.path.join(connection_path, "connect.txt")
- curr_app_name, curr_app_id = _read_connected_file(connected_file)
-
- if not curr_app_name:
- continue
-
- if app_name_or_id in (curr_app_name, curr_app_id):
- return connection_path
-
- return None
diff --git a/src/lightning/app/cli/connect/data.py b/src/lightning/app/cli/connect/data.py
deleted file mode 100644
index 6069432ddb187..0000000000000
--- a/src/lightning/app/cli/connect/data.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# Copyright The Lightning team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import ast
-import sys
-
-import click
-import lightning_cloud
-import rich
-from rich.live import Live
-from rich.spinner import Spinner
-from rich.text import Text
-
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cli_helpers import _error_and_exit
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.network import LightningClient
-
-logger = Logger(__name__)
-
-
-@click.argument("name", required=True)
-@click.option("--region", help="The AWS region of your bucket. Example: `us-west-1`.", required=True)
-@click.option(
- "--source", help="The URL path to your AWS S3 folder. Example: `s3://pl-flash-data/images/`.", required=True
-)
-@click.option(
- "--secret_arn_name",
- help="The name of role stored as a secret on Lightning AI to access your data. "
- "Learn more with https://gist.github.com/tchaton/12ad4b788012e83c0eb35e6223ae09fc. "
- "Example: `my_role`.",
- required=False,
-)
-@click.option(
- "--destination", help="Where your data should appear in the cloud. Currently not supported.", required=False
-)
-@click.option("--project_name", help="The project name on which to create the data connection.", required=False)
-def connect_data(
- name: str,
- region: str,
- source: str,
- secret_arn_name: str = "",
- destination: str = "",
- project_name: str = "",
-) -> None:
- """Create a new data connection."""
-
- from lightning_cloud.openapi import Create, V1AwsDataConnection
-
- if sys.platform == "win32":
- _error_and_exit("Data connection isn't supported on windows. Open an issue on Github.")
-
- with Live(Spinner("point", text=Text("pending...", style="white")), transient=True) as live:
- live.stop()
-
- client = LightningClient(retry=False)
- projects = client.projects_service_list_memberships()
-
- project_id = None
-
- for project in projects.memberships:
- if project.name == project_name:
- project_id = project.project_id
- break
-
- if project_id is None:
- project_id = _get_project(client).project_id
-
- if not source.startswith("s3://"):
- return _error_and_exit(
- "Only public S3 folders are supported for now. Please, open a Github issue with your use case."
- )
-
- try:
- client.data_connection_service_create_data_connection(
- body=Create(
- name=name,
- aws=V1AwsDataConnection(
- region=region,
- source=source,
- destination=destination,
- secret_arn_name=secret_arn_name,
- ),
- ),
- project_id=project_id,
- )
-
- # Note: Expose through lightning show data {DATA_NAME}
- # response = client.data_connection_service_list_data_connection_artifacts(
- # project_id=project_id,
- # id=response.id,
- # )
- except lightning_cloud.openapi.rest.ApiException as e:
- message = ast.literal_eval(e.body.decode("utf-8"))["message"]
- _error_and_exit(f"The data connection creation failed. Message: {message}")
-
- rich.print(f"[green]Succeeded[/green]: You have created a new data connection {name}.")
- return None
diff --git a/src/lightning/app/cli/core.py b/src/lightning/app/cli/core.py
deleted file mode 100644
index 6d54c31426ee1..0000000000000
--- a/src/lightning/app/cli/core.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-
-from rich.table import Table
-
-
-class Formatable(abc.ABC):
- @abc.abstractmethod
- def as_table(self) -> Table:
- pass
-
- @abc.abstractmethod
- def as_json(self) -> str:
- pass
diff --git a/src/lightning/app/cli/lightning_cli.py b/src/lightning/app/cli/lightning_cli.py
deleted file mode 100644
index 6aa84063ab93f..0000000000000
--- a/src/lightning/app/cli/lightning_cli.py
+++ /dev/null
@@ -1,503 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import sys
-from pathlib import Path
-from typing import Tuple, Union
-
-import click
-from requests.exceptions import ConnectionError
-
-import lightning.app.core.constants as constants
-from lightning.app import __version__ as ver
-from lightning.app.cli import cmd_init, cmd_install, cmd_pl_init, cmd_react_ui_init
-from lightning.app.cli.commands.app_commands import _run_app_command
-from lightning.app.cli.commands.cd import cd
-from lightning.app.cli.commands.cp import cp
-from lightning.app.cli.commands.logs import logs
-from lightning.app.cli.commands.ls import ls
-from lightning.app.cli.commands.pwd import pwd
-from lightning.app.cli.commands.rm import rm
-from lightning.app.cli.connect.app import (
- _list_app_commands,
- _retrieve_connection_to_an_app,
- connect_app,
- disconnect_app,
-)
-from lightning.app.cli.connect.data import connect_data
-from lightning.app.cli.lightning_cli_delete import delete
-from lightning.app.cli.lightning_cli_launch import launch
-from lightning.app.cli.lightning_cli_list import get_list
-from lightning.app.core.constants import (
- APP_SERVER_HOST,
- APP_SERVER_PORT,
- ENABLE_APP_COMMENT_COMMAND_EXECUTION,
- get_lightning_cloud_url,
-)
-from lightning.app.launcher.launcher import (
- run_lightning_flow,
- run_lightning_work,
- serve_frontend,
- start_application_server,
- start_flow_and_servers,
-)
-from lightning.app.runners.cloud import CloudRuntime
-from lightning.app.runners.runtime import dispatch
-from lightning.app.runners.runtime_type import RuntimeType
-from lightning.app.utilities.app_commands import run_app_commands
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cli_helpers import (
- _check_environment_and_redirect,
- _check_version_and_upgrade,
- _format_input_env_variables,
-)
-from lightning.app.utilities.exceptions import _ApiExceptionHandler
-from lightning.app.utilities.login import Auth
-from lightning.app.utilities.port import _find_lit_app_port
-
-logger = Logger(__name__)
-
-
-def main() -> None:
- # Check environment and versions if not in the cloud and not testing
- is_testing = bool(int(os.getenv("LIGHTING_TESTING", "0")))
- if not is_testing and "LIGHTNING_APP_STATE_URL" not in os.environ:
- try:
- # Enforce running in PATH Python
- _check_environment_and_redirect()
-
- # Check for newer versions and upgrade
- _check_version_and_upgrade()
- except SystemExit:
- raise
- except Exception:
- # Note: We intentionally ignore all exceptions here so that we never panic if one of the above calls fails.
- # If they fail for some reason users should still be able to continue with their command.
- click.echo(
- "We encountered an unexpected problem while checking your environment."
- "We will still proceed with the command, however, there is a chance that errors may occur."
- )
-
- # 1: Handle connection to a Lightning App.
- if len(sys.argv) > 1 and sys.argv[1] in ("connect", "disconnect", "logout"):
- _main()
- else:
- # 2: Collect the connection a Lightning App.
- app_name, app_id = _retrieve_connection_to_an_app()
- if app_name:
- # 3: Handle development use case.
- is_local_app = app_name == "localhost"
- if sys.argv[1:3] == ["run", "app"] or (
- sys.argv[1:3] == ["show", "logs"] and "show logs" not in _list_app_commands(False)
- ):
- _main()
- else:
- if is_local_app:
- message = "You are connected to the local Lightning App."
- else:
- message = f"You are connected to the cloud Lightning App: {app_name}."
-
- if (len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help"]) or len(sys.argv) == 1:
- _list_app_commands()
- else:
- _run_app_command(app_name, app_id)
-
- click.echo()
- click.echo(message + " Return to the primary CLI with `lightning_app disconnect`.")
- else:
- _main()
-
-
-@click.group(cls=_ApiExceptionHandler)
-@click.version_option(ver)
-def _main() -> None:
- pass
-
-
-@_main.group()
-def show() -> None:
- """Show given resource."""
- pass
-
-
-@_main.group()
-def connect() -> None:
- """Connect apps and data."""
- pass
-
-
-@_main.group()
-def disconnect() -> None:
- """Disconnect apps."""
- pass
-
-
-connect.command("app")(connect_app)
-disconnect.command("app")(disconnect_app)
-connect.command("data", hidden=True)(connect_data)
-_main.command(hidden=True)(ls)
-_main.command(hidden=True)(cd)
-_main.command(hidden=True)(cp)
-_main.command(hidden=True)(pwd)
-_main.command(hidden=True)(rm)
-show.command()(logs)
-
-
-@_main.command()
-def login() -> None:
- """Log in to your lightning.ai account."""
- auth = Auth()
- auth.clear()
-
- try:
- auth.authenticate()
- except ConnectionError:
- click.echo(f"Unable to connect to {get_lightning_cloud_url()}. Please check your internet connection.")
- exit(1)
-
-
-@_main.command()
-def logout() -> None:
- """Log out of your lightning.ai account."""
- Auth().clear()
- disconnect_app(logout=True)
-
-
-def _run_app(
- file: str,
- cloud: bool,
- without_server: bool,
- no_cache: bool,
- name: str,
- blocking: bool,
- open_ui: bool,
- env: tuple,
- secret: tuple,
- run_app_comment_commands: bool,
- enable_basic_auth: str,
-) -> None:
- if not os.path.exists(file):
- original_file = file
- file = cmd_install.gallery_apps_and_components(file, True, "latest", overwrite=True) # type: ignore[assignment] # E501
- if file is None:
- click.echo(f"The provided entrypoint `{original_file}` doesn't exist.")
- sys.exit(1)
- run_app_comment_commands = True
-
- runtime_type = RuntimeType.CLOUD if cloud else RuntimeType.MULTIPROCESS
-
- # Cloud specific validations
- if runtime_type != RuntimeType.CLOUD:
- if no_cache:
- raise click.ClickException(
- "Caching is a property of apps running in cloud. "
- "Using the flag --no-cache in local execution is not supported."
- )
- if secret:
- raise click.ClickException(
- "Secrets can only be used for apps running in cloud. "
- "Using the option --secret in local execution is not supported."
- )
- if (ENABLE_APP_COMMENT_COMMAND_EXECUTION or run_app_comment_commands) and file is not None:
- run_app_commands(str(file))
-
- env_vars = _format_input_env_variables(env)
- os.environ.update(env_vars)
-
- secrets = _format_input_env_variables(secret)
-
- port = _find_lit_app_port(constants.APP_SERVER_PORT)
- constants.APP_SERVER_PORT = port
-
- click.echo("Your Lightning App is starting. This won't take long.")
-
- # TODO: Fixme when Grid utilities are available.
- # And refactor test_lightning_run_app_cloud
- file_path = Path(file)
- dispatch(
- file_path,
- runtime_type,
- start_server=not without_server,
- no_cache=no_cache,
- blocking=blocking,
- open_ui=open_ui,
- name=name,
- env_vars=env_vars,
- secrets=secrets,
- run_app_comment_commands=run_app_comment_commands,
- enable_basic_auth=enable_basic_auth,
- port=port,
- )
- if runtime_type == RuntimeType.CLOUD:
- click.echo("Application is ready in the cloud")
-
-
-@_main.group()
-def run() -> None:
- """Run a Lightning application locally or on the cloud."""
-
-
-@run.command("app")
-@click.argument("file", type=str)
-@click.option("--cloud", type=bool, default=False, is_flag=True)
-@click.option("--name", help="The current application name", default="", type=str)
-@click.option("--without-server", is_flag=True, default=False)
-@click.option(
- "--no-cache",
- is_flag=True,
- default=False,
- help="Disable caching of packages " "installed from requirements.txt",
-)
-@click.option("--blocking", "blocking", type=bool, default=False)
-@click.option(
- "--open-ui",
- type=bool,
- default=True,
- help="Decide whether to launch the app UI in a web browser",
-)
-@click.option("--env", type=str, default=[], multiple=True, help="Environment variables to be set for the app.")
-@click.option("--secret", type=str, default=[], multiple=True, help="Secret variables to be set for the app.")
-@click.option("--app_args", type=str, default=[], multiple=True, help="Collection of arguments for the app.")
-@click.option(
- "--setup",
- "-s",
- "run_app_comment_commands",
- is_flag=True,
- default=False,
- help="run environment setup commands from the app comments.",
-)
-@click.option(
- "--enable-basic-auth",
- type=str,
- default="",
- help="Enable basic authentication for the app and use credentials provided in the format username:password",
-)
-def run_app(
- file: str,
- cloud: bool,
- without_server: bool,
- no_cache: bool,
- name: str,
- blocking: bool,
- open_ui: bool,
- env: tuple,
- secret: tuple,
- app_args: tuple,
- run_app_comment_commands: bool,
- enable_basic_auth: str,
-) -> None:
- """Run an app from a file."""
- _run_app(
- file,
- cloud,
- without_server,
- no_cache,
- name,
- blocking,
- open_ui,
- env,
- secret,
- run_app_comment_commands,
- enable_basic_auth,
- )
-
-
-@_main.command("open", hidden=True)
-@click.argument("path", type=str, default=".")
-@click.option("--name", help="The name to use for the CloudSpace", default="", type=str)
-def open(path: str, name: str) -> None:
- """Open files or folders from your machine on the cloud."""
- if not os.path.exists(path):
- click.echo(f"The provided path `{path}` doesn't exist.")
- sys.exit(1)
-
- runtime = CloudRuntime(entrypoint=Path(path))
- runtime.open(name)
-
-
-_main.add_command(get_list)
-_main.add_command(delete)
-_main.add_command(launch)
-_main.add_command(cmd_install.install)
-
-
-@_main.group()
-def init() -> None:
- """Init a Lightning App and/or component."""
-
-
-@init.command("app")
-@click.argument("name", type=str, required=False)
-def init_app(name: str) -> None:
- cmd_init.app(name)
-
-
-@init.command("pl-app")
-@click.argument("source", nargs=-1)
-@click.option(
- "--name",
- "-n",
- type=str,
- default="pl-app",
- help="The name of the folder where the app code will be. Default: pl-app",
-)
-@click.option(
- "--overwrite",
- "-f",
- is_flag=True,
- default=False,
- help="When set, overwrite the output directory without asking if it already exists.",
-)
-def init_pl_app(source: Union[Tuple[str], Tuple[str, str]], name: str, overwrite: bool = False) -> None:
- """Create an app from your PyTorch Lightning source files."""
- if len(source) == 1:
- script_path = source[0]
- source_dir = str(Path(script_path).resolve().parent)
- elif len(source) == 2:
- # enable type checking once https://github.com/python/mypy/issues/1178 is available
- source_dir, script_path = source
- else:
- click.echo(
- f"Incorrect number of arguments. You passed ({', '.join(source)}) but only either one argument"
- f" (script path) or two arguments (root dir, script path) are allowed. Examples:\n"
- f"lightning init pl-app ./path/to/script.py\n"
- f"lightning init pl-app ./code ./code/path/to/script.py",
- err=True,
- )
- raise SystemExit(1)
-
- cmd_pl_init.pl_app(source_dir=source_dir, script_path=script_path, name=name, overwrite=overwrite)
-
-
-@init.command("component")
-@click.argument("name", type=str, required=False)
-def init_component(name: str) -> None:
- cmd_init.component(name)
-
-
-@init.command("react-ui")
-@click.option(
- "--dest_dir",
- "-dest_dir",
- type=str,
- help="optional destination directory to create the react ui",
-)
-def init_react_ui(dest_dir: str) -> None:
- """Create a react UI to give a Lightning component a React.js web user interface (UI)"""
- cmd_react_ui_init.react_ui(dest_dir)
-
-
-def _prepare_file(file: str) -> str:
- exists = os.path.exists(file)
- if exists:
- return file
-
- raise FileNotFoundError(f"The provided file {file} hasn't been found.")
-
-
-@run.command("server")
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-@click.option("--host", help="Application running host", default=APP_SERVER_HOST, type=str)
-@click.option("--port", help="Application running port", default=APP_SERVER_PORT, type=int)
-def run_server(file: str, queue_id: str, host: str, port: int) -> None:
- """It takes the application file as input, build the application object and then use that to run the application
- server.
-
- This is used by the cloud runners to start the status server for the application
-
- """
- logger.debug(f"Run Server: {file} {queue_id} {host} {port}")
- start_application_server(file, host, port, queue_id=queue_id)
-
-
-@run.command("flow")
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-@click.option("--base-url", help="Base url at which the app server is hosted", default="")
-def run_flow(file: str, queue_id: str, base_url: str) -> None:
- """It takes the application file as input, build the application object, proxy all the work components and then run
- the application flow defined in the root component.
-
- It does exactly what a singleprocess dispatcher would do but with proxied work components.
-
- """
- logger.debug(f"Run Flow: {file} {queue_id} {base_url}")
- run_lightning_flow(file, queue_id=queue_id, base_url=base_url)
-
-
-@run.command("work")
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--work-name", type=str)
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-def run_work(file: str, work_name: str, queue_id: str) -> None:
- """Unlike other entrypoints, this command will take the file path or module details for a work component and run
- that by fetching the states from the queues."""
- logger.debug(f"Run Work: {file} {work_name} {queue_id}")
- run_lightning_work(
- file=file,
- work_name=work_name,
- queue_id=queue_id,
- )
-
-
-@run.command("frontend")
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--flow-name")
-@click.option("--host")
-@click.option("--port", type=int)
-def run_frontend(file: str, flow_name: str, host: str, port: int) -> None:
- """Serve the frontend specified by the given flow."""
- logger.debug(f"Run Frontend: {file} {flow_name} {host}")
- serve_frontend(file=file, flow_name=flow_name, host=host, port=port)
-
-
-@run.command("flow-and-servers")
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-@click.option("--base-url", help="Base url at which the app server is hosted", default="")
-@click.option("--host", help="Application running host", default=APP_SERVER_HOST, type=str)
-@click.option("--port", help="Application running port", default=APP_SERVER_PORT, type=int)
-@click.option(
- "--flow-port",
- help="Pair of flow name and frontend port",
- type=(str, int),
- multiple=True,
-)
-def run_flow_and_servers(
- file: str,
- base_url: str,
- queue_id: str,
- host: str,
- port: int,
- flow_port: Tuple[Tuple[str, int]],
-) -> None:
- """It takes the application file as input, build the application object and then use that to run the application
- flow defined in the root component, the application server and all the flow frontends.
-
- This is used by the cloud runners to start the flow, the status server and all frontends for the application
-
- """
- logger.debug(f"Run Flow: {file} {queue_id} {base_url}")
- logger.debug(f"Run Server: {file} {queue_id} {host} {port}.")
- logger.debug(f"Run Frontend's: {flow_port}")
- start_flow_and_servers(
- entrypoint_file=file,
- base_url=base_url,
- queue_id=queue_id,
- host=host,
- port=port,
- flow_names_and_ports=flow_port,
- )
diff --git a/src/lightning/app/cli/lightning_cli_delete.py b/src/lightning/app/cli/lightning_cli_delete.py
deleted file mode 100644
index 179e5b6fc365d..0000000000000
--- a/src/lightning/app/cli/lightning_cli_delete.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import click
-import inquirer
-from inquirer.themes import GreenPassion
-from rich.console import Console
-
-from lightning.app.cli.cmd_apps import _AppManager
-
-
-@click.group("delete")
-def delete() -> None:
- """Delete Lightning AI self-managed resources (e.g. apps)"""
- pass
-
-
-def _find_selected_app_instance_id(app_name: str) -> str:
- console = Console()
- app_manager = _AppManager()
-
- all_app_names_and_ids = {}
- selected_app_instance_id = None
-
- for app in app_manager.list_apps():
- all_app_names_and_ids[app.name] = app.id
- # figure out the ID of some app_name
- if app_name == app.name or app_name == app.id:
- selected_app_instance_id = app.id
- break
-
- if selected_app_instance_id is None:
- # when there is no app with the given app_name,
- # ask the user which app they would like to delete.
- console.print(f'[b][yellow]Cannot find app named "{app_name}"[/yellow][/b]')
- try:
- ask = [
- inquirer.List(
- "app_name",
- message="Select the app name to delete",
- choices=list(all_app_names_and_ids.keys()),
- ),
- ]
- app_name = inquirer.prompt(ask, theme=GreenPassion(), raise_keyboard_interrupt=True)["app_name"]
- selected_app_instance_id = all_app_names_and_ids[app_name]
- except KeyboardInterrupt:
- console.print("[b][red]Cancelled by user![/b][/red]")
- raise InterruptedError
-
- return selected_app_instance_id
-
-
-def _delete_app_confirmation_prompt(app_name: str) -> None:
- console = Console()
-
- # when the --yes / -y flags were not passed, do a final
- # confirmation that the user wants to delete the app.
- try:
- ask = [
- inquirer.Confirm(
- "confirm",
- message=f'Are you sure you want to delete app "{app_name}""?',
- default=False,
- ),
- ]
- if inquirer.prompt(ask, theme=GreenPassion(), raise_keyboard_interrupt=True)["confirm"] is False:
- console.print("[b][red]Aborted![/b][/red]")
- raise InterruptedError
- except KeyboardInterrupt:
- console.print("[b][red]Cancelled by user![/b][/red]")
- raise InterruptedError
-
-
-@delete.command("app")
-@click.argument("app-name", type=str)
-@click.option(
- "skip_user_confirm_prompt",
- "--yes",
- "-y",
- is_flag=True,
- default=False,
- help="Do not prompt for confirmation.",
-)
-def delete_app(app_name: str, skip_user_confirm_prompt: bool) -> None:
- """Delete a Lightning app.
-
- Deleting an app also deletes all app websites, works, artifacts, and logs. This permanently removes any record of
- the app as well as all any of its associated resources and data. This does not affect any resources and data
- associated with other Lightning apps on your account.
-
- """
- console = Console()
-
- try:
- selected_app_instance_id = _find_selected_app_instance_id(app_name=app_name)
- if not skip_user_confirm_prompt:
- _delete_app_confirmation_prompt(app_name=app_name)
- except InterruptedError:
- return
-
- try:
- # Delete the app!
- app_manager = _AppManager()
- app_manager.delete(app_id=selected_app_instance_id)
- except Exception as ex:
- console.print(
- f'[b][red]An issue occurred while deleting app "{app_name}. If the issue persists, please '
- "reach out to us at [link=mailto:support@lightning.ai]support@lightning.ai[/link][/b][/red]."
- )
- raise click.ClickException(str(ex))
-
- console.print(f'[b][green]App "{app_name}" has been successfully deleted"![/green][/b]')
- return
diff --git a/src/lightning/app/cli/lightning_cli_launch.py b/src/lightning/app/cli/lightning_cli_launch.py
deleted file mode 100644
index c171fd7b946f1..0000000000000
--- a/src/lightning/app/cli/lightning_cli_launch.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import logging
-from typing import Tuple
-
-import click
-
-from lightning.app.core.constants import APP_SERVER_HOST, APP_SERVER_PORT
-from lightning.app.launcher.launcher import (
- run_lightning_flow,
- run_lightning_work,
- serve_frontend,
- start_application_server,
- start_flow_and_servers,
-)
-
-logger = logging.getLogger(__name__)
-
-
-@click.group(name="launch", hidden=True)
-def launch() -> None:
- """Launch your application."""
-
-
-@launch.command("server", hidden=True)
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-@click.option("--host", help="Application running host", default=APP_SERVER_HOST, type=str)
-@click.option("--port", help="Application running port", default=APP_SERVER_PORT, type=int)
-def run_server(file: str, queue_id: str, host: str, port: int) -> None:
- """It takes the application file as input, build the application object and then use that to run the application
- server.
-
- This is used by the cloud runners to start the status server for the application
-
- """
- logger.debug(f"Run Server: {file} {queue_id} {host} {port}")
- start_application_server(file, host, port, queue_id=queue_id)
-
-
-@launch.command("flow", hidden=True)
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-@click.option("--base-url", help="Base url at which the app server is hosted", default="")
-def run_flow(file: str, queue_id: str, base_url: str) -> None:
- """It takes the application file as input, build the application object, proxy all the work components and then run
- the application flow defined in the root component.
-
- It does exactly what a singleprocess dispatcher would do but with proxied work components.
-
- """
- logger.debug(f"Run Flow: {file} {queue_id} {base_url}")
- run_lightning_flow(file, queue_id=queue_id, base_url=base_url)
-
-
-@launch.command("work", hidden=True)
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--work-name", type=str)
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-def run_work(file: str, work_name: str, queue_id: str) -> None:
- """Unlike other entrypoints, this command will take the file path or module details for a work component and run
- that by fetching the states from the queues."""
- logger.debug(f"Run Work: {file} {work_name} {queue_id}")
- run_lightning_work(
- file=file,
- work_name=work_name,
- queue_id=queue_id,
- )
-
-
-@launch.command("frontend", hidden=True)
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--flow-name")
-@click.option("--host")
-@click.option("--port", type=int)
-def run_frontend(file: str, flow_name: str, host: str, port: int) -> None:
- """Serve the frontend specified by the given flow."""
- logger.debug(f"Run Frontend: {file} {flow_name} {host}")
- serve_frontend(file=file, flow_name=flow_name, host=host, port=port)
-
-
-@launch.command("flow-and-servers", hidden=True)
-@click.argument("file", type=click.Path(exists=True))
-@click.option("--queue-id", help="ID for identifying queue", default="", type=str)
-@click.option("--base-url", help="Base url at which the app server is hosted", default="")
-@click.option("--host", help="Application running host", default=APP_SERVER_HOST, type=str)
-@click.option("--port", help="Application running port", default=APP_SERVER_PORT, type=int)
-@click.option(
- "--flow-port",
- help="Pair of flow name and frontend port",
- type=(str, int),
- multiple=True,
-)
-def run_flow_and_servers(
- file: str,
- base_url: str,
- queue_id: str,
- host: str,
- port: int,
- flow_port: Tuple[Tuple[str, int]],
-) -> None:
- """It takes the application file as input, build the application object and then use that to run the application
- flow defined in the root component, the application server and all the flow frontends.
-
- This is used by the cloud runners to start the flow, the status server and all frontends for the application
-
- """
- logger.debug(f"Run Flow: {file} {queue_id} {base_url}")
- logger.debug(f"Run Server: {file} {queue_id} {host} {port}.")
- logger.debug(f"Run Frontend's: {flow_port}")
- start_flow_and_servers(
- entrypoint_file=file,
- base_url=base_url,
- queue_id=queue_id,
- host=host,
- port=port,
- flow_names_and_ports=flow_port,
- )
diff --git a/src/lightning/app/cli/lightning_cli_list.py b/src/lightning/app/cli/lightning_cli_list.py
deleted file mode 100644
index 0cbc8e3cc1887..0000000000000
--- a/src/lightning/app/cli/lightning_cli_list.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any
-
-import click
-
-from lightning.app.cli.cmd_apps import _AppManager
-
-
-@click.group(name="list")
-def get_list() -> None:
- """List Lightning AI self-managed resources (e.g. apps)"""
- pass
-
-
-@get_list.command("apps")
-def list_apps(**kwargs: Any) -> None:
- """List your Lightning AI apps."""
- app_manager = _AppManager()
- app_manager.list()
diff --git a/src/lightning/app/cli/pl-app-template/.gitignore b/src/lightning/app/cli/pl-app-template/.gitignore
deleted file mode 100644
index 01aa0091c3945..0000000000000
--- a/src/lightning/app/cli/pl-app-template/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.storage
diff --git a/src/lightning/app/cli/pl-app-template/.lightningignore b/src/lightning/app/cli/pl-app-template/.lightningignore
deleted file mode 100644
index 5895fd5187660..0000000000000
--- a/src/lightning/app/cli/pl-app-template/.lightningignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.storage
-ui/node_modules
diff --git a/src/lightning/app/cli/pl-app-template/app.py b/src/lightning/app/cli/pl-app-template/app.py
deleted file mode 100644
index b15ea21a02276..0000000000000
--- a/src/lightning/app/cli/pl-app-template/app.py
+++ /dev/null
@@ -1,105 +0,0 @@
-import os
-from typing import Dict, List, Optional, Union
-
-from core.components import TensorBoard, WeightsAndBiases
-from core.components.script_runner import ScriptRunner
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.frontend import StaticWebFrontend
-from lightning.app.storage.path import Path
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-
-class ReactUI(LightningFlow):
- def configure_layout(self):
- return StaticWebFrontend(str(Path(__file__).parent / "ui/build"))
-
-
-class ScriptOrchestrator(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.script_runner: Optional[ScriptRunner] = None
- self.triggered: bool = False
- self.running: bool = False
- self.succeeded: bool = False
- self.failed: bool = False
- self.script_args: List[str] = []
- self.cloud_compute_args: Dict[str, Union[str, int]] = {"name": "cpu-small"}
- self.environment_variables: Dict[str, str] = {}
- self.script_path = "{{ script_path }}"
-
- def run(self) -> None:
- if not self.triggered:
- return
-
- if self.script_runner is None:
- self.script_runner = ScriptRunner(
- root_path=str(Path(__file__).parent / "source"),
- script_path=str(Path(__file__).parent / "source" / self.script_path),
- script_args=self.script_args,
- env=self._prepare_environment(),
- parallel=True,
- cloud_compute=CloudCompute(**self.cloud_compute_args),
- raise_exception=False,
- )
- self.script_runner.run()
-
- self.running = self.script_runner is not None and self.script_runner.has_started
- self.succeeded = self.script_runner is not None and self.script_runner.has_succeeded
- self.failed = self.script_runner is not None and self.script_runner.has_failed
-
- if self.succeeded or self.failed:
- self.triggered = False
- # TODO: support restarting
- # self.script_runner = None
-
- def _prepare_environment(self) -> Dict[str, str]:
- env = os.environ.copy()
- env.update(self.environment_variables)
- return env
-
-
-class Main(LightningFlow):
- def __init__(self) -> None:
- super().__init__()
- self.react_ui = ReactUI()
- self.script_orchestrator = ScriptOrchestrator()
- self.running_in_cloud = bool(os.environ.get("LIGHTNING_CLOUD_APP_ID", False))
-
- def run(self) -> None:
- self.react_ui.run()
- self.script_orchestrator.run()
-
- if self.script_orchestrator.script_runner and self.script_orchestrator.script_runner.logger_metadatas:
- if not getattr(self, "logger_component", None):
- # TODO: Hack with hasattr and setattr until
- # https://linear.app/gridai/issue/LAI2-8970/work-getting-set-to-none-in-state-update-from-appstate
- # is resolved
- logger_component = self._choose_logger_component()
- if logger_component is not None:
- setattr(self, "logger_component", logger_component)
- else:
- self.logger_component.run()
-
- def configure_layout(self):
- tabs = [{"name": "Home", "content": self.react_ui}]
- if hasattr(self, "logger_component"):
- tabs.extend(self.logger_component.configure_layout())
- return tabs
-
- def _choose_logger_component(self) -> Optional[Union[TensorBoard, WeightsAndBiases]]:
- logger_metadatas = self.script_orchestrator.script_runner.logger_metadatas
- if not logger_metadatas:
- return None
- if logger_metadatas[0].get("class_name") == "TensorBoardLogger":
- return TensorBoard(log_dir=self.script_orchestrator.script_runner.log_dir)
- if logger_metadatas[0].get("class_name") == "WandbLogger":
- return WeightsAndBiases(
- username=logger_metadatas[0]["username"],
- project_name=logger_metadatas[0]["project_name"],
- run_id=logger_metadatas[0]["run_id"],
- api_key=self.script_orchestrator.environment_variables.get("WANDB_API_KEY"),
- )
- return None
-
-
-app = LightningApp(Main())
diff --git a/src/lightning/app/cli/pl-app-template/core/__init__.py b/src/lightning/app/cli/pl-app-template/core/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/pl-app-template/core/callbacks.py b/src/lightning/app/cli/pl-app-template/core/callbacks.py
deleted file mode 100644
index 87ec8e9bcbda2..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/callbacks.py
+++ /dev/null
@@ -1,319 +0,0 @@
-import inspect
-from typing import TYPE_CHECKING, Any, Dict, Union
-
-import lightning.pytorch as pl
-from lightning.app.storage.path import Path
-from lightning.app.utilities.app_helpers import Logger
-from lightning.pytorch import Callback
-from lightning.pytorch.callbacks.progress.progress_bar import get_standard_metrics
-from lightning.pytorch.loggers import TensorBoardLogger, WandbLogger
-from lightning.pytorch.utilities.parsing import collect_init_args
-
-from core.state import ProgressBarState, TrainerState
-
-if TYPE_CHECKING:
- from core.components.script_runner import ScriptRunner
-
-
-_log = Logger(__name__)
-
-
-class PLAppProgressTracker(Callback):
- """This callback tracks and communicates the Trainer's progress to the running PyTorch Lightning App."""
-
- def __init__(self, work: "ScriptRunner", refresh_rate: int = 1) -> None:
- super().__init__()
- self.work = work
- self.refresh_rate = refresh_rate
- self.is_enabled = False
- self._state = ProgressBarState()
-
- def setup(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- stage: str,
- ) -> None:
- self.is_enabled = trainer.is_global_zero
-
- def on_train_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- # We calculate the estimated stepping batches here instead of in the setup hook, because calling the
- # `Trainer.estimated_stepping_batches` too early would lead to a barrier() call in case of DDP and since this
- # callback is only attached on rank 0, would lead to a stall.
- self._state.fit.estimated_stepping_batches = trainer.estimated_stepping_batches
-
- def on_train_epoch_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule", *_: Any) -> None:
- self._state.fit.total_train_batches = self._total_train_batches(trainer)
- self._state.fit.total_val_batches = self._total_val_batches(trainer)
- self._state.fit.current_epoch = trainer.current_epoch
- if self.is_enabled:
- self._send_state()
-
- def on_train_batch_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule", *_: Any) -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- current = self._train_batch_idx(trainer)
- self._state.fit.train_batch_idx = current
- self._state.fit.global_step = trainer.global_step
- if self._should_send(current, self._total_train_batches(trainer)):
- self._send_state()
-
- def on_train_epoch_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- if self.is_enabled:
- self._send_state()
-
- def on_validation_batch_start(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- batch: Any,
- batch_idx: int,
- dataloader_idx: int,
- ) -> None:
- if trainer.state.fn == "fit":
- self._state.fit.val_dataloader_idx = dataloader_idx
- self._state.fit.total_val_batches = self._total_val_batches(trainer)
- if trainer.state.fn == "validate":
- self._state.val.dataloader_idx = dataloader_idx
- self._state.val.total_val_batches = self._total_val_batches(trainer)
-
- def on_validation_batch_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule", *_: Any) -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- current = self._val_batch_idx(trainer)
- if trainer.state.fn == "fit":
- self._state.fit.val_batch_idx = current
- if trainer.state.fn == "validate":
- self._state.val.val_batch_idx = current
- if self._should_send(current, self._total_val_batches(trainer)):
- self._send_state()
-
- def on_validation_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- if self.is_enabled:
- self._send_state()
-
- def on_test_batch_start(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- batch: Any,
- batch_idx: int,
- dataloader_idx: int,
- ) -> None:
- self._state.test.dataloader_idx = dataloader_idx
- self._state.test.total_test_batches = trainer.num_test_batches[dataloader_idx]
-
- def on_test_batch_end(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- outputs: Any,
- batch: Any,
- batch_idx: int,
- dataloader_idx: int = 0,
- ) -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- current = self._test_batch_idx(trainer)
- self._state.test.test_batch_idx = current
- if self._should_send(current, trainer.num_test_batches[dataloader_idx]):
- self._send_state()
-
- def on_test_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- if self.is_enabled:
- self._send_state()
-
- def on_predict_batch_start(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- batch: Any,
- batch_idx: int,
- dataloader_idx: int,
- ) -> None:
- self._state.predict.dataloader_idx = dataloader_idx
- self._state.predict.total_predict_batches = trainer.num_predict_batches[dataloader_idx]
-
- def on_predict_batch_end(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- outputs: Any,
- batch: Any,
- batch_idx: int,
- dataloader_idx: int = 0,
- ) -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- current = self._predict_batch_idx(trainer)
- self._state.predict.predict_batch_idx = current
- if self._should_send(current, trainer.num_predict_batches[dataloader_idx]):
- self._send_state()
-
- def on_predict_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.metrics = self._progress_bar_metrics(trainer, pl_module)
- if self.is_enabled:
- self._send_state()
-
- def _train_batch_idx(self, trainer: "pl.Trainer") -> int:
- return trainer.fit_loop.epoch_loop.batch_progress.current.processed
-
- def _val_batch_idx(self, trainer: "pl.Trainer") -> int:
- loop = trainer.fit_loop.epoch_loop.val_loop if trainer.state.fn == "fit" else trainer.validate_loop
-
- return loop.epoch_loop.batch_progress.current.processed
-
- def _test_batch_idx(self, trainer: "pl.Trainer") -> int:
- return trainer.test_loop.epoch_loop.batch_progress.current.processed
-
- def _predict_batch_idx(self, trainer: "pl.Trainer") -> int:
- return trainer.predict_loop.epoch_loop.batch_progress.current.processed
-
- def _total_train_batches(self, trainer: "pl.Trainer") -> Union[int, float]:
- return trainer.num_training_batches
-
- def _total_val_batches(self, trainer: "pl.Trainer") -> Union[int, float]:
- return sum(trainer.num_val_batches) if trainer.fit_loop.epoch_loop._should_check_val_epoch() else 0
-
- def _progress_bar_metrics(
- self, trainer: "pl.Trainer", pl_module: "pl.LightningModule"
- ) -> Dict[str, Union[str, float]]:
- standard_metrics = get_standard_metrics(trainer, pl_module)
- pbar_metrics = trainer.progress_bar_metrics
- return {**standard_metrics, **pbar_metrics}
-
- def _send_state(self) -> None:
- self.work.trainer_progress = self._state.dict()
-
- def _should_send(self, current: int, total: int) -> bool:
- return self.is_enabled and current % self.refresh_rate == 0 or current == total
-
-
-class PLAppTrainerStateTracker(Callback):
- def __init__(self, work: "ScriptRunner") -> None:
- super().__init__()
- self.work = work
- self._state = TrainerState()
-
- def on_fit_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.fn = "fit"
- self.work.trainer_state = self._state.dict()
-
- def on_fit_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.fn = None
- self.work.trainer_state = self._state.dict()
-
- def on_train_epoch_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.stage = "training"
- self.work.trainer_state = self._state.dict()
-
- def on_train_epoch_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.stage = None
- self.work.trainer_state = self._state.dict()
-
- def on_validation_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.stage = "validating"
- self.work.trainer_state = self._state.dict()
-
- def on_validation_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.stage = None
- self.work.trainer_state = self._state.dict()
-
- def on_test_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.fn = "test"
- self._state.stage = "testing"
- self.work.trainer_state = self._state.dict()
-
- def on_test_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.fn = None
- self._state.stage = None
- self.work.trainer_state = self._state.dict()
-
- def on_predict_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.fn = "predict"
- self._state.stage = "predicting"
- self.work.trainer_state = self._state.dict()
-
- def on_predict_end(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- self._state.fn = None
- self._state.stage = None
- self.work.trainer_state = self._state.dict()
-
-
-class PLAppSummary(Callback):
- def __init__(self, work: "ScriptRunner") -> None:
- super().__init__()
- self.work = work
-
- def on_init_end(self, trainer: "pl.Trainer") -> None:
- current_frame = inspect.currentframe()
- # Trainer.init() -> Trainer._call_callback_hooks() -> Callback.on_init_end()
- frame = current_frame.f_back.f_back
- init_args = {}
- for local_args in collect_init_args(frame, []):
- init_args.update(local_args)
-
- self.work.trainer_hparams = self._sanitize_trainer_init_args(init_args)
-
- def setup(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- stage: str,
- ) -> None:
- self.work.model_hparams = self._sanitize_model_init_args(dict(**pl_module.hparams))
-
- def _sanitize_trainer_init_args(self, init_args: Dict[str, Any]) -> Dict[str, str]:
- if init_args["callbacks"]:
- init_args["callbacks"] = [c.__class__.__name__ for c in init_args["callbacks"]]
- return {k: str(v) for k, v in init_args.items()}
-
- def _sanitize_model_init_args(self, init_args: Dict[str, Any]) -> Dict[str, str]:
- return {k: str(v) for k, v in init_args.items()}
-
-
-class PLAppArtifactsTracker(Callback):
- def __init__(self, work: "ScriptRunner") -> None:
- super().__init__()
- self.work = work
-
- def setup(
- self,
- trainer: "pl.Trainer",
- pl_module: "pl.LightningModule",
- stage: str,
- ) -> None:
- log_dir = self._get_logdir(trainer)
- self.work.log_dir = Path(log_dir) if log_dir is not None else None
- self._collect_logger_metadata(trainer)
-
- def on_train_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None:
- if trainer.checkpoint_callback and trainer.checkpoint_callback.dirpath is not None:
- self.work.checkpoint_dir = Path(trainer.checkpoint_callback.dirpath)
-
- def _collect_logger_metadata(self, trainer: "pl.Trainer") -> None:
- if not trainer.loggers:
- return
-
- for logger in trainer.loggers:
- metadata = {"class_name": logger.__class__.__name__}
- if isinstance(logger, WandbLogger) and not logger._offline:
- metadata.update({
- "username": logger.experiment.entity,
- "project_name": logger.name,
- "run_id": logger.version,
- })
-
- if metadata and metadata not in self.work.logger_metadatas:
- self.work.logger_metadatas.append(metadata)
-
- @staticmethod
- def _get_logdir(trainer: "pl.Trainer") -> str:
- """The code here is the same as in the ``Trainer.log_dir``, with the exception of the broadcast call."""
- if len(trainer.loggers) == 1:
- if isinstance(trainer.logger, TensorBoardLogger):
- dirpath = trainer.logger.log_dir
- else:
- dirpath = trainer.logger.save_dir
- else:
- dirpath = trainer.default_root_dir
- return dirpath
diff --git a/src/lightning/app/cli/pl-app-template/core/components/__init__.py b/src/lightning/app/cli/pl-app-template/core/components/__init__.py
deleted file mode 100644
index 75f49eb7da05e..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/components/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from core.components.logger.tensorboard import TensorBoard # noqa: F401
-from core.components.logger.weights_and_biases import WeightsAndBiases # noqa: F401
diff --git a/src/lightning/app/cli/pl-app-template/core/components/logger/__init__.py b/src/lightning/app/cli/pl-app-template/core/components/logger/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/pl-app-template/core/components/logger/tensorboard.py b/src/lightning/app/cli/pl-app-template/core/components/logger/tensorboard.py
deleted file mode 100644
index 0e1a536ff4859..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/components/logger/tensorboard.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import subprocess
-import time
-from typing import Dict, List
-
-from lightning.app import BuildConfig, LightningFlow, LightningWork
-from lightning.app.storage.path import Path
-
-
-class TensorBoard(LightningFlow):
- def __init__(self, log_dir: Path, sync_every_n_seconds: int = 5) -> None:
- """This TensorBoard component synchronizes the log directory of an experiment and starts up the server.
-
- Args:
- log_dir: The path to the directory where the TensorBoard log-files will appear.
- sync_every_n_seconds: How often to sync the log directory (given as an argument to the run method)
-
- """
- super().__init__()
- self.worker = TensorBoardWorker(log_dir=log_dir, sync_every_n_seconds=sync_every_n_seconds)
-
- def run(self) -> None:
- self.worker.run()
-
- def configure_layout(self) -> List[Dict[str, str]]:
- return [{"name": "Training Logs", "content": self.worker.url}]
-
-
-class TensorBoardWorker(LightningWork):
- def __init__(self, log_dir: Path, sync_every_n_seconds: int = 5) -> None:
- super().__init__(cloud_build_config=BuildConfig(requirements=["tensorboard"]))
- self.log_dir = log_dir
- self._sync_every_n_seconds = sync_every_n_seconds
-
- def run(self) -> None:
- subprocess.Popen([
- "tensorboard",
- "--logdir",
- str(self.log_dir),
- "--host",
- self.host,
- "--port",
- str(self.port),
- ])
-
- # Download the log directory periodically
- while True:
- time.sleep(self._sync_every_n_seconds)
- if self.log_dir.exists_remote():
- self.log_dir.get(overwrite=True)
diff --git a/src/lightning/app/cli/pl-app-template/core/components/logger/weights_and_biases.py b/src/lightning/app/cli/pl-app-template/core/components/logger/weights_and_biases.py
deleted file mode 100644
index bf20d17de033c..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/components/logger/weights_and_biases.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import os
-from typing import TYPE_CHECKING, Dict, List, Optional
-
-from lightning.app import LightningFlow
-
-if TYPE_CHECKING:
- import wandb
-
-
-class WeightsAndBiases(LightningFlow):
- def __init__(self, username: str, project_name: str, run_id: str, api_key: Optional[str] = None) -> None:
- super().__init__()
- self.username = username
- self.project_name = project_name
- self.run_id = run_id
- self._api_key = api_key
- self._run: Optional[wandb.Run] = None
-
- def run(self) -> None:
- if self._run is not None:
- return
-
- if self._api_key:
- os.environ["WANDB_API_KEY"] = self._api_key
-
- import wandb
-
- self._run = wandb.init(project=self.project_name, id=self.run_id, entity=self.username)
-
- def configure_layout(self) -> List[Dict[str, str]]:
- if self._run is not None:
- return [{"name": "Training Logs", "content": self._run.get_url()}]
- return []
diff --git a/src/lightning/app/cli/pl-app-template/core/components/script_runner/__init__.py b/src/lightning/app/cli/pl-app-template/core/components/script_runner/__init__.py
deleted file mode 100644
index b74bcabd5fbd7..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/components/script_runner/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from core.components.script_runner.script_runner import ScriptRunner # noqa: F401
diff --git a/src/lightning/app/cli/pl-app-template/core/components/script_runner/script_runner.py b/src/lightning/app/cli/pl-app-template/core/components/script_runner/script_runner.py
deleted file mode 100644
index 0c2f09e372237..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/components/script_runner/script_runner.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import sys
-import traceback
-from typing import Any, Dict, List, Optional, Tuple
-
-from lightning.app.components.python import TracerPythonScript
-from lightning.app.storage.path import Path
-from lightning.app.utilities.packaging.build_config import BuildConfig, load_requirements
-from lightning.app.utilities.tracer import Tracer
-
-
-class ScriptRunner(TracerPythonScript):
- """The ScriptRunner executes the script using ``runpy`` and also patches the Trainer methods to inject additional
- code."""
-
- def __init__(self, root_path: str, *args: Any, **kwargs: Any) -> None:
- super().__init__(*args, cloud_build_config=self._get_build_config(root_path), **kwargs)
- self.root_path = root_path
- self.exception_message: str = ""
- self.trainer_progress: dict = {}
- self.trainer_state: dict = {}
- self.trainer_hparams: dict = {}
- self.model_hparams: dict = {}
- self.log_dir: Optional[Path] = None
- self.checkpoint_dir: Optional[Path] = None
- self.logger_metadatas: List[Dict[str, str]] = []
-
- def configure_tracer(self) -> Tracer:
- from lightning.pytorch import Trainer
-
- from core.callbacks import PLAppArtifactsTracker, PLAppProgressTracker, PLAppSummary, PLAppTrainerStateTracker
-
- tracer = Tracer()
- trainer_artifacts_tracker = PLAppArtifactsTracker(work=self)
- trainer_state_tracker = PLAppTrainerStateTracker(work=self)
- progress_tracker = PLAppProgressTracker(work=self)
- summary = PLAppSummary(work=self)
-
- def pre_trainer_init(_, *args: Any, **kwargs: Any) -> Tuple[Dict, Tuple[Any, ...], Dict[str, Any]]:
- kwargs.setdefault("callbacks", [])
- kwargs["callbacks"].extend([
- trainer_artifacts_tracker,
- trainer_state_tracker,
- progress_tracker,
- summary,
- ])
- return {}, args, kwargs
-
- tracer.add_traced(Trainer, "__init__", pre_fn=pre_trainer_init)
- return tracer
-
- def run(self) -> None:
- self.exception_message = ""
- # We need to set the module path both in sys.path and the PYTHONPATH env variable.
- # The former is for the current process which is already running, and the env variable is needed in case
- # the script launches subprocesses
- sys.path.insert(0, self.root_path)
- self.env["PYTHONPATH"] = self.root_path
- super().run()
-
- def on_exception(self, exception: BaseException) -> None:
- self.exception_message = traceback.format_exc()
- super().on_exception(exception)
-
- @staticmethod
- def _get_build_config(root_path: str) -> Optional[BuildConfig]:
- # These are the requirements for the script runner itself
- requirements = [
- "protobuf<4.21.0",
- "pytorch-lightning<=1.6.3",
- "pydantic<=1.9.0",
- ]
- if Path(root_path, "requirements.txt").exists():
- # Requirements from the user's code folder
- requirements.extend(load_requirements(root_path, file_name="requirements.txt"))
-
- return BuildConfig(requirements=requirements)
diff --git a/src/lightning/app/cli/pl-app-template/core/state.py b/src/lightning/app/cli/pl-app-template/core/state.py
deleted file mode 100644
index 80a9f3d4e0619..0000000000000
--- a/src/lightning/app/cli/pl-app-template/core/state.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from typing import Dict, Optional, Union
-
-from pydantic import BaseModel, Field
-
-
-class FitProgress(BaseModel):
- current_epoch: int = 0
- train_batch_idx: int = 0
- total_train_batches: int = 0
- val_dataloader_idx: int = 0
- val_batch_idx: int = 0
- total_val_batches: int = 0
- global_step: int = 0
- estimated_stepping_batches: int = 0
-
-
-class ValidateProgress(BaseModel):
- dataloader_idx: int = 0
- val_batch_idx: int = 0
- total_val_batches: int = 0
-
-
-class TestProgress(BaseModel):
- dataloader_idx: int = 0
- test_batch_idx: int = 0
- total_test_batches: int = 0
-
-
-class PredictProgress(BaseModel):
- dataloader_idx: int = 0
- predict_batch_idx: int = 0
- total_predict_batches: int = 0
-
-
-class ProgressBarState(BaseModel):
- fit: FitProgress = Field(default_factory=FitProgress)
- val: ValidateProgress = Field(alias="validate", default_factory=ValidateProgress)
- test: TestProgress = Field(default_factory=TestProgress)
- predict: PredictProgress = Field(default_factory=PredictProgress)
- metrics: Dict[str, Union[float, str]] = {}
-
-
-class TrainerState(BaseModel):
- fn: Optional[str] = None
- stage: Optional[str] = None
diff --git a/src/lightning/app/cli/pl-app-template/setup.py b/src/lightning/app/cli/pl-app-template/setup.py
deleted file mode 100644
index dc223931779a2..0000000000000
--- a/src/lightning/app/cli/pl-app-template/setup.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import os
-from typing import List
-
-from setuptools import find_packages, setup
-
-_PROJECT_ROOT = os.path.dirname(__file__)
-
-
-def _load_requirements(path_dir: str, file_name: str = "requirements.txt", comment_char: str = "#") -> List[str]:
- """Load requirements from a file."""
- with open(os.path.join(path_dir, file_name)) as file:
- lines = [ln.strip() for ln in file.readlines()]
- reqs = []
- for ln in lines:
- # filer all comments
- if comment_char in ln:
- ln = ln[: ln.index(comment_char)].strip()
- # skip directly installed dependencies
- if ln.startswith("http"):
- continue
- # skip index url
- if ln.startswith("--extra-index-url"):
- continue
- if ln: # if requirement is not empty
- reqs.append(ln)
- return reqs
-
-
-setup(
- name="{{ app_name }}",
- version="0.0.1",
- packages=find_packages(exclude=["ui"]),
- python_requires=">=3.8",
-)
diff --git a/src/lightning/app/cli/pl-app-template/tests/__init__.py b/src/lightning/app/cli/pl-app-template/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/pl-app-template/tests/core/__init__.py b/src/lightning/app/cli/pl-app-template/tests/core/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/cli/pl-app-template/tests/core/test_callbacks.py b/src/lightning/app/cli/pl-app-template/tests/core/test_callbacks.py
deleted file mode 100644
index a211da35e6a90..0000000000000
--- a/src/lightning/app/cli/pl-app-template/tests/core/test_callbacks.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import os.path
-from unittest.mock import Mock
-
-import pytest
-from core.callbacks import PLAppArtifactsTracker, PLAppProgressTracker, PLAppSummary
-from core.components.script_runner import ScriptRunner
-from lightning.app.storage.path import Path
-from lightning.pytorch import LightningModule, Trainer
-from lightning.pytorch.loggers import TensorBoardLogger
-
-
-@pytest.mark.parametrize("rank", [0, 1])
-def test_progress_tracker_enabled(rank):
- trainer = Mock()
- trainer.global_rank = rank
- trainer.is_global_zero = rank == 0
- work = Mock()
- tracker = PLAppProgressTracker(work)
- assert not tracker.is_enabled
- tracker.setup(trainer, Mock(), Mock())
- assert tracker.is_enabled == trainer.is_global_zero
-
-
-def test_summary_callback_tracks_hyperparameters():
- class ModelWithParameters(LightningModule):
- def __init__(self, float_arg=0.1, int_arg=5, bool_arg=True, string_arg="string"):
- super().__init__()
- self.save_hyperparameters()
-
- model = ModelWithParameters()
- work = Mock()
- summary = PLAppSummary(work)
- trainer = Trainer(max_epochs=22, callbacks=[summary]) # this triggers the `Callback.on_init_end` hook
- summary.setup(trainer, model)
- assert work.model_hparams == {
- "float_arg": "0.1",
- "int_arg": "5",
- "bool_arg": "True",
- "string_arg": "string",
- }
-
- assert work.trainer_hparams["max_epochs"] == "22"
- assert work.trainer_hparams["logger"] == "True"
- assert "ModelCheckpoint" in work.trainer_hparams["callbacks"]
- assert "PLAppSummary" in work.trainer_hparams["callbacks"]
-
-
-def test_artifacts_tracker(tmpdir):
- work = ScriptRunner(root_path=os.path.dirname(__file__), script_path=__file__)
- tracker = PLAppArtifactsTracker(work=work)
- trainer = Mock()
-
- trainer.loggers = []
- trainer.default_root_dir = "default_root_dir"
- tracker.setup(trainer=trainer, pl_module=Mock())
- assert work.log_dir == Path("default_root_dir")
- assert not work.logger_metadatas
-
- trainer.loggers = [TensorBoardLogger(save_dir=tmpdir)]
- trainer.logger = trainer.loggers[0]
- tracker.setup(trainer=trainer, pl_module=Mock())
- assert work.log_dir == Path(tmpdir / "lightning_logs" / "version_0")
- assert len(work.logger_metadatas) == 1
- assert work.logger_metadatas[0] == {"class_name": "TensorBoardLogger"}
-
- # call setup a second time and the metadata length should not change
- tracker.setup(trainer=trainer, pl_module=Mock())
- assert len(work.logger_metadatas) == 1
diff --git a/src/lightning/app/cli/pl-app-template/tests/test_app.py b/src/lightning/app/cli/pl-app-template/tests/test_app.py
deleted file mode 100644
index 3fc14bfcdbf69..0000000000000
--- a/src/lightning/app/cli/pl-app-template/tests/test_app.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import pytest
-
-
-@pytest.mark.skip()
-def test_is_running_in_cloud(monkeypatch):
- from app import Main
-
- monkeypatch.setenv("LIGHTNING_CLOUD_APP_ID", "anything")
- app = Main()
- assert app.running_in_cloud
-
- monkeypatch.delenv("LIGHTNING_CLOUD_APP_ID", raising=False)
- app = Main()
- assert not app.running_in_cloud
diff --git a/src/lightning/app/cli/pl-app-template/ui/.gitignore b/src/lightning/app/cli/pl-app-template/ui/.gitignore
deleted file mode 100644
index 6c2d44cd3ba13..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/.gitignore
+++ /dev/null
@@ -1,25 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-/cypress/videos
-/cypress/screenshots
-/cypress/downloads
-
-.eslintcache
diff --git a/src/lightning/app/cli/pl-app-template/ui/.prettierignore b/src/lightning/app/cli/pl-app-template/ui/.prettierignore
deleted file mode 100644
index 2ea70f096046d..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/.prettierignore
+++ /dev/null
@@ -1,3 +0,0 @@
-resources
-build
-node_modules
diff --git a/src/lightning/app/cli/pl-app-template/ui/.prettierrc b/src/lightning/app/cli/pl-app-template/ui/.prettierrc
deleted file mode 100644
index cad1459af3548..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/.prettierrc
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "jsxSingleQuote": false,
- "arrowParens": "avoid",
- "tabWidth": 2,
- "useTabs": false,
- "printWidth": 119,
- "singleQuote": false,
- "semi": true,
- "endOfLine": "lf",
- "proseWrap": "always",
- "bracketSameLine": true,
- "quoteProps": "consistent",
- "trailingComma": "all",
- "bracketSpacing": true,
- "importOrder": [
- "^react$",
- "",
- "^(components|hooks|resources|utils|lightning-.*)",
- "^tests",
- "^[./]"
- ],
- "importOrderSeparation": true,
- "importOrderSortSpecifiers": true
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/craco.config.js b/src/lightning/app/cli/pl-app-template/ui/craco.config.js
deleted file mode 100644
index 979d08985ff19..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/craco.config.js
+++ /dev/null
@@ -1,29 +0,0 @@
-const path = require("path");
-const fs = require("fs");
-const cracoBabelLoader = require("craco-babel-loader");
-
-// manage relative paths to packages
-const appDirectory = fs.realpathSync(process.cwd());
-const resolvePackage = relativePath => path.resolve(appDirectory, relativePath);
-
-module.exports = {
- devServer: {
- // When launching `yarn start dev`, write the files to the build folder too
- devMiddleware: { writeToDisk: true },
- },
- webpack: {
- configure: {
- output: {
- publicPath: "./",
- },
- },
- },
- plugins: [
- {
- plugin: cracoBabelLoader,
- options: {
- includes: [resolvePackage("node_modules/lightning-ui")],
- },
- },
- ],
-};
diff --git a/src/lightning/app/cli/pl-app-template/ui/package.json b/src/lightning/app/cli/pl-app-template/ui/package.json
deleted file mode 100644
index 690bd85b6498c..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/package.json
+++ /dev/null
@@ -1,95 +0,0 @@
-{
- "name": "pytorch-lightning-app",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@emotion/react": "^11.7.1",
- "@emotion/styled": "^11.6.0",
- "@mui/icons-material": "^5.6.2",
- "@mui/lab": "^5.0.0-alpha.64",
- "@mui/material": "^5.2.7",
- "@reduxjs/toolkit": "^1.8.0",
- "@stripe/stripe-js": "^1.29.0",
- "axios": "^0.25.0",
- "boring-avatars": "^1.6.3",
- "filter-material-ui": "2.7.0",
- "fontfaceobserver": "^2.1.0",
- "lightning-ui": "git+ssh://git@github.com/gridai/lightning-ui.git#35f4124cc8a16a313174fe63ec82cb74af388c6b",
- "lodash": "^4.17.21",
- "notistack": "^2.0.4",
- "query-string": "^7.1.0",
- "react": "^17.0.2",
- "react-dom": "^17.0.2",
- "react-github-btn": "^1.2.1",
- "react-hook-form": "^7.27.1",
- "react-query": "^3.34.7",
- "react-router-dom": "^6.2.1",
- "react-scripts": "5.0.0",
- "react-spring": "^9.4.4",
- "react-table": "^7.7.0",
- "rxjs": "^7.5.2",
- "typescript": "^4.4.2",
- "use-debounce": "^7.0.1",
- "web-vitals": "^2.1.0",
- "xterm": "^4.18.0",
- "xterm-addon-fit": "^0.5.0",
- "xterm-addon-search": "^0.8.2"
- },
- "scripts": {
- "start": "craco start",
- "build": "craco build",
- "lint": "eslint --cache --max-warnings=0 . && prettier -c .",
- "lint:fix": "eslint --cache --max-warnings=0 . --fix && prettier -w .",
- "eject": "react-scripts eject"
- },
- "lint-staged": {
- "**/*": "prettier --write --ignore-unknown"
- },
- "eslintConfig": {
- "extends": [
- "react-app"
- ],
- "ignorePatterns": [
- "node_modules/**",
- "build/**"
- ],
- "rules": {
- "react/jsx-sort-props": [
- "off",
- {
- "callbacksLast": true,
- "ignoreCase": true,
- "noSortAlphabetically": false,
- "reservedFirst": true,
- "shorthandFirst": true
- }
- ],
- "react/jsx-pascal-case": "warn"
- }
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- },
- "devDependencies": {
- "@craco/craco": "^6.4.3",
- "@trivago/prettier-plugin-sort-imports": "^3.1.1",
- "@types/fontfaceobserver": "^2.1.0",
- "@types/lodash": "^4.14.182",
- "@types/node": "^16.7.13",
- "@types/react": "^17.0.20",
- "@types/react-dom": "^17.0.9",
- "@types/react-table": "^7.7.9",
- "craco-babel-loader": "^1.0.3",
- "lint-staged": "^12.3.2",
- "prettier": "2.5.1"
- }
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/public/favicon.svg b/src/lightning/app/cli/pl-app-template/ui/public/favicon.svg
deleted file mode 100644
index 94a65989d0b4b..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/public/favicon.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/lightning/app/cli/pl-app-template/ui/public/index.html b/src/lightning/app/cli/pl-app-template/ui/public/index.html
deleted file mode 100644
index 0f2384212e1d3..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/public/index.html
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- PyTorch Lightning App
-
-
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
diff --git a/src/lightning/app/cli/pl-app-template/ui/public/manifest.json b/src/lightning/app/cli/pl-app-template/ui/public/manifest.json
deleted file mode 100644
index 4a97d68d6b7ba..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/public/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "short_name": "PL App",
- "name": "PyTorch Lightning App",
- "icons": [
- {
- "src": "favicon.svg",
- "sizes": "512x512 192x192 64x64 32x32 24x24 16x16",
- "type": "image/svg+xml"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/public/robots.txt b/src/lightning/app/cli/pl-app-template/ui/public/robots.txt
deleted file mode 100644
index e9e57dc4d41b9..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/App.tsx b/src/lightning/app/cli/pl-app-template/ui/src/App.tsx
deleted file mode 100644
index 717984216f298..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/App.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { useEffect } from "react";
-
-import { QueryClient, QueryClientProvider } from "react-query";
-import { BrowserRouter } from "react-router-dom";
-
-import ErrorPanel from "components/ErrorPanel";
-import HyperparameterSummary from "components/HyperparameterSummary";
-import Launcher from "components/Launcher";
-import ProgressBarGroup from "components/ProgressBarGroup";
-import {
- Breadcrumbs,
- Card,
- CardContent,
- CardHeader,
- Grid,
- SnackbarProvider,
- Stack,
- useSnackbar,
-} from "lightning-ui/src/design-system/components";
-import ThemeProvider from "lightning-ui/src/design-system/theme";
-
-import ExecutionSummary from "./components/ExecutionSummary";
-import { useLightningState } from "./hooks/useLightningState";
-
-const queryClient = new QueryClient();
-
-function AppContainer() {
- const { lightningState } = useLightningState();
-
- const trainer_progress = lightningState?.flows.script_orchestrator.works.script_runner?.vars.trainer_progress;
- const trainer_state = lightningState?.flows.script_orchestrator.works.script_runner?.vars.trainer_state;
- const trainer_hparams = lightningState?.flows.script_orchestrator.works.script_runner?.vars.trainer_hparams;
- const model_hparams = lightningState?.flows.script_orchestrator.works.script_runner?.vars.model_hparams;
-
- const script_running = lightningState?.flows.script_orchestrator.vars.running;
- const script_succeeded = lightningState?.flows.script_orchestrator.vars.succeeded;
- const script_failed = lightningState?.flows.script_orchestrator.vars.failed;
- const start_triggered = lightningState?.flows.script_orchestrator.vars.triggered;
- const script_path = lightningState?.flows.script_orchestrator.vars.script_path;
- const running_in_cloud = lightningState?.vars.running_in_cloud;
-
- const breadCrumbItems = [
- { title: "Users", href: "url/to/href/1" },
- { title: "adrian", href: "url/to/href/2" },
- { title: "projects", href: "url/to/href/3" },
- { title: "app_name", href: "url/to/href/4" },
- { title: "source", href: "url/to/href/5" },
- { title: "train.py", href: "url/to/href/6" },
- ];
-
- const { enqueueSnackbar } = useSnackbar();
- const exception_message = lightningState?.flows.script_orchestrator.works.script_runner?.vars?.exception_message;
- useEffect(() => {
- if (exception_message) {
- enqueueSnackbar({
- title: "The script failed to complete",
- severity: "error",
- children: "See the error message",
- });
- }
- }, [exception_message]);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-function App() {
- return (
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default App;
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/EnvironmentConfigurator.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/EnvironmentConfigurator.tsx
deleted file mode 100644
index 2d26f86ad9965..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/EnvironmentConfigurator.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Button, Stack, TextField } from "lightning-ui/src/design-system/components";
-
-interface Data {
- [key: string]: string;
-}
-
-export function data2dict(data: Data[]) {
- var dict: Data = {};
- for (var i = 0; i < data.length; i++) {
- if (data[i]["name"] === "") {
- continue;
- }
- dict[data[i]["name"]] = data[i]["value"];
- }
- return dict;
-}
-
-export default function EnvironmentConfigurator(props: any) {
- const data: Data[] = props.data;
- const setData = props.setData;
- const addItemAllowed = data[data.length - 1].name.length > 0;
-
- const onItemAdd = () => {
- setData([...data, { name: "", value: "" }]);
- };
-
- const onItemChange = (fieldName: string, index: number, text: any) => {
- let newData = [...data];
-
- text = text.trim();
- if (fieldName == "name") {
- text = text.replace(/[^0-9a-zA-Z_]+/gi, "").toUpperCase();
- }
-
- newData[index][fieldName] = text;
- setData(newData);
- };
-
- return (
-
- {data.map((entry, index) => (
-
- onItemChange("name", index, e)}
- placeholder="KEY"
- size="medium"
- statusText=""
- type="text"
- value={entry.name || ""}
- />
- onItemChange("value", index, e)}
- placeholder="VALUE"
- size="medium"
- statusText=""
- type="text"
- value={entry.value || ""}
- />
-
- ))}
-
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/ErrorPanel.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/ErrorPanel.tsx
deleted file mode 100644
index 7b88e20007003..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/ErrorPanel.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as React from "react";
-
-import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
-import Accordion from "@mui/material/Accordion";
-import AccordionDetails from "@mui/material/AccordionDetails";
-import AccordionSummary from "@mui/material/AccordionSummary";
-import Typography from "@mui/material/Typography";
-
-export default function SimpleAccordion(props: any) {
- return (
-
- } aria-controls="panel1a-content" id="panel1a-header">
- Errors
-
-
-
-
- {props?.error_message}
-
-
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/ExecutionSummary.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/ExecutionSummary.tsx
deleted file mode 100644
index 48489c1246847..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/ExecutionSummary.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import Typography from "@mui/material/Typography";
-
-import Timer from "components/Timer";
-import { Grid, Stack } from "lightning-ui/src/design-system/components";
-
-export default function ExecutionSummary(props: any) {
- const trainer_progress = props?.trainer_progress;
- const trainer_state = props?.trainer_state;
- const script_running = props?.script_running;
-
- const global_step = trainer_progress?.fit?.global_step || 0;
- const estimated_stepping_batches = trainer_progress?.fit?.estimated_stepping_batches || Infinity;
- const total_estimated_progress = Math.round((100 * global_step) / estimated_stepping_batches);
-
- return (
-
-
-
-
- Duration
-
-
-
-
-
-
-
-
-
-
-
- Stage
-
-
-
- {trainer_state?.stage != undefined ? trainer_state?.stage : "-"}
-
-
-
-
-
-
-
- Epoch
-
-
-
- {trainer_progress?.fit?.current_epoch != undefined ? trainer_progress?.fit?.current_epoch : "-"}
-
-
-
-
-
-
-
- Batch
-
-
-
- {trainer_progress?.fit?.train_batch_idx != undefined ? trainer_progress?.fit?.train_batch_idx : "-"}
-
-
-
-
-
-
-
- Total Progress
-
-
-
- {trainer_progress?.fit != undefined ? total_estimated_progress + "%" : "-"}
-
-
-
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/HyperparameterSummary.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/HyperparameterSummary.tsx
deleted file mode 100644
index 085348c9a7355..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/HyperparameterSummary.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { useState } from "react";
-
-import { TextField } from "@mui/material";
-
-import { Checkbox, Stack, Table, Typography } from "lightning-ui/src/design-system/components";
-
-function HyperparametersTable(props: any) {
- return ;
-}
-
-export default function HyperparameterSummary(props: any) {
- const model_hparams = props?.model_hparams ? props.model_hparams : {};
- const trainer_hparams = props?.trainer_hparams ? props.trainer_hparams : {};
- const model_hparams_keys = Object.keys(model_hparams);
- const trainer_hparams_keys = Object.keys(trainer_hparams);
-
- const [searched, setSearched] = useState("");
- const filteredModelHparams = model_hparams_keys
- .filter(key => key.toLowerCase().includes(searched.toLowerCase()))
- .map(key => [key, model_hparams[key]]);
- const filteredTrainerHparams = trainer_hparams_keys
- .filter(key => key.toLowerCase().includes(searched.toLowerCase()))
- .map(key => [key, trainer_hparams[key]]);
-
- const [modelHparamsVisible, setModelHparamsVisible] = useState(true);
- const [trainerHparamsVisible, setTrainerHparamsVisible] = useState(true);
-
- const requestSearch = (event: any) => {
- const searchedVal = event?.target.value;
- setSearched(searchedVal);
- };
-
- const toggleModelHparamsCheckbox = (value: boolean) => {
- setModelHparamsVisible(value);
- };
-
- const toggleTrainerHparamsCheckbox = (value: boolean) => {
- setTrainerHparamsVisible(value);
- };
-
- return (
-
-
- Trainer>}
- checked={trainerHparamsVisible}
- disabled={filteredTrainerHparams.length == 0}
- />
- Model>}
- checked={modelHparamsVisible}
- disabled={filteredModelHparams.length == 0}
- />
-
-
-
- {(!model_hparams || Object.keys(model_hparams).length == 0) && (
- Hyperparameters will appear when script is running.
- )}
-
-
- {modelHparamsVisible && model_hparams_keys && model_hparams_keys.length > 0 && (
-
- Model
-
-
- )}
-
- {trainerHparamsVisible && trainer_hparams && Object.keys(trainer_hparams).length > 0 && (
-
- Trainer
-
-
- )}
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/Launcher.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/Launcher.tsx
deleted file mode 100644
index d16ebd27d3229..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/Launcher.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import { useState } from "react";
-
-import PlayCircleFilledWhiteOutlinedIcon from "@mui/icons-material/PlayCircleFilledWhiteOutlined";
-import cloneDeep from "lodash/cloneDeep";
-
-import { useLightningState } from "hooks/useLightningState";
-import {
- Banner,
- Button,
- Dialog,
- DialogActions,
- DialogContent,
- DialogTitle,
- Label,
- Select,
- Stack,
- TextField,
- Typography,
- useSnackbar,
-} from "lightning-ui/src/design-system/components";
-
-import EnvironmentConfigurator, { data2dict } from "./EnvironmentConfigurator";
-
-export default function Launcher(props: any) {
- // We had to pass the updateLightningState as props because accessing them through useLightningState does not work
- const { updateLightningState } = useLightningState();
- const [hardwareType, setHardwareType] = useState("cpu-small");
- const [environmentVariables, setEnvironmentVariables] = useState([{ name: "", value: "" }]);
- const [showHardwareDialog, setShowHardwareDialog] = useState(false);
- const { enqueueSnackbar } = useSnackbar();
- const [scriptArgs, setScriptArgs] = useState("");
-
- let status_text = "";
- let status_color: "default" | "primary" | "success" | "error" | "warning" | "purple" = "default";
- if (props.script_running) {
- status_text = "Running";
- status_color = "success";
- } else if (props.start_triggered && !props.script_running) {
- status_text = "Starting";
- status_color = "warning";
- } else if (props.script_succeeded) {
- status_text = "Finished";
- status_color = "success";
- } else if (props.script_failed) {
- status_text = "Failed";
- status_color = "error";
- }
-
- const onStartClick = async () => {
- setShowHardwareDialog(true);
- };
-
- const onHardwareDialogCancelClick = () => {
- setShowHardwareDialog(false);
- };
-
- const onHardwareDialogConfirmClick = () => {
- setShowHardwareDialog(false);
- enqueueSnackbar({
- title: "Hardware request sent",
- severity: "info",
- children: "Your script will start once the hardware is ready.",
- });
- if (props.lightningState) {
- const newLightningState = cloneDeep(props.lightningState);
- newLightningState.flows.script_orchestrator.vars.triggered =
- !newLightningState.flows.script_orchestrator.vars.triggered;
-
- newLightningState.flows.script_orchestrator.vars.cloud_compute_args = {
- name: hardwareType,
- };
- newLightningState.flows.script_orchestrator.vars.environment_variables = data2dict(environmentVariables);
- newLightningState.flows.script_orchestrator.vars.script_args =
- scriptArgs.length > 0 ? scriptArgs.trim().split(/[ ]+/) : [];
- updateLightningState(newLightningState);
- }
- };
-
- const handleHardwareTypeChange = (new_value: any) => {
- setHardwareType(new_value);
- };
-
- const handleScriptArgsChange = (new_value: string | null) => {
- if (new_value !== null) {
- setScriptArgs(new_value);
- }
- };
-
- return (
-
-
- }
- disabled={props.script_running || props.start_triggered}
- />
-
- {props.script_path}
-
- {status_text ? :
}
-
-
-
-
-
- Hardware
-
-
-
-
- Hardware selection is only available in the cloud.
-
- Hint: Try running the app with --cloud.
-
-
- Script Arguments
-
-
- Environment Variables
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/ProgressBar.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/ProgressBar.tsx
deleted file mode 100644
index 0fc1bec3999fc..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/ProgressBar.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as React from "react";
-
-import Box from "@mui/material/Box";
-import CircularProgress, { CircularProgressProps, circularProgressClasses } from "@mui/material/CircularProgress";
-import LinearProgress, { linearProgressClasses } from "@mui/material/LinearProgress";
-import Typography from "@mui/material/Typography";
-import { styled } from "@mui/material/styles";
-
-import { LIGHTNING_PURPLE } from "lightning-colors";
-
-const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
- height: 10,
- borderRadius: 5,
- [`&.${linearProgressClasses.colorPrimary}`]: {
- backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800],
- },
- [`& .${linearProgressClasses.bar}`]: {
- borderRadius: 5,
- backgroundColor: { LIGHTNING_PURPLE },
- },
-}));
-
-export default function ProgressBar(props: any) {
- const percentage = props.current ? (props.current * 100) / props.total : 0;
- return (
-
-
-
-
-
- {`${Math.round(percentage)}%`}
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/ProgressBarGroup.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/ProgressBarGroup.tsx
deleted file mode 100644
index 90787ab4f03ee..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/ProgressBarGroup.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { toPath } from "lodash";
-
-import { Stack, Typography } from "lightning-ui/src/design-system/components";
-
-import ProgressBar from "./ProgressBar";
-
-export default function ProgressBarGroup(props: any) {
- const trainer_progress = props.trainer_progress;
- const trainer_state = props.trainer_state;
-
- const primary_bar_title = "Training";
- var secondary_bar_title = "";
- var current = 0;
- var total = 0;
-
- switch (trainer_state?.stage) {
- case "validating":
- secondary_bar_title = "Validation";
- current = trainer_progress?.fit?.val_batch_idx;
- total = trainer_progress?.fit?.total_val_batches;
- break;
- case "testing":
- secondary_bar_title = "Test";
- current = trainer_progress?.test?.test_batch_idx;
- total = trainer_progress?.test?.total_test_batches;
- break;
- case "predicting":
- secondary_bar_title = "Prediction";
- current = trainer_progress?.predict?.predict_batch_idx;
- total = trainer_progress?.predict?.total_predict_batches;
- break;
- default:
- secondary_bar_title = "Hidden";
- }
-
- return (
-
- {primary_bar_title}
-
-
-
{secondary_bar_title}
-
-
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/components/Timer.tsx b/src/lightning/app/cli/pl-app-template/ui/src/components/Timer.tsx
deleted file mode 100644
index 29a6f5bca42b0..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/components/Timer.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useEffect, useState } from "react";
-
-export default function Timer(props: any) {
- const isActive = props?.isActive;
- var [totalSeconds, setTotalSeconds] = useState(0);
-
- var hours = Math.floor(totalSeconds / 3600);
- var totalSeconds = totalSeconds % 3600;
- var minutes = Math.floor(totalSeconds / 60);
- var seconds = totalSeconds % 60;
-
- useEffect(() => {
- let interval: any = null;
- if (isActive) {
- interval = setInterval(() => {
- setTotalSeconds(totalSeconds => totalSeconds + 1);
- }, 1000);
- } else if (!isActive && totalSeconds !== 0) {
- clearInterval(interval);
- }
- return () => clearInterval(interval);
- }, [isActive, totalSeconds]);
-
- return (
-
- {("0" + hours).slice(-2)}:{("0" + minutes).slice(-2)}:{("0" + seconds).slice(-2)}
-
- );
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/hooks/useLightningState.ts b/src/lightning/app/cli/pl-app-template/ui/src/hooks/useLightningState.ts
deleted file mode 100644
index f4aa42604843b..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/hooks/useLightningState.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useEffect, useState } from "react";
-
-import type { LightingState } from "../types/lightning";
-
-interface LightningState {
- subscribe(handler: (state: any) => void): () => void;
- next(state: any): void;
-}
-
-declare global {
- interface Window {
- LightningState: LightningState;
- }
-}
-
-export const useLightningState = () => {
- const [lightningState, setLightningState] = useState();
-
- useEffect(() => {
- const unsubscribe = window.LightningState.subscribe(setLightningState);
-
- return unsubscribe;
- }, []);
-
- const updateLightningState = window.LightningState.next;
-
- return {
- lightningState,
- updateLightningState,
- };
-};
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/index.css b/src/lightning/app/cli/pl-app-template/ui/src/index.css
deleted file mode 100644
index 6e15903b858f8..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/index.css
+++ /dev/null
@@ -1,19 +0,0 @@
-body {
- margin: 0;
- font-family: "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- overflow-y: overlay;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
-}
-
-pre {
- overflow: scroll;
- font-size: 0.8em;
- max-height: 40em;
-}
-
-@import "lightning-ui/src/design-system/style.css";
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/index.tsx b/src/lightning/app/cli/pl-app-template/ui/src/index.tsx
deleted file mode 100644
index b1feb569d6770..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/index.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from "react";
-
-import FontFaceObserver from "fontfaceobserver";
-import ReactDOM from "react-dom";
-
-import App from "./App";
-import "./index.css";
-import reportWebVitals from "./reportWebVitals";
-
-// Make sure Roboto Mono is loaded before rendering the app as it is used within a canvas element
-// The rest of the fonts don't need to be loaded before the render as they will be applied
-// as soon as they are available
-const font = new FontFaceObserver("Roboto Mono");
-font.load().then(() => {
- ReactDOM.render(
-
-
- ,
- document.getElementById("root"),
- );
-});
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/lightning-colors.ts b/src/lightning/app/cli/pl-app-template/ui/src/lightning-colors.ts
deleted file mode 100644
index a910b9a1dc455..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/lightning-colors.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export const LIGHTNING_PURPLE = "#6162D1";
-export const BREADCRUMBS_BACKGROUND = "#F7F8FB";
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/react-app-env.d.ts b/src/lightning/app/cli/pl-app-template/ui/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5fc6b2c..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/reportWebVitals.ts b/src/lightning/app/cli/pl-app-template/ui/src/reportWebVitals.ts
deleted file mode 100644
index 5fa3583b7500b..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/reportWebVitals.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReportHandler } from "web-vitals";
-
-const reportWebVitals = (onPerfEntry?: ReportHandler) => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/src/lightning/app/cli/pl-app-template/ui/src/types/lightning.ts b/src/lightning/app/cli/pl-app-template/ui/src/types/lightning.ts
deleted file mode 100644
index 02893ca2a9321..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/src/types/lightning.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Represents the internal state of a Lightning app as exposed by
- * the `/state` endpoint of the Lightning HTTP API.
- */
-export type LightingState = {
- vars: {
- _layout: Layout | Layout[];
- [key: string]: any;
- };
- calls: {
- [key: string]: {
- name: string;
- call_hash: string;
- ret: boolean;
- };
- };
- flows: {
- [key: string]: ChildState;
- };
- works: {
- [key: string]: any;
- };
- changes: {
- [key: string]: any;
- };
- app_state: {
- stage: AppStage;
- };
-};
-
-export type ChildState = Omit;
-
-export type Layout = LayoutBranch | LayoutLeaf;
-
-export type LayoutBranch = {
- name: string;
- content: string;
-};
-
-export type LayoutLeaf = {
- name: string;
- type: LayoutType;
- source?: string;
- target: string;
-};
-
-export enum LayoutType {
- web = "web",
- streamlit = "streamlit",
-}
-
-export enum AppStage {
- blocking = "blocking",
- restarting = "restarting",
- running = "running",
- stopping = "stopping",
-}
diff --git a/src/lightning/app/cli/pl-app-template/ui/tsconfig.json b/src/lightning/app/cli/pl-app-template/ui/tsconfig.json
deleted file mode 100644
index cf7275fc65df5..0000000000000
--- a/src/lightning/app/cli/pl-app-template/ui/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "baseUrl": "src/",
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "types": ["node"],
- "include": ["src"]
-}
diff --git a/src/lightning/app/cli/react-ui-template/README.md b/src/lightning/app/cli/react-ui-template/README.md
deleted file mode 100644
index 1f28215d22199..0000000000000
--- a/src/lightning/app/cli/react-ui-template/README.md
+++ /dev/null
@@ -1,103 +0,0 @@
-# React-ui template
-
-This is a full react template ready to use in a component
-
-This UI was automatically generated with:
-
-```commandline
-lightning_app init react-ui
-```
-
-### Delete files
-
-This template has 3 main files/folders:
-
-- README.md
-- example_app.py
-- ui
-
-The README.md and example_app.py are here to help you get started. However, they should be deleted.
-All you need is the `ui` folder when you connect this react UI to your component.
-
-### Run the example_app
-
-This template comes with `example_app.py` to show how to integrate the UI into a component.
-
-run it with:
-
-```bash
-lightning_app run app react-ui/example_app.py
-```
-
-### Connect React to your component
-
-To connect the react UI to your component, simply point the `StaticWebFrontend` to the `dist/` folder generated by yarn after building your react website.
-
-```python
-import lightning as L
-
-
-class YourComponent(L.LightningFlow):
- def configure_layout(self):
- return Lapp.frontend.StaticWebFrontend(Path(__file__).parent / "react-ui/src/dist")
-```
-
-### Set up interactions between React and the component
-
-To communicate bi-directionally between react and the component, use the `useLightningState` module:
-
-```js
-// App.tsx
-
-import { useLightningState } from "./hooks/useLightningState";
-import cloneDeep from "lodash/cloneDeep";
-
-function App() {
- const { lightningState, updateLightningState } = useLightningState();
-
- const modify_and_send_back_the_state = async (event: ChangeEvent) => {
- if (lightningState) {
- const newLightningState = cloneDeep(lightningState);
- // Update the state and send it back.
- newLightningState.flows.counter += 1
-
- updateLightningState(newLightningState);
- }
- };
-
- return (
-
-
- );
-}
-
-export default App;
-```
-
-#### Application Folder Structure
-
-You would find the following structure in the folder `/react`.
-
-```
-react
-├── dist
-├── index.html
-├── node_modules
-├── package.json
-├── src
-│ ├── App.css
-│ ├── App.tsx
-│ ├── hooks
-│ ├── index.css
-│ ├── main.tsx
-│ ├── types
-│ └── vite-env.d.ts
-├── tsconfig.json
-├── tsconfig.node.json
-├── vite.config.ts
-└── yarn.lock
-```
-
-### Read the docs
-
-For more features and information, [read the documentation](https://ideal-bassoon-5313f557.pages.github.io/workflows/add_web_ui/react/index.html).
diff --git a/src/lightning/app/cli/react-ui-template/example_app.py b/src/lightning/app/cli/react-ui-template/example_app.py
deleted file mode 100644
index 362a2a0477f7e..0000000000000
--- a/src/lightning/app/cli/react-ui-template/example_app.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# example_app.py
-
-from pathlib import Path
-
-from lightning.app import LightningApp, LightningFlow, frontend
-
-
-class YourComponent(LightningFlow):
- def __init__(self):
- super().__init__()
- self.message_to_print = "Hello World!"
- self.should_print = False
-
- def configure_layout(self):
- return frontend.StaticWebFrontend(Path(__file__).parent / "ui/dist")
-
-
-class HelloLitReact(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
- self.react_ui = YourComponent()
-
- def run(self):
- if self.react_ui.should_print:
- print(f"{self.counter}: {self.react_ui.message_to_print}")
- self.counter += 1
-
- def configure_layout(self):
- return [{"name": "React UI", "content": self.react_ui}]
-
-
-app = LightningApp(HelloLitReact())
diff --git a/src/lightning/app/cli/react-ui-template/ui/index.html b/src/lightning/app/cli/react-ui-template/ui/index.html
deleted file mode 100644
index 837abf069d329..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
- Vite App
-
-
-
-
-
-
-
diff --git a/src/lightning/app/cli/react-ui-template/ui/package.json b/src/lightning/app/cli/react-ui-template/ui/package.json
deleted file mode 100644
index d43665302c55a..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/package.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "hello-world",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "start": "vite",
- "build": "tsc --noEmit && vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "@emotion/react": "^11.8.2",
- "@emotion/styled": "^11.8.1",
- "@mui/material": "5.8.5",
- "axios": "^1.6.0",
- "lodash": "^4.17.21",
- "nanoid": "^3.3.1",
- "react": "^17.0.2",
- "react-dom": "^17.0.2"
- },
- "devDependencies": {
- "@types/lodash": "^4.14.179",
- "@types/react": "^18.0.1",
- "@types/react-dom": "^18.0.0",
- "@vitejs/plugin-react": "^1.0.7",
- "prettier": "^2.5.1",
- "typescript": "^4.5.4",
- "vite": "^2.9.17"
- },
- "main": "index.js",
- "license": "MIT"
-}
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/App.css b/src/lightning/app/cli/react-ui-template/ui/src/App.css
deleted file mode 100644
index 4fce394eeb216..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/App.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.App {
- text-align: center;
- height: 100vh;
- background: rgb(34, 193, 195);
- background: linear-gradient(0deg, rgba(34, 193, 195, 1) 0%, rgba(253, 187, 45, 1) 100%);
-}
-
-.App .wrapper {
- transform: translateY(100%);
-}
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/App.tsx b/src/lightning/app/cli/react-ui-template/ui/src/App.tsx
deleted file mode 100644
index 716280938fbc4..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/App.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// App.tsx
-
-import { Button } from "@mui/material";
-import { TextField } from "@mui/material";
-import Box from "@mui/material/Box";
-import { ChangeEvent } from "react";
-import cloneDeep from "lodash/cloneDeep";
-
-import "./App.css";
-import { useLightningState } from "./hooks/useLightningState";
-
-function App() {
- const { lightningState, updateLightningState } = useLightningState();
-
- const counter = lightningState?.vars.counter;
-
- const handleClick = async () => {
- if (lightningState) {
- const newLightningState = cloneDeep(lightningState);
- newLightningState.flows.react_ui.vars.should_print = !newLightningState.flows.react_ui.vars.should_print;
-
- updateLightningState(newLightningState);
- }
- };
-
- const handleTextField = async (event: ChangeEvent) => {
- if (lightningState) {
- const newLightningState = cloneDeep(lightningState);
- newLightningState.flows.react_ui.vars.message_to_print = event.target.value;
-
- updateLightningState(newLightningState);
- }
- };
-
- return (
-
-
-
- handleClick()}>
-
- {lightningState?.["flows"]?.["react_ui"]?.["vars"]?.["should_print"] ? "Stop printing" : "Start Printing"}
-
-
-
-
-
-
-
-
-
Total number of prints in your terminal: {counter}
-
-
-
-
- );
-}
-
-export default App;
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/favicon.svg b/src/lightning/app/cli/react-ui-template/ui/src/favicon.svg
deleted file mode 100644
index de4aeddc12bdf..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/favicon.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/hooks/useLightningState.ts b/src/lightning/app/cli/react-ui-template/ui/src/hooks/useLightningState.ts
deleted file mode 100644
index 5a44a304c0d9b..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/hooks/useLightningState.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useState, useEffect } from "react";
-
-import type { LightingState } from "../types/lightning";
-
-interface LightningState {
- subscribe(handler: (state: any) => void): () => void;
- next(state: any): void;
-}
-
-declare global {
- interface Window {
- LightningState: LightningState;
- }
-}
-
-export const useLightningState = () => {
- const [lightningState, setLightningState] = useState();
-
- useEffect(() => {
- const unsubscribe = window.LightningState.subscribe(setLightningState);
-
- return unsubscribe;
- }, []);
-
- const updateLightningState = window.LightningState.next;
-
- return {
- lightningState,
- updateLightningState,
- };
-};
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/index.css b/src/lightning/app/cli/react-ui-template/ui/src/index.css
deleted file mode 100644
index e9927237d70c0..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/index.css
+++ /dev/null
@@ -1,11 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
- "Droid Sans", "Helvetica Neue", sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
-}
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/main.tsx b/src/lightning/app/cli/react-ui-template/ui/src/main.tsx
deleted file mode 100644
index e7cd236bbac70..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/main.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom";
-import "./index.css";
-import App from "./App";
-
-ReactDOM.render(
-
-
- ,
- document.getElementById("root"),
-);
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/types/lightning.ts b/src/lightning/app/cli/react-ui-template/ui/src/types/lightning.ts
deleted file mode 100644
index 02893ca2a9321..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/types/lightning.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Represents the internal state of a Lightning app as exposed by
- * the `/state` endpoint of the Lightning HTTP API.
- */
-export type LightingState = {
- vars: {
- _layout: Layout | Layout[];
- [key: string]: any;
- };
- calls: {
- [key: string]: {
- name: string;
- call_hash: string;
- ret: boolean;
- };
- };
- flows: {
- [key: string]: ChildState;
- };
- works: {
- [key: string]: any;
- };
- changes: {
- [key: string]: any;
- };
- app_state: {
- stage: AppStage;
- };
-};
-
-export type ChildState = Omit;
-
-export type Layout = LayoutBranch | LayoutLeaf;
-
-export type LayoutBranch = {
- name: string;
- content: string;
-};
-
-export type LayoutLeaf = {
- name: string;
- type: LayoutType;
- source?: string;
- target: string;
-};
-
-export enum LayoutType {
- web = "web",
- streamlit = "streamlit",
-}
-
-export enum AppStage {
- blocking = "blocking",
- restarting = "restarting",
- running = "running",
- stopping = "stopping",
-}
diff --git a/src/lightning/app/cli/react-ui-template/ui/src/vite-env.d.ts b/src/lightning/app/cli/react-ui-template/ui/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe2a0061..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/src/lightning/app/cli/react-ui-template/ui/tsconfig.json b/src/lightning/app/cli/react-ui-template/ui/tsconfig.json
deleted file mode 100644
index c8bdc64082aa2..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "compilerOptions": {
- "target": "ESNext",
- "useDefineForClassFields": true,
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
- "allowJs": false,
- "skipLibCheck": false,
- "esModuleInterop": false,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "module": "ESNext",
- "moduleResolution": "Node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": ["src"],
- "references": [{ "path": "./tsconfig.node.json" }]
-}
diff --git a/src/lightning/app/cli/react-ui-template/ui/tsconfig.node.json b/src/lightning/app/cli/react-ui-template/ui/tsconfig.node.json
deleted file mode 100644
index e993792cb12c9..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/tsconfig.node.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "compilerOptions": {
- "composite": true,
- "module": "esnext",
- "moduleResolution": "node"
- },
- "include": ["vite.config.ts"]
-}
diff --git a/src/lightning/app/cli/react-ui-template/ui/vite.config.ts b/src/lightning/app/cli/react-ui-template/ui/vite.config.ts
deleted file mode 100644
index 82632ac0ed8e6..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/vite.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-
-// https://vitejs.dev/config/
-export default defineConfig({
- plugins: [react()],
- // NOTE: Component UI's are served under `/{componentName}/` subpath, so the app needs to be configured for relative base path.
- base: "./",
-});
diff --git a/src/lightning/app/cli/react-ui-template/ui/yarn.lock b/src/lightning/app/cli/react-ui-template/ui/yarn.lock
deleted file mode 100644
index 50570338c1877..0000000000000
--- a/src/lightning/app/cli/react-ui-template/ui/yarn.lock
+++ /dev/null
@@ -1,1278 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@ampproject/remapping@^2.1.0":
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
- integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
- dependencies:
- "@jridgewell/gen-mapping" "^0.1.0"
- "@jridgewell/trace-mapping" "^0.3.9"
-
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
- integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
- dependencies:
- "@babel/highlight" "^7.18.6"
-
-"@babel/code-frame@^7.22.13":
- version "7.22.13"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
- integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
- dependencies:
- "@babel/highlight" "^7.22.13"
- chalk "^2.4.2"
-
-"@babel/compat-data@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.6.tgz#8b37d24e88e8e21c499d4328db80577d8882fa53"
- integrity sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==
-
-"@babel/core@^7.17.10":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d"
- integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==
- dependencies:
- "@ampproject/remapping" "^2.1.0"
- "@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.18.6"
- "@babel/helper-compilation-targets" "^7.18.6"
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helpers" "^7.18.6"
- "@babel/parser" "^7.18.6"
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
- convert-source-map "^1.7.0"
- debug "^4.1.0"
- gensync "^1.0.0-beta.2"
- json5 "^2.2.1"
- semver "^6.3.0"
-
-"@babel/generator@^7.18.6":
- version "7.18.7"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd"
- integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==
- dependencies:
- "@babel/types" "^7.18.7"
- "@jridgewell/gen-mapping" "^0.3.2"
- jsesc "^2.5.1"
-
-"@babel/generator@^7.23.0":
- version "7.23.0"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
- integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
- dependencies:
- "@babel/types" "^7.23.0"
- "@jridgewell/gen-mapping" "^0.3.2"
- "@jridgewell/trace-mapping" "^0.3.17"
- jsesc "^2.5.1"
-
-"@babel/helper-annotate-as-pure@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
- integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==
- dependencies:
- "@babel/types" "^7.18.6"
-
-"@babel/helper-compilation-targets@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96"
- integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==
- dependencies:
- "@babel/compat-data" "^7.18.6"
- "@babel/helper-validator-option" "^7.18.6"
- browserslist "^4.20.2"
- semver "^6.3.0"
-
-"@babel/helper-environment-visitor@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7"
- integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==
-
-"@babel/helper-environment-visitor@^7.22.20":
- version "7.22.20"
- resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
- integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
-
-"@babel/helper-function-name@^7.23.0":
- version "7.23.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
- integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
- dependencies:
- "@babel/template" "^7.22.15"
- "@babel/types" "^7.23.0"
-
-"@babel/helper-hoist-variables@^7.22.5":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
- integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
- dependencies:
- "@babel/types" "^7.22.5"
-
-"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
- integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==
- dependencies:
- "@babel/types" "^7.18.6"
-
-"@babel/helper-module-transforms@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz#57e3ca669e273d55c3cda55e6ebf552f37f483c8"
- integrity sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==
- dependencies:
- "@babel/helper-environment-visitor" "^7.18.6"
- "@babel/helper-module-imports" "^7.18.6"
- "@babel/helper-simple-access" "^7.18.6"
- "@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/helper-validator-identifier" "^7.18.6"
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
-
-"@babel/helper-plugin-utils@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d"
- integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==
-
-"@babel/helper-simple-access@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea"
- integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==
- dependencies:
- "@babel/types" "^7.18.6"
-
-"@babel/helper-split-export-declaration@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
- integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
- dependencies:
- "@babel/types" "^7.18.6"
-
-"@babel/helper-split-export-declaration@^7.22.6":
- version "7.22.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
- integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
- dependencies:
- "@babel/types" "^7.22.5"
-
-"@babel/helper-string-parser@^7.22.5":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
- integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
-
-"@babel/helper-validator-identifier@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
- integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
-
-"@babel/helper-validator-identifier@^7.22.20":
- version "7.22.20"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
- integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
-
-"@babel/helper-validator-option@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
- integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==
-
-"@babel/helpers@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd"
- integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==
- dependencies:
- "@babel/template" "^7.18.6"
- "@babel/traverse" "^7.18.6"
- "@babel/types" "^7.18.6"
-
-"@babel/highlight@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
- integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
- dependencies:
- "@babel/helper-validator-identifier" "^7.18.6"
- chalk "^2.0.0"
- js-tokens "^4.0.0"
-
-"@babel/highlight@^7.22.13":
- version "7.22.20"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
- integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
- dependencies:
- "@babel/helper-validator-identifier" "^7.22.20"
- chalk "^2.4.2"
- js-tokens "^4.0.0"
-
-"@babel/parser@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc"
- integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==
-
-"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
- version "7.23.0"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
- integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
-
-"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0"
- integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
-
-"@babel/plugin-transform-react-jsx-development@^7.16.7":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5"
- integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==
- dependencies:
- "@babel/plugin-transform-react-jsx" "^7.18.6"
-
-"@babel/plugin-transform-react-jsx-self@^7.16.7":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7"
- integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
-
-"@babel/plugin-transform-react-jsx-source@^7.16.7":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz#06e9ae8a14d2bc19ce6e3c447d842032a50598fc"
- integrity sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
-
-"@babel/plugin-transform-react-jsx@^7.17.3", "@babel/plugin-transform-react-jsx@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff"
- integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw==
- dependencies:
- "@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-module-imports" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/plugin-syntax-jsx" "^7.18.6"
- "@babel/types" "^7.18.6"
-
-"@babel/runtime@^7.13.10", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580"
- integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==
- dependencies:
- regenerator-runtime "^0.13.4"
-
-"@babel/template@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31"
- integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==
- dependencies:
- "@babel/code-frame" "^7.18.6"
- "@babel/parser" "^7.18.6"
- "@babel/types" "^7.18.6"
-
-"@babel/template@^7.22.15":
- version "7.22.15"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
- integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
- dependencies:
- "@babel/code-frame" "^7.22.13"
- "@babel/parser" "^7.22.15"
- "@babel/types" "^7.22.15"
-
-"@babel/traverse@^7.18.6":
- version "7.23.2"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
- integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
- dependencies:
- "@babel/code-frame" "^7.22.13"
- "@babel/generator" "^7.23.0"
- "@babel/helper-environment-visitor" "^7.22.20"
- "@babel/helper-function-name" "^7.23.0"
- "@babel/helper-hoist-variables" "^7.22.5"
- "@babel/helper-split-export-declaration" "^7.22.6"
- "@babel/parser" "^7.23.0"
- "@babel/types" "^7.23.0"
- debug "^4.1.0"
- globals "^11.1.0"
-
-"@babel/types@^7.18.6", "@babel/types@^7.18.7":
- version "7.18.7"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.7.tgz#a4a2c910c15040ea52cdd1ddb1614a65c8041726"
- integrity sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==
- dependencies:
- "@babel/helper-validator-identifier" "^7.18.6"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
- version "7.23.0"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
- integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
- dependencies:
- "@babel/helper-string-parser" "^7.22.5"
- "@babel/helper-validator-identifier" "^7.22.20"
- to-fast-properties "^2.0.0"
-
-"@emotion/babel-plugin@^11.7.1":
- version "11.9.2"
- resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95"
- integrity sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==
- dependencies:
- "@babel/helper-module-imports" "^7.12.13"
- "@babel/plugin-syntax-jsx" "^7.12.13"
- "@babel/runtime" "^7.13.10"
- "@emotion/hash" "^0.8.0"
- "@emotion/memoize" "^0.7.5"
- "@emotion/serialize" "^1.0.2"
- babel-plugin-macros "^2.6.1"
- convert-source-map "^1.5.0"
- escape-string-regexp "^4.0.0"
- find-root "^1.1.0"
- source-map "^0.5.7"
- stylis "4.0.13"
-
-"@emotion/cache@^11.7.1", "@emotion/cache@^11.9.3":
- version "11.9.3"
- resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.9.3.tgz#96638449f6929fd18062cfe04d79b29b44c0d6cb"
- integrity sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==
- dependencies:
- "@emotion/memoize" "^0.7.4"
- "@emotion/sheet" "^1.1.1"
- "@emotion/utils" "^1.0.0"
- "@emotion/weak-memoize" "^0.2.5"
- stylis "4.0.13"
-
-"@emotion/hash@^0.8.0":
- version "0.8.0"
- resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
- integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
-
-"@emotion/is-prop-valid@^1.1.2", "@emotion/is-prop-valid@^1.1.3":
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.1.3.tgz#f0907a416368cf8df9e410117068e20fe87c0a3a"
- integrity sha512-RFg04p6C+1uO19uG8N+vqanzKqiM9eeV1LDOG3bmkYmuOj7NbKNlFC/4EZq5gnwAIlcC/jOT24f8Td0iax2SXA==
- dependencies:
- "@emotion/memoize" "^0.7.4"
-
-"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5":
- version "0.7.5"
- resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
- integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
-
-"@emotion/react@^11.8.2":
- version "11.9.3"
- resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.3.tgz#f4f4f34444f6654a2e550f5dab4f2d360c101df9"
- integrity sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==
- dependencies:
- "@babel/runtime" "^7.13.10"
- "@emotion/babel-plugin" "^11.7.1"
- "@emotion/cache" "^11.9.3"
- "@emotion/serialize" "^1.0.4"
- "@emotion/utils" "^1.1.0"
- "@emotion/weak-memoize" "^0.2.5"
- hoist-non-react-statics "^3.3.1"
-
-"@emotion/serialize@^1.0.2", "@emotion/serialize@^1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.4.tgz#ff31fd11bb07999611199c2229e152faadc21a3c"
- integrity sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==
- dependencies:
- "@emotion/hash" "^0.8.0"
- "@emotion/memoize" "^0.7.4"
- "@emotion/unitless" "^0.7.5"
- "@emotion/utils" "^1.0.0"
- csstype "^3.0.2"
-
-"@emotion/sheet@^1.1.1":
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.1.tgz#015756e2a9a3c7c5f11d8ec22966a8dbfbfac787"
- integrity sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA==
-
-"@emotion/styled@^11.8.1":
- version "11.9.3"
- resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.9.3.tgz#47f0c71137fec7c57035bf3659b52fb536792340"
- integrity sha512-o3sBNwbtoVz9v7WB1/Y/AmXl69YHmei2mrVnK7JgyBJ//Rst5yqPZCecEJlMlJrFeWHp+ki/54uN265V2pEcXA==
- dependencies:
- "@babel/runtime" "^7.13.10"
- "@emotion/babel-plugin" "^11.7.1"
- "@emotion/is-prop-valid" "^1.1.3"
- "@emotion/serialize" "^1.0.4"
- "@emotion/utils" "^1.1.0"
-
-"@emotion/unitless@^0.7.5":
- version "0.7.5"
- resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
- integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
-
-"@emotion/utils@^1.0.0", "@emotion/utils@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf"
- integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==
-
-"@emotion/weak-memoize@^0.2.5":
- version "0.2.5"
- resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
- integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
-
-"@jridgewell/gen-mapping@^0.1.0":
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
- integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==
- dependencies:
- "@jridgewell/set-array" "^1.0.0"
- "@jridgewell/sourcemap-codec" "^1.4.10"
-
-"@jridgewell/gen-mapping@^0.3.2":
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
- integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
- dependencies:
- "@jridgewell/set-array" "^1.0.1"
- "@jridgewell/sourcemap-codec" "^1.4.10"
- "@jridgewell/trace-mapping" "^0.3.9"
-
-"@jridgewell/resolve-uri@^3.0.3":
- version "3.0.8"
- resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1"
- integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==
-
-"@jridgewell/resolve-uri@^3.1.0":
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
- integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
-
-"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
- integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
-
-"@jridgewell/sourcemap-codec@^1.4.10":
- version "1.4.14"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
- integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
-
-"@jridgewell/sourcemap-codec@^1.4.14":
- version "1.4.15"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
- integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
-
-"@jridgewell/trace-mapping@^0.3.17":
- version "0.3.20"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
- integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
- dependencies:
- "@jridgewell/resolve-uri" "^3.1.0"
- "@jridgewell/sourcemap-codec" "^1.4.14"
-
-"@jridgewell/trace-mapping@^0.3.9":
- version "0.3.14"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
- integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
- dependencies:
- "@jridgewell/resolve-uri" "^3.0.3"
- "@jridgewell/sourcemap-codec" "^1.4.10"
-
-"@mui/base@5.0.0-alpha.86":
- version "5.0.0-alpha.86"
- resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.86.tgz#7ac5af939cec7e763c1bf49bf5e30bb9464c4ebf"
- integrity sha512-0vi/Nni1mizrgrzKeyksEjw5JVSrgT8Vr2NhxzFtYxqpMgtdSrBvcmcuzBf9kE/ECMPbgpSIcqv0nLbLZUYkOQ==
- dependencies:
- "@babel/runtime" "^7.17.2"
- "@emotion/is-prop-valid" "^1.1.2"
- "@mui/types" "^7.1.4"
- "@mui/utils" "^5.8.4"
- "@popperjs/core" "^2.11.5"
- clsx "^1.1.1"
- prop-types "^15.8.1"
- react-is "^17.0.2"
-
-"@mui/material@5.8.5":
- version "5.8.5"
- resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.8.5.tgz#a1a79fc57b212a9781eb4a53e9995c4a9df04753"
- integrity sha512-wngPXlOI9BurLSGlObQM/2L0QFFaIcvJnDK5A+ALxuUyuQnPviVWfC1l/r8rPlxQ4PCbSYpq3gzLlgnLoWcO/g==
- dependencies:
- "@babel/runtime" "^7.17.2"
- "@mui/base" "5.0.0-alpha.86"
- "@mui/system" "^5.8.5"
- "@mui/types" "^7.1.4"
- "@mui/utils" "^5.8.4"
- "@types/react-transition-group" "^4.4.4"
- clsx "^1.1.1"
- csstype "^3.1.0"
- prop-types "^15.8.1"
- react-is "^17.0.2"
- react-transition-group "^4.4.2"
-
-"@mui/private-theming@^5.8.6":
- version "5.8.6"
- resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.8.6.tgz#db2bafeda1699e43e67b3ff4f770d6b7a234501f"
- integrity sha512-yHsJk1qU9r/q0DlnxGRJPHyM0Y/nUv8FTNgDTiI9I58GWuVuZqeTUr7JRvPh6ybeP/FLtW5eXEavRK9wxVk4uQ==
- dependencies:
- "@babel/runtime" "^7.17.2"
- "@mui/utils" "^5.8.6"
- prop-types "^15.8.1"
-
-"@mui/styled-engine@^5.8.0":
- version "5.8.0"
- resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.8.0.tgz#89ed42efe7c8749e5a60af035bc5d3a6bea362bf"
- integrity sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==
- dependencies:
- "@babel/runtime" "^7.17.2"
- "@emotion/cache" "^11.7.1"
- prop-types "^15.8.1"
-
-"@mui/system@^5.8.5":
- version "5.8.6"
- resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.8.6.tgz#aed7e501c513429dab9cfbbe86da5dcd056c2a0a"
- integrity sha512-+a+rD58XltKQHDrrjcuCta2cUBqdnLDUDwnphSLCMFigRl8/uk+R+fdQRlMNRXAOgnMb8ioWIgfjxri5pmTH4A==
- dependencies:
- "@babel/runtime" "^7.17.2"
- "@mui/private-theming" "^5.8.6"
- "@mui/styled-engine" "^5.8.0"
- "@mui/types" "^7.1.4"
- "@mui/utils" "^5.8.6"
- clsx "^1.1.1"
- csstype "^3.1.0"
- prop-types "^15.8.1"
-
-"@mui/types@^7.1.4":
- version "7.1.4"
- resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.4.tgz#4185c05d6df63ec673cda15feab80440abadc764"
- integrity sha512-uveM3byMbthO+6tXZ1n2zm0W3uJCQYtwt/v5zV5I77v2v18u0ITkb8xwhsDD2i3V2Kye7SaNR6FFJ6lMuY/WqQ==
-
-"@mui/utils@^5.8.4", "@mui/utils@^5.8.6":
- version "5.8.6"
- resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.8.6.tgz#543de64a64bb9135316ecfd91d75a8740544d79f"
- integrity sha512-QM2Sd1xZo2jOt2Vz5Rmro+pi2FLJyiv4+OjxkUwXR3oUM65KSMAMLl/KNYU55s3W3DLRFP5MVwE4FhAbHseHAg==
- dependencies:
- "@babel/runtime" "^7.17.2"
- "@types/prop-types" "^15.7.5"
- "@types/react-is" "^16.7.1 || ^17.0.0"
- prop-types "^15.8.1"
- react-is "^17.0.2"
-
-"@popperjs/core@^2.11.5":
- version "2.11.5"
- resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
- integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
-
-"@rollup/pluginutils@^4.2.1":
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
- integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
- dependencies:
- estree-walker "^2.0.1"
- picomatch "^2.2.2"
-
-"@types/lodash@^4.14.179":
- version "4.14.182"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
- integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
-
-"@types/parse-json@^4.0.0":
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
- integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
-
-"@types/prop-types@*", "@types/prop-types@^15.7.5":
- version "15.7.5"
- resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
- integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
-
-"@types/react-dom@^18.0.0":
- version "18.0.5"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a"
- integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==
- dependencies:
- "@types/react" "*"
-
-"@types/react-is@^16.7.1 || ^17.0.0":
- version "17.0.3"
- resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a"
- integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
- dependencies:
- "@types/react" "*"
-
-"@types/react-transition-group@^4.4.4":
- version "4.4.5"
- resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
- integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
- dependencies:
- "@types/react" "*"
-
-"@types/react@*", "@types/react@^18.0.1":
- version "18.0.14"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d"
- integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==
- dependencies:
- "@types/prop-types" "*"
- "@types/scheduler" "*"
- csstype "^3.0.2"
-
-"@types/scheduler@*":
- version "0.16.2"
- resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
- integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
-
-"@vitejs/plugin-react@^1.0.7":
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-1.3.2.tgz#2fcf0b6ce9bcdcd4cec5c760c199779d5657ece1"
- integrity sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==
- dependencies:
- "@babel/core" "^7.17.10"
- "@babel/plugin-transform-react-jsx" "^7.17.3"
- "@babel/plugin-transform-react-jsx-development" "^7.16.7"
- "@babel/plugin-transform-react-jsx-self" "^7.16.7"
- "@babel/plugin-transform-react-jsx-source" "^7.16.7"
- "@rollup/pluginutils" "^4.2.1"
- react-refresh "^0.13.0"
- resolve "^1.22.0"
-
-ansi-styles@^3.2.1:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
- integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
- dependencies:
- color-convert "^1.9.0"
-
-asynckit@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
- integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
-
-axios@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102"
- integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==
- dependencies:
- follow-redirects "^1.15.0"
- form-data "^4.0.0"
- proxy-from-env "^1.1.0"
-
-babel-plugin-macros@^2.6.1:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
- integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
- dependencies:
- "@babel/runtime" "^7.7.2"
- cosmiconfig "^6.0.0"
- resolve "^1.12.0"
-
-browserslist@^4.20.2:
- version "4.21.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00"
- integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==
- dependencies:
- caniuse-lite "^1.0.30001359"
- electron-to-chromium "^1.4.172"
- node-releases "^2.0.5"
- update-browserslist-db "^1.0.4"
-
-callsites@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
- integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-
-caniuse-lite@^1.0.30001359:
- version "1.0.30001361"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz#ba2adb2527566fb96f3ac7c67698ae7fc495a28d"
- integrity sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==
-
-chalk@^2.0.0, chalk@^2.4.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
- integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
- dependencies:
- ansi-styles "^3.2.1"
- escape-string-regexp "^1.0.5"
- supports-color "^5.3.0"
-
-clsx@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
- integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
-
-color-convert@^1.9.0:
- version "1.9.3"
- resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
- integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
- dependencies:
- color-name "1.1.3"
-
-color-name@1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
- integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-
-combined-stream@^1.0.8:
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
- integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
- dependencies:
- delayed-stream "~1.0.0"
-
-convert-source-map@^1.5.0, convert-source-map@^1.7.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
- integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
- dependencies:
- safe-buffer "~5.1.1"
-
-cosmiconfig@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
- integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
- dependencies:
- "@types/parse-json" "^4.0.0"
- import-fresh "^3.1.0"
- parse-json "^5.0.0"
- path-type "^4.0.0"
- yaml "^1.7.2"
-
-csstype@^3.0.2, csstype@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
- integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
-
-debug@^4.1.0:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
- integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
- dependencies:
- ms "2.1.2"
-
-delayed-stream@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
- integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
-
-dom-helpers@^5.0.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
- integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
- dependencies:
- "@babel/runtime" "^7.8.7"
- csstype "^3.0.2"
-
-electron-to-chromium@^1.4.172:
- version "1.4.174"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.174.tgz#ffdf57f26dd4558c5aabdb4b190c47af1c4e443b"
- integrity sha512-JER+w+9MV2MBVFOXxP036bLlNOnzbYAWrWU8sNUwoOO69T3w4564WhM5H5atd8VVS8U4vpi0i0kdoYzm1NPQgQ==
-
-error-ex@^1.3.1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
- integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
- dependencies:
- is-arrayish "^0.2.1"
-
-esbuild-android-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.48.tgz#7e6394a0e517f738641385aaf553c7e4fb6d1ae3"
- integrity sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==
-
-esbuild-android-arm64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.48.tgz#6877566be0f82dd5a43030c0007d06ece7f7c02f"
- integrity sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==
-
-esbuild-darwin-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.48.tgz#ea3caddb707d88f844b1aa1dea5ff3b0a71ef1fd"
- integrity sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==
-
-esbuild-darwin-arm64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.48.tgz#4e5eaab54df66cc319b76a2ac0e8af4e6f0d9c2f"
- integrity sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==
-
-esbuild-freebsd-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.48.tgz#47b5abc7426eae66861490ffbb380acc67af5b15"
- integrity sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==
-
-esbuild-freebsd-arm64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.48.tgz#e8c54c8637cd44feed967ea12338b0a4da3a7b11"
- integrity sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==
-
-esbuild-linux-32@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.48.tgz#229cf3246de2b7937c3ac13fac622d4d7a1344c5"
- integrity sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==
-
-esbuild-linux-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.48.tgz#7c0e7226c02c42aacc5656c36977493dc1e96c4f"
- integrity sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==
-
-esbuild-linux-arm64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.48.tgz#0af1eda474b5c6cc0cace8235b74d0cb8fcf57a7"
- integrity sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==
-
-esbuild-linux-arm@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.48.tgz#de4d1fa6b77cdcd00e2bb43dd0801e4680f0ab52"
- integrity sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==
-
-esbuild-linux-mips64le@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.48.tgz#822c1778495f7868e990d4da47ad7281df28fd15"
- integrity sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==
-
-esbuild-linux-ppc64le@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.48.tgz#55de0a9ec4a48fedfe82a63e083164d001709447"
- integrity sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==
-
-esbuild-linux-riscv64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.48.tgz#cd2b7381880b2f4b21a5a598fb673492120f18a5"
- integrity sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==
-
-esbuild-linux-s390x@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.48.tgz#4b319eca2a5c64637fc7397ffbd9671719cdb6bf"
- integrity sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==
-
-esbuild-netbsd-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.48.tgz#c27cde8b5cb55dcc227943a18ab078fb98d0adbf"
- integrity sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==
-
-esbuild-openbsd-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.48.tgz#af5ab2d1cb41f09064bba9465fc8bf1309150df1"
- integrity sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==
-
-esbuild-sunos-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.48.tgz#db3ae20526055cf6fd5c4582676233814603ac54"
- integrity sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==
-
-esbuild-windows-32@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.48.tgz#021ffceb0a3f83078262870da88a912293c57475"
- integrity sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==
-
-esbuild-windows-64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.48.tgz#a4d3407b580f9faac51f61eec095fa985fb3fee4"
- integrity sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==
-
-esbuild-windows-arm64@0.14.48:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.48.tgz#762c0562127d8b09bfb70a3c816460742dd82880"
- integrity sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==
-
-esbuild@^0.14.27:
- version "0.14.48"
- resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.48.tgz#da5d8d25cd2d940c45ea0cfecdca727f7aee2b85"
- integrity sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==
- optionalDependencies:
- esbuild-android-64 "0.14.48"
- esbuild-android-arm64 "0.14.48"
- esbuild-darwin-64 "0.14.48"
- esbuild-darwin-arm64 "0.14.48"
- esbuild-freebsd-64 "0.14.48"
- esbuild-freebsd-arm64 "0.14.48"
- esbuild-linux-32 "0.14.48"
- esbuild-linux-64 "0.14.48"
- esbuild-linux-arm "0.14.48"
- esbuild-linux-arm64 "0.14.48"
- esbuild-linux-mips64le "0.14.48"
- esbuild-linux-ppc64le "0.14.48"
- esbuild-linux-riscv64 "0.14.48"
- esbuild-linux-s390x "0.14.48"
- esbuild-netbsd-64 "0.14.48"
- esbuild-openbsd-64 "0.14.48"
- esbuild-sunos-64 "0.14.48"
- esbuild-windows-32 "0.14.48"
- esbuild-windows-64 "0.14.48"
- esbuild-windows-arm64 "0.14.48"
-
-escalade@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
- integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
-escape-string-regexp@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
- integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
-
-escape-string-regexp@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
- integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
-estree-walker@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
- integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
-
-find-root@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
- integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
-
-follow-redirects@^1.15.0:
- version "1.15.6"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
- integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
-
-form-data@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
- integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.8"
- mime-types "^2.1.12"
-
-fsevents@~2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
- integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
-
-function-bind@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
- integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
-
-gensync@^1.0.0-beta.2:
- version "1.0.0-beta.2"
- resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
- integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-
-globals@^11.1.0:
- version "11.12.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
- integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-
-has-flag@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
- integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
-
-has@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
- integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
- dependencies:
- function-bind "^1.1.1"
-
-hoist-non-react-statics@^3.3.1:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
- integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
- dependencies:
- react-is "^16.7.0"
-
-import-fresh@^3.1.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
- integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
- dependencies:
- parent-module "^1.0.0"
- resolve-from "^4.0.0"
-
-is-arrayish@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
- integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
-
-is-core-module@^2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
- integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
- dependencies:
- has "^1.0.3"
-
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
- integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-jsesc@^2.5.1:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
- integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
-
-json-parse-even-better-errors@^2.3.0:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
- integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
-
-json5@^2.2.1:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
- integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
-
-lines-and-columns@^1.1.6:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
- integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
-
-lodash@^4.17.21:
- version "4.17.21"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
- integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-
-loose-envify@^1.1.0, loose-envify@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
- integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
- dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
-
-mime-db@1.52.0:
- version "1.52.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
- integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-
-mime-types@^2.1.12:
- version "2.1.35"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
- integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
- dependencies:
- mime-db "1.52.0"
-
-ms@2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
- integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-
-nanoid@^3.3.1:
- version "3.3.4"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
- integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
-
-nanoid@^3.3.6:
- version "3.3.6"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
- integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
-
-node-releases@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666"
- integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==
-
-object-assign@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
- integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
-
-parent-module@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
- integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
- dependencies:
- callsites "^3.0.0"
-
-parse-json@^5.0.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
- integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- error-ex "^1.3.1"
- json-parse-even-better-errors "^2.3.0"
- lines-and-columns "^1.1.6"
-
-path-parse@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
- integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
-
-path-type@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
- integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-
-picocolors@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
- integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
-
-picomatch@^2.2.2:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
- integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
-
-postcss@^8.4.13:
- version "8.4.31"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
- integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
- dependencies:
- nanoid "^3.3.6"
- picocolors "^1.0.0"
- source-map-js "^1.0.2"
-
-prettier@^2.5.1:
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
- integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
-
-prop-types@^15.6.2, prop-types@^15.8.1:
- version "15.8.1"
- resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
- integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
- dependencies:
- loose-envify "^1.4.0"
- object-assign "^4.1.1"
- react-is "^16.13.1"
-
-proxy-from-env@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
- integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
-
-react-dom@^17.0.2:
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
- integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
- scheduler "^0.20.2"
-
-react-is@^16.13.1, react-is@^16.7.0:
- version "16.13.1"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
- integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-
-react-is@^17.0.2:
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
- integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-
-react-refresh@^0.13.0:
- version "0.13.0"
- resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.13.0.tgz#cbd01a4482a177a5da8d44c9755ebb1f26d5a1c1"
- integrity sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==
-
-react-transition-group@^4.4.2:
- version "4.4.2"
- resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
- integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
- dependencies:
- "@babel/runtime" "^7.5.5"
- dom-helpers "^5.0.1"
- loose-envify "^1.4.0"
- prop-types "^15.6.2"
-
-react@^17.0.2:
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
- integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
-
-regenerator-runtime@^0.13.4:
- version "0.13.9"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
- integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
-
-resolve-from@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
- integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-
-resolve@^1.12.0, resolve@^1.22.0:
- version "1.22.1"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
- integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
- dependencies:
- is-core-module "^2.9.0"
- path-parse "^1.0.7"
- supports-preserve-symlinks-flag "^1.0.0"
-
-"rollup@>=2.59.0 <2.78.0":
- version "2.77.3"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
- integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
- optionalDependencies:
- fsevents "~2.3.2"
-
-safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-scheduler@^0.20.2:
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
- integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
- dependencies:
- loose-envify "^1.1.0"
- object-assign "^4.1.1"
-
-semver@^6.3.0:
- version "6.3.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
- integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-
-source-map-js@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
- integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
-
-source-map@^0.5.7:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
- integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
-
-stylis@4.0.13:
- version "4.0.13"
- resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
- integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
-
-supports-color@^5.3.0:
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
- integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
- dependencies:
- has-flag "^3.0.0"
-
-supports-preserve-symlinks-flag@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
- integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-
-to-fast-properties@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
- integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
-
-typescript@^4.5.4:
- version "4.7.4"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
- integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
-
-update-browserslist-db@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824"
- integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==
- dependencies:
- escalade "^3.1.1"
- picocolors "^1.0.0"
-
-vite@^2.9.17:
- version "2.9.17"
- resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.17.tgz#6b770525e12fa2a2e3a0fa0d028d304f4f7dc7d4"
- integrity sha512-XxcRzra6d7xrKXH66jZUgb+srThoPu+TLJc06GifUyKq9JmjHkc1Numc8ra0h56rju2jfVWw3B3fs5l3OFMvUw==
- dependencies:
- esbuild "^0.14.27"
- postcss "^8.4.13"
- resolve "^1.22.0"
- rollup ">=2.59.0 <2.78.0"
- optionalDependencies:
- fsevents "~2.3.2"
-
-yaml@^1.7.2:
- version "1.10.2"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
- integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
diff --git a/src/lightning/app/components/README.md b/src/lightning/app/components/README.md
deleted file mode 100644
index 5d3e9eeaf608e..0000000000000
--- a/src/lightning/app/components/README.md
+++ /dev/null
@@ -1 +0,0 @@
-TODO: add a guide how to add and use an external component
diff --git a/src/lightning/app/components/__init__.py b/src/lightning/app/components/__init__.py
deleted file mode 100644
index 8e44c0370d38b..0000000000000
--- a/src/lightning/app/components/__init__.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from lightning.app.components.database.client import DatabaseClient
-from lightning.app.components.database.server import Database
-from lightning.app.components.multi_node import (
- FabricMultiNode,
- LightningTrainerMultiNode,
- MultiNode,
- PyTorchSpawnMultiNode,
-)
-from lightning.app.components.python.popen import PopenPythonScript
-from lightning.app.components.python.tracer import Code, TracerPythonScript
-from lightning.app.components.serve.auto_scaler import AutoScaler
-from lightning.app.components.serve.cold_start_proxy import ColdStartProxy
-from lightning.app.components.serve.gradio_server import ServeGradio
-from lightning.app.components.serve.python_server import Category, Image, Number, PythonServer, Text
-from lightning.app.components.serve.serve import ModelInferenceAPI
-from lightning.app.components.serve.streamlit import ServeStreamlit
-from lightning.app.components.training import LightningTrainerScript, PyTorchLightningScriptRunner
-
-__all__ = [
- "AutoScaler",
- "ColdStartProxy",
- "DatabaseClient",
- "Database",
- "PopenPythonScript",
- "Code",
- "TracerPythonScript",
- "ServeGradio",
- "ServeStreamlit",
- "ModelInferenceAPI",
- "PythonServer",
- "Image",
- "Number",
- "Category",
- "Text",
- "MultiNode",
- "FabricMultiNode",
- "LightningTrainerScript",
- "PyTorchLightningScriptRunner",
- "PyTorchSpawnMultiNode",
- "LightningTrainerMultiNode",
-]
diff --git a/src/lightning/app/components/database/__init__.py b/src/lightning/app/components/database/__init__.py
deleted file mode 100644
index 50e7d095716b4..0000000000000
--- a/src/lightning/app/components/database/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from lightning.app.components.database.client import DatabaseClient
-from lightning.app.components.database.server import Database
-
-__all__ = ["Database", "DatabaseClient"]
diff --git a/src/lightning/app/components/database/client.py b/src/lightning/app/components/database/client.py
deleted file mode 100644
index 81f0862918934..0000000000000
--- a/src/lightning/app/components/database/client.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any, Dict, List, Optional, Type, TypeVar
-
-import requests
-from requests import Session
-from requests.adapters import HTTPAdapter
-from urllib3.util.retry import Retry
-
-from lightning.app.components.database.utilities import _GeneralModel
-
-_CONNECTION_RETRY_TOTAL = 5
-_CONNECTION_RETRY_BACKOFF_FACTOR = 1
-
-
-def _configure_session() -> Session:
- """Configures the session for GET and POST requests.
-
- It enables a generous retrial strategy that waits for the application server to connect.
-
- """
- retry_strategy = Retry(
- # wait time between retries increases exponentially according to: backoff_factor * (2 ** (retry - 1))
- total=_CONNECTION_RETRY_TOTAL,
- backoff_factor=_CONNECTION_RETRY_BACKOFF_FACTOR,
- status_forcelist=[429, 500, 502, 503, 504],
- )
- adapter = HTTPAdapter(max_retries=retry_strategy)
- http = requests.Session()
- http.mount("https://", adapter)
- http.mount("http://", adapter)
- return http
-
-
-T = TypeVar("T")
-
-
-class DatabaseClient:
- def __init__(self, db_url: str, token: Optional[str] = None, model: Optional[T] = None) -> None:
- self.db_url = db_url
- self.model = model
- self.token = token or ""
- self._session = None
-
- def select_all(self, model: Optional[Type[T]] = None) -> List[T]:
- cls = model if model else self.model
- resp = self.session.post(
- self.db_url + "/select_all/", data=_GeneralModel.from_cls(cls, token=self.token).json()
- )
- assert resp.status_code == 200
- return [cls(**data) for data in resp.json()]
-
- def insert(self, model: T) -> None:
- resp = self.session.post(
- self.db_url + "/insert/",
- data=_GeneralModel.from_obj(model, token=self.token).json(),
- )
- assert resp.status_code == 200
-
- def update(self, model: T) -> None:
- resp = self.session.post(
- self.db_url + "/update/",
- data=_GeneralModel.from_obj(model, token=self.token).json(),
- )
- assert resp.status_code == 200
-
- def delete(self, model: T) -> None:
- resp = self.session.post(
- self.db_url + "/delete/",
- data=_GeneralModel.from_obj(model, token=self.token).json(),
- )
- assert resp.status_code == 200
-
- @property
- def session(self):
- if self._session is None:
- self._session = _configure_session()
- return self._session
-
- def to_dict(self) -> Dict[str, Any]:
- return {"db_url": self.db_url, "model": self.model.__name__ if self.model else None}
diff --git a/src/lightning/app/components/database/server.py b/src/lightning/app/components/database/server.py
deleted file mode 100644
index 3fbf75f01ac85..0000000000000
--- a/src/lightning/app/components/database/server.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import os
-import sqlite3
-import sys
-import tempfile
-import threading
-import traceback
-from typing import List, Optional, Type, Union
-
-import uvicorn
-from fastapi import FastAPI
-from uvicorn import run
-
-from lightning.app.components.database.utilities import _create_database, _Delete, _Insert, _SelectAll, _Update
-from lightning.app.core.work import LightningWork
-from lightning.app.storage import Drive
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.imports import _is_sqlmodel_available
-from lightning.app.utilities.packaging.build_config import BuildConfig
-
-if _is_sqlmodel_available():
- from sqlmodel import SQLModel
-else:
- SQLModel = object
-
-
-logger = Logger(__name__)
-
-
-# Required to avoid Uvicorn Server overriding Lightning App signal handlers.
-# Discussions: https://github.com/encode/uvicorn/discussions/1708
-class _DatabaseUvicornServer(uvicorn.Server):
- has_started_queue = None
-
- def run(self, sockets=None):
- self.config.setup_event_loop()
- loop = asyncio.get_event_loop()
- asyncio.ensure_future(self.serve(sockets=sockets))
- loop.run_forever()
-
- def install_signal_handlers(self):
- """Ignore Uvicorn Signal Handlers."""
-
-
-_lock = threading.Lock()
-
-
-class Database(LightningWork):
- def __init__(
- self,
- models: Union[Type["SQLModel"], List[Type["SQLModel"]]],
- db_filename: str = "database.db",
- store_interval: int = 10,
- debug: bool = False,
- ) -> None:
- """The Database Component enables to interact with an SQLite database to store some structured information
- about your application.
-
- The provided models are SQLModel tables
-
- Arguments:
- models: A SQLModel or a list of SQLModels table to be added to the database.
- db_filename: The name of the SQLite database.
- store_interval: Time interval (in seconds) at which the database is periodically synchronized to the Drive.
- Note that the database is also always synchronized on exit.
- debug: Whether to run the database in debug mode.
-
- Example::
-
- from typing import List
- from sqlmodel import SQLModel, Field
- from uuid import uuid4
-
- from lightning.app import LightningFlow, LightningApp
- from lightning.app.components.database import Database, DatabaseClient
-
- class CounterModel(SQLModel, table=True):
- __table_args__ = {"extend_existing": True}
-
- id: int = Field(default=None, primary_key=True)
- count: int
-
-
- class Flow(LightningFlow):
-
- def __init__(self):
- super().__init__()
- self._private_token = uuid4().hex
- self.db = Database(models=[CounterModel])
- self._client = None
- self.counter = 0
-
- def run(self):
- self.db.run(token=self._private_token)
-
- if not self.db.alive():
- return
-
- if self.counter == 0:
- self._client = DatabaseClient(
- model=CounterModel,
- db_url=self.db.url,
- token=self._private_token,
- )
-
- rows = self._client.select_all()
-
- print(f"{self.counter}: {rows}")
-
- if not rows:
- self._client.insert(CounterModel(count=0))
- else:
- row: CounterModel = rows[0]
- row.count += 1
- self._client.update(row)
-
- if self.counter >= 100:
- row: CounterModel = rows[0]
- self._client.delete(row)
- self.stop()
-
- self.counter += 1
-
- app = LightningApp(Flow())
-
- If you want to use nested SQLModels, we provide a utility to do so as follows:
-
- Example::
-
- from typing import List
- from sqlmodel import SQLModel, Field
- from sqlalchemy import Column
-
- from lightning.app.components.database.utilities import pydantic_column_type
-
- class KeyValuePair(SQLModel):
- name: str
- value: str
-
- class CounterModel(SQLModel, table=True):
- __table_args__ = {"extend_existing": True}
-
- name: int = Field(default=None, primary_key=True)
-
- # RIGHT THERE ! You need to use Field and Column with the `pydantic_column_type` utility.
- kv: List[KeyValuePair] = Field(..., sa_column=Column(pydantic_column_type(List[KeyValuePair])))
-
- """
- super().__init__(parallel=True, cloud_build_config=BuildConfig(["sqlmodel"]))
- self.db_filename = db_filename
- self._root_folder = os.path.dirname(db_filename)
- self.debug = debug
- self.store_interval = store_interval
- self._models = models if isinstance(models, list) else [models]
- self._store_thread = None
- self._exit_event = None
-
- def store_database(self):
- try:
- with tempfile.TemporaryDirectory() as tmpdir:
- tmp_db_filename = os.path.join(tmpdir, os.path.basename(self.db_filename))
-
- source = sqlite3.connect(self.db_filename)
- dest = sqlite3.connect(tmp_db_filename)
-
- source.backup(dest)
-
- source.close()
- dest.close()
-
- drive = Drive("lit://database", component_name=self.name, root_folder=tmpdir)
- drive.put(os.path.basename(tmp_db_filename))
-
- logger.debug("Stored the database to the Drive.")
- except Exception:
- print(traceback.print_exc())
-
- def periodic_store_database(self, store_interval):
- while not self._exit_event.is_set():
- with _lock:
- self.store_database()
- self._exit_event.wait(store_interval)
-
- def run(self, token: Optional[str] = None) -> None:
- """
- Arguments:
- token: Token used to protect the database access. Ensure you don't expose it through the App State.
- """
- drive = Drive("lit://database", component_name=self.name, root_folder=self._root_folder)
- filenames = drive.list(component_name=self.name)
- if self.db_filename in filenames:
- drive.get(self.db_filename)
- print("Retrieved the database from Drive.")
-
- app = FastAPI()
-
- _create_database(self.db_filename, self._models, self.debug)
- models = {m.__name__: m for m in self._models}
- app.post("/select_all/")(_SelectAll(models, token))
- app.post("/insert/")(_Insert(models, token))
- app.post("/update/")(_Update(models, token))
- app.post("/delete/")(_Delete(models, token))
-
- sys.modules["uvicorn.main"].Server = _DatabaseUvicornServer
-
- self._exit_event = threading.Event()
- self._store_thread = threading.Thread(target=self.periodic_store_database, args=(self.store_interval,))
- self._store_thread.start()
-
- run(app, host=self.host, port=self.port, log_level="error")
-
- def alive(self) -> bool:
- """Hack: Returns whether the server is alive."""
- return self.db_url != ""
-
- @property
- def db_url(self) -> Optional[str]:
- use_localhost = "LIGHTNING_APP_STATE_URL" not in os.environ
- if use_localhost:
- return self.url
- ip_addr = self.public_ip or self.internal_ip
- if ip_addr != "":
- return f"http://{ip_addr}:{self.port}"
- return ip_addr
-
- def on_exit(self):
- self._exit_event.set()
- with _lock:
- self.store_database()
diff --git a/src/lightning/app/components/database/utilities.py b/src/lightning/app/components/database/utilities.py
deleted file mode 100644
index 129f8a210a758..0000000000000
--- a/src/lightning/app/components/database/utilities.py
+++ /dev/null
@@ -1,263 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import functools
-import json
-import pathlib
-from typing import Any, Dict, Generic, List, Type, TypeVar
-
-from fastapi import Response, status
-from fastapi.encoders import jsonable_encoder
-from lightning_utilities.core.imports import RequirementCache
-from pydantic import BaseModel, parse_obj_as
-
-if RequirementCache("pydantic>=2.0.0"):
- from pydantic.v1.main import ModelMetaclass
-else:
- from pydantic.main import ModelMetaclass
-
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.imports import _is_sqlmodel_available
-
-if _is_sqlmodel_available():
- from sqlalchemy.inspection import inspect as sqlalchemy_inspect
- from sqlmodel import JSON, Session, SQLModel, TypeDecorator, select
-
-logger = Logger(__name__)
-engine = None
-
-T = TypeVar("T")
-
-
-# Taken from https://github.com/tiangolo/sqlmodel/issues/63#issuecomment-1081555082
-def _pydantic_column_type(pydantic_type: Any) -> Any:
- """This function enables to support JSON types with SQLModel.
-
- Example::
-
- from sqlmodel import SQLModel
- from sqlalchemy import Column
-
- class TrialConfig(SQLModel, table=False):
- ...
- params: Dict[str, Union[Dict[str, float]] = Field(sa_column=Column(pydantic_column_type[Dict[str, float]))
-
- """
-
- class PydanticJSONType(TypeDecorator, Generic[T]):
- impl = JSON()
-
- def __init__(
- self,
- json_encoder=json,
- ):
- self.json_encoder = json_encoder
- super().__init__()
-
- def bind_processor(self, dialect):
- impl_processor = self.impl.bind_processor(dialect)
- dumps = self.json_encoder.dumps
- if impl_processor:
-
- def process(value: T):
- if value is not None:
- if isinstance(pydantic_type, ModelMetaclass):
- # This allows to assign non-InDB models and if they're
- # compatible, they're directly parsed into the InDB
- # representation, thus hiding the implementation in the
- # background. However, the InDB model will still be returned
- value_to_dump = pydantic_type.from_orm(value)
- else:
- value_to_dump = value
- value = jsonable_encoder(value_to_dump)
- return impl_processor(value)
-
- else:
-
- def process(value):
- if isinstance(pydantic_type, ModelMetaclass):
- # This allows to assign non-InDB models and if they're
- # compatible, they're directly parsed into the InDB
- # representation, thus hiding the implementation in the
- # background. However, the InDB model will still be returned
- value_to_dump = pydantic_type.from_orm(value)
- else:
- value_to_dump = value
- return dumps(jsonable_encoder(value_to_dump))
-
- return process
-
- def result_processor(self, dialect, coltype) -> T:
- impl_processor = self.impl.result_processor(dialect, coltype)
- if impl_processor:
-
- def process(value):
- value = impl_processor(value)
- if value is None:
- return None
-
- data = value
- # Explicitly use the generic directly, not type(T)
- return parse_obj_as(pydantic_type, data)
-
- else:
-
- def process(value):
- if value is None:
- return None
-
- # Explicitly use the generic directly, not type(T)
- return parse_obj_as(pydantic_type, value)
-
- return process
-
- def compare_values(self, x, y):
- return x == y
-
- return PydanticJSONType
-
-
-@functools.lru_cache(maxsize=128)
-def _get_primary_key(model_type: Type["SQLModel"]) -> str:
- primary_keys = sqlalchemy_inspect(model_type).primary_key
-
- if len(primary_keys) != 1:
- raise ValueError(f"The model {model_type.__name__} should have a single primary key field.")
-
- return primary_keys[0].name
-
-
-class _GeneralModel(BaseModel):
- cls_name: str
- data: str
- token: str
-
- def convert_to_model(self, models: Dict[str, BaseModel]):
- return models[self.cls_name].parse_raw(self.data)
-
- @classmethod
- def from_obj(cls, obj, token):
- return cls(**{
- "cls_name": obj.__class__.__name__,
- "data": obj.json(),
- "token": token,
- })
-
- @classmethod
- def from_cls(cls, obj_cls, token):
- return cls(**{
- "cls_name": obj_cls.__name__,
- "data": "",
- "token": token,
- })
-
-
-class _SelectAll:
- def __init__(self, models, token):
- print(models, token)
- self.models = models
- self.token = token
-
- def __call__(self, data: Dict, response: Response):
- if self.token and data["token"] != self.token:
- response.status_code = status.HTTP_401_UNAUTHORIZED
- return {"status": "failure", "reason": "Unauthorized request to the database."}
-
- with Session(engine) as session:
- cls: Type["SQLModel"] = self.models[data["cls_name"]]
- statement = select(cls)
- results = session.exec(statement)
- return results.all()
-
-
-class _Insert:
- def __init__(self, models, token):
- self.models = models
- self.token = token
-
- def __call__(self, data: Dict, response: Response):
- if self.token and data["token"] != self.token:
- response.status_code = status.HTTP_401_UNAUTHORIZED
- return {"status": "failure", "reason": "Unauthorized request to the database."}
-
- with Session(engine) as session:
- ele = self.models[data["cls_name"]].parse_raw(data["data"])
- session.add(ele)
- session.commit()
- session.refresh(ele)
- return ele
-
-
-class _Update:
- def __init__(self, models, token):
- self.models = models
- self.token = token
-
- def __call__(self, data: Dict, response: Response):
- if self.token and data["token"] != self.token:
- response.status_code = status.HTTP_401_UNAUTHORIZED
- return {"status": "failure", "reason": "Unauthorized request to the database."}
-
- with Session(engine) as session:
- update_data = self.models[data["cls_name"]].parse_raw(data["data"])
- primary_key = _get_primary_key(update_data.__class__)
- identifier = getattr(update_data.__class__, primary_key, None)
- statement = select(update_data.__class__).where(identifier == getattr(update_data, primary_key))
- results = session.exec(statement)
- result = results.one()
- for k, v in vars(update_data).items():
- if k in ("id", "_sa_instance_state"):
- continue
- if getattr(result, k) != v:
- setattr(result, k, v)
- session.add(result)
- session.commit()
- session.refresh(result)
- return None
-
-
-class _Delete:
- def __init__(self, models, token):
- self.models = models
- self.token = token
-
- def __call__(self, data: Dict, response: Response):
- if self.token and data["token"] != self.token:
- response.status_code = status.HTTP_401_UNAUTHORIZED
- return {"status": "failure", "reason": "Unauthorized request to the database."}
-
- with Session(engine) as session:
- update_data = self.models[data["cls_name"]].parse_raw(data["data"])
- primary_key = _get_primary_key(update_data.__class__)
- identifier = getattr(update_data.__class__, primary_key, None)
- statement = select(update_data.__class__).where(identifier == getattr(update_data, primary_key))
- results = session.exec(statement)
- result = results.one()
- session.delete(result)
- session.commit()
- return None
-
-
-def _create_database(db_filename: str, models: List[Type["SQLModel"]], echo: bool = False):
- global engine
-
- from sqlmodel import create_engine
-
- engine = create_engine(f"sqlite:///{pathlib.Path(db_filename).resolve()}", echo=echo)
-
- logger.debug(f"Creating the following tables {models}")
- try:
- SQLModel.metadata.create_all(engine)
- except Exception as ex:
- logger.debug(ex)
diff --git a/src/lightning/app/components/multi_node/__init__.py b/src/lightning/app/components/multi_node/__init__.py
deleted file mode 100644
index e672a0d422ed9..0000000000000
--- a/src/lightning/app/components/multi_node/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from lightning.app.components.multi_node.base import MultiNode
-from lightning.app.components.multi_node.fabric import FabricMultiNode
-from lightning.app.components.multi_node.pytorch_spawn import PyTorchSpawnMultiNode
-from lightning.app.components.multi_node.trainer import LightningTrainerMultiNode
-
-__all__ = ["FabricMultiNode", "MultiNode", "PyTorchSpawnMultiNode", "LightningTrainerMultiNode"]
diff --git a/src/lightning/app/components/multi_node/base.py b/src/lightning/app/components/multi_node/base.py
deleted file mode 100644
index e5a651466c0a4..0000000000000
--- a/src/lightning/app/components/multi_node/base.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import warnings
-from typing import Any, Type
-
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.work import LightningWork
-from lightning.app.structures import List as _List
-from lightning.app.utilities.cloud import is_running_in_cloud
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-
-class MultiNode(LightningFlow):
- def __init__(
- self,
- work_cls: Type["LightningWork"],
- num_nodes: int,
- cloud_compute: "CloudCompute",
- *work_args: Any,
- **work_kwargs: Any,
- ) -> None:
- """This component enables performing distributed multi-node multi-device training.
-
- Example::
-
- import torch
-
- from lightning.app import LightningWork, CloudCompute
- from lightning.components import MultiNode
-
- class AnyDistributedComponent(LightningWork):
- def run(
- self,
- main_address: str,
- main_port: int,
- node_rank: int,
- ):
- print(f"ADD YOUR DISTRIBUTED CODE: {main_address} {main_port} {node_rank}")
-
-
- compute = CloudCompute("gpu")
- app = LightningApp(
- MultiNode(
- AnyDistributedComponent,
- num_nodes=8,
- cloud_compute=compute,
- )
- )
-
- Arguments:
- work_cls: The work to be executed
- num_nodes: Number of nodes. Gets ignored when running locally. Launch the app with --cloud to run on
- multiple cloud machines.
- cloud_compute: The cloud compute object used in the cloud. The value provided here gets ignored when
- running locally.
- work_args: Arguments to be provided to the work on instantiation.
- work_kwargs: Keywords arguments to be provided to the work on instantiation.
-
- """
- super().__init__()
- if num_nodes > 1 and not is_running_in_cloud():
- warnings.warn(
- f"You set {type(self).__name__}(num_nodes={num_nodes}, ...)` but this app is running locally."
- " We assume you are debugging and will ignore the `num_nodes` argument."
- " To run on multiple nodes in the cloud, launch your app with `--cloud`."
- )
- num_nodes = 1
- self.ws = _List(*[
- work_cls(
- *work_args,
- cloud_compute=cloud_compute.clone(),
- **work_kwargs,
- parallel=True,
- )
- for _ in range(num_nodes)
- ])
-
- def run(self) -> None:
- # 1. Wait for all works to be started !
- if not all(w.internal_ip for w in self.ws):
- return
-
- # 2. Loop over all node machines
- for node_rank in range(len(self.ws)):
- # 3. Run the user code in a distributed way !
- self.ws[node_rank].run(
- main_address=self.ws[0].internal_ip,
- main_port=self.ws[0].port,
- num_nodes=len(self.ws),
- node_rank=node_rank,
- )
-
- # 4. Stop the machine when finished.
- if self.ws[node_rank].has_succeeded:
- self.ws[node_rank].stop()
diff --git a/src/lightning/app/components/multi_node/fabric.py b/src/lightning/app/components/multi_node/fabric.py
deleted file mode 100644
index 542e0fe3afc43..0000000000000
--- a/src/lightning/app/components/multi_node/fabric.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import importlib
-import os
-import warnings
-from dataclasses import dataclass
-from typing import Any, Callable, Protocol, Type, runtime_checkable
-
-from lightning.app.components.multi_node.base import MultiNode
-from lightning.app.components.multi_node.pytorch_spawn import _PyTorchSpawnRunExecutor
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-from lightning.app.utilities.tracer import Tracer
-
-
-@runtime_checkable
-class _FabricWorkProtocol(Protocol):
- @staticmethod
- def run() -> None:
- """Run."""
-
-
-@dataclass
-class _FabricRunExecutor(_PyTorchSpawnRunExecutor):
- @staticmethod
- def run(
- local_rank: int,
- work_run: Callable,
- main_address: str,
- main_port: int,
- num_nodes: int,
- node_rank: int,
- nprocs: int,
- ):
- fabrics = []
- strategies = []
- mps_accelerators = []
-
- for pkg_name in ("lightning.fabric", "lightning_" + "fabric"):
- try:
- pkg = importlib.import_module(pkg_name)
- fabrics.append(pkg.Fabric)
- strategies.append(pkg.strategies.DDPStrategy)
- mps_accelerators.append(pkg.accelerators.MPSAccelerator)
- except (ImportError, ModuleNotFoundError):
- continue
-
- # Used to configure PyTorch progress group
- os.environ["MASTER_ADDR"] = main_address
- os.environ["MASTER_PORT"] = str(main_port)
-
- # Used to hijack TorchElastic Cluster Environnement.
- os.environ["GROUP_RANK"] = str(node_rank)
- os.environ["RANK"] = str(local_rank + node_rank * nprocs)
- os.environ["LOCAL_RANK"] = str(local_rank)
- os.environ["WORLD_SIZE"] = str(num_nodes * nprocs)
- os.environ["LOCAL_WORLD_SIZE"] = str(nprocs)
- os.environ["TORCHELASTIC_RUN_ID"] = "1"
-
- # Used to force Fabric to setup the distributed environnement.
- os.environ["LT_CLI_USED"] = "1"
-
- # Used to pass information to Fabric directly.
- def pre_fn(fabric, *args: Any, **kwargs: Any):
- kwargs["devices"] = nprocs
- kwargs["num_nodes"] = num_nodes
-
- if any(acc.is_available() for acc in mps_accelerators):
- old_acc_value = kwargs.get("accelerator", "auto")
- kwargs["accelerator"] = "cpu"
-
- if old_acc_value != kwargs["accelerator"]:
- warnings.warn("Forcing `accelerator=cpu` as MPS does not support distributed training.")
- else:
- kwargs["accelerator"] = "auto"
- strategy = kwargs.get("strategy", None)
- if strategy:
- if isinstance(strategy, str):
- if strategy == "ddp_spawn":
- strategy = "ddp"
- elif strategy == "ddp_sharded_spawn":
- strategy = "ddp_sharded"
- elif isinstance(strategy, tuple(strategies)) and strategy._start_method in ("spawn", "fork"):
- raise ValueError("DDP Spawned strategies aren't supported yet.")
-
- kwargs["strategy"] = strategy
-
- return {}, args, kwargs
-
- tracer = Tracer()
- for lf in fabrics:
- tracer.add_traced(lf, "__init__", pre_fn=pre_fn)
- tracer._instrument()
- ret_val = work_run()
- tracer._restore()
- return ret_val
-
-
-class FabricMultiNode(MultiNode):
- def __init__(
- self,
- work_cls: Type["LightningWork"],
- cloud_compute: "CloudCompute",
- num_nodes: int,
- *work_args: Any,
- **work_kwargs: Any,
- ) -> None:
- assert issubclass(work_cls, _FabricWorkProtocol)
-
- # Note: Private way to modify the work run executor
- # Probably exposed to the users in the future if needed.
- work_cls._run_executor_cls = _FabricRunExecutor
-
- super().__init__(
- work_cls,
- *work_args,
- num_nodes=num_nodes,
- cloud_compute=cloud_compute,
- **work_kwargs,
- )
diff --git a/src/lightning/app/components/multi_node/pytorch_spawn.py b/src/lightning/app/components/multi_node/pytorch_spawn.py
deleted file mode 100644
index 9bccbad5cfc11..0000000000000
--- a/src/lightning/app/components/multi_node/pytorch_spawn.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any, Callable, Protocol, Type, runtime_checkable
-
-from lightning.app.components.multi_node.base import MultiNode
-from lightning.app.core.queues import MultiProcessQueue
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-from lightning.app.utilities.proxies import WorkRunExecutor, WorkStateObserver, _proxy_setattr, unwrap
-
-
-@runtime_checkable
-class _PyTorchSpawnWorkProtocol(Protocol):
- def run(
- self,
- world_size: int,
- node_rank: int,
- global_rank: int,
- local_rank: int,
- ) -> None:
- pass
-
-
-class _PyTorchSpawnRunExecutor(WorkRunExecutor):
- enable_start_observer: bool = False
-
- def __call__(
- self,
- main_address: str,
- main_port: int,
- num_nodes: int,
- node_rank: int,
- ):
- import torch
-
- with self.enable_spawn():
- nprocs = torch.cuda.device_count() if torch.cuda.is_available() else 1
- queue = self.delta_queue if isinstance(self.delta_queue, MultiProcessQueue) else self.delta_queue.to_dict()
- torch.multiprocessing.spawn(
- self.dispatch_run,
- args=(self.__class__, self.work, queue, main_address, main_port, num_nodes, node_rank, nprocs),
- nprocs=nprocs,
- )
-
- @staticmethod
- def dispatch_run(local_rank, cls, work, delta_queue, *args: Any, **kwargs: Any):
- if local_rank == 0:
- if isinstance(delta_queue, dict):
- delta_queue = cls.process_queue(delta_queue)
- work._request_queue = cls.process_queue(work._request_queue)
- work._response_queue = cls.process_queue(work._response_queue)
-
- state_observer = WorkStateObserver(work, delta_queue=delta_queue)
- state_observer.start()
- _proxy_setattr(work, delta_queue, state_observer)
-
- cls.run(local_rank, unwrap(work.run), *args, **kwargs)
-
- if local_rank == 0:
- state_observer.join(0)
-
- @staticmethod
- def run(
- local_rank: int,
- work_run: Callable,
- main_address: str,
- main_port: int,
- num_nodes: int,
- node_rank: int,
- nprocs: int,
- ):
- import torch
-
- # 1. Setting distributed environment
- global_rank = local_rank + node_rank * nprocs
- world_size = num_nodes * nprocs
-
- if torch.distributed.is_available():
- if not torch.distributed.is_initialized():
- torch.distributed.init_process_group(
- "nccl" if torch.cuda.is_available() else "gloo",
- rank=global_rank,
- world_size=world_size,
- init_method=f"tcp://{main_address}:{main_port}",
- )
- elif world_size > 1:
- raise Exception("Torch distributed should be available.")
-
- return work_run(world_size, node_rank, global_rank, local_rank)
-
-
-class PyTorchSpawnMultiNode(MultiNode):
- def __init__(
- self,
- work_cls: Type["LightningWork"],
- cloud_compute: "CloudCompute",
- num_nodes: int,
- *work_args: Any,
- **work_kwargs: Any,
- ) -> None:
- assert issubclass(work_cls, _PyTorchSpawnWorkProtocol)
-
- # Note: Private way to modify the work run executor
- # Probably exposed to the users in the future if needed.
- work_cls._run_executor_cls = _PyTorchSpawnRunExecutor
-
- super().__init__(work_cls, num_nodes, cloud_compute, *work_args, **work_kwargs)
diff --git a/src/lightning/app/components/multi_node/trainer.py b/src/lightning/app/components/multi_node/trainer.py
deleted file mode 100644
index ee7aae36f981b..0000000000000
--- a/src/lightning/app/components/multi_node/trainer.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import importlib
-import os
-import warnings
-from dataclasses import dataclass
-from typing import Any, Callable, Protocol, Type, runtime_checkable
-
-from lightning.app.components.multi_node.base import MultiNode
-from lightning.app.components.multi_node.pytorch_spawn import _PyTorchSpawnRunExecutor
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-from lightning.app.utilities.tracer import Tracer
-
-
-@runtime_checkable
-class _LightningTrainerWorkProtocol(Protocol):
- @staticmethod
- def run() -> None:
- """Run."""
-
-
-@dataclass
-class _LightningTrainerRunExecutor(_PyTorchSpawnRunExecutor):
- @staticmethod
- def run(
- local_rank: int,
- work_run: Callable,
- main_address: str,
- main_port: int,
- num_nodes: int,
- node_rank: int,
- nprocs: int,
- ):
- trainers = []
- strategies = []
- mps_accelerators = []
-
- for pkg_name in ("lightning.pytorch", "pytorch_" + "lightning"):
- try:
- pkg = importlib.import_module(pkg_name)
- trainers.append(pkg.Trainer)
- strategies.append(pkg.strategies.DDPStrategy)
- mps_accelerators.append(pkg.accelerators.MPSAccelerator)
- except (ImportError, ModuleNotFoundError):
- continue
-
- # Used to configure PyTorch progress group
- os.environ["MASTER_ADDR"] = main_address
- os.environ["MASTER_PORT"] = str(main_port)
-
- # Used to hijack TorchElastic Cluster Environnement.
- os.environ["GROUP_RANK"] = str(node_rank)
- os.environ["RANK"] = str(local_rank + node_rank * nprocs)
- os.environ["LOCAL_RANK"] = str(local_rank)
- os.environ["WORLD_SIZE"] = str(num_nodes * nprocs)
- os.environ["LOCAL_WORLD_SIZE"] = str(nprocs)
- os.environ["TORCHELASTIC_RUN_ID"] = "1"
-
- # Used to pass information to the Trainer directly.
- def pre_fn(trainer, *args: Any, **kwargs: Any):
- kwargs["devices"] = nprocs
- kwargs["num_nodes"] = num_nodes
- if any(acc.is_available() for acc in mps_accelerators):
- old_acc_value = kwargs.get("accelerator", "auto")
- kwargs["accelerator"] = "cpu"
-
- if old_acc_value != kwargs["accelerator"]:
- warnings.warn("Forcing `accelerator=cpu` as MPS does not support distributed training.")
- else:
- kwargs["accelerator"] = "auto"
-
- strategy = kwargs.get("strategy", None)
- if strategy:
- if isinstance(strategy, str):
- if strategy == "ddp_spawn":
- strategy = "ddp"
- elif strategy == "ddp_sharded_spawn":
- strategy = "ddp_sharded"
- elif isinstance(strategy, tuple(strategies)):
- raise ValueError("DDP Spawned strategies aren't supported yet.")
- kwargs["strategy"] = strategy
- return {}, args, kwargs
-
- tracer = Tracer()
- for trainer in trainers:
- tracer.add_traced(trainer, "__init__", pre_fn=pre_fn)
- tracer._instrument()
- ret_val = work_run()
- tracer._restore()
- return ret_val
-
-
-class LightningTrainerMultiNode(MultiNode):
- def __init__(
- self,
- work_cls: Type["LightningWork"],
- cloud_compute: "CloudCompute",
- num_nodes: int,
- *work_args: Any,
- **work_kwargs: Any,
- ) -> None:
- assert issubclass(work_cls, _LightningTrainerWorkProtocol)
-
- # Note: Private way to modify the work run executor
- # Probably exposed to the users in the future if needed.
- work_cls._run_executor_cls = _LightningTrainerRunExecutor
-
- super().__init__(
- work_cls,
- *work_args,
- num_nodes=num_nodes,
- cloud_compute=cloud_compute,
- **work_kwargs,
- )
-
- # the Trainer enables TensorBoard by default, so this is often an undesired directory to upload to the cloud
- self.lightningignore += ("lightning_logs",)
diff --git a/src/lightning/app/components/python/__init__.py b/src/lightning/app/components/python/__init__.py
deleted file mode 100644
index 86de268e86fcf..0000000000000
--- a/src/lightning/app/components/python/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from lightning.app.components.python.popen import PopenPythonScript
-from lightning.app.components.python.tracer import TracerPythonScript
-
-__all__ = ["PopenPythonScript", "TracerPythonScript"]
diff --git a/src/lightning/app/components/python/popen.py b/src/lightning/app/components/python/popen.py
deleted file mode 100644
index cb9d1ea14562d..0000000000000
--- a/src/lightning/app/components/python/popen.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import signal
-import subprocess
-import sys
-from pathlib import Path
-from typing import Any, Dict, List, Optional, Union
-
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.app_helpers import Logger, _collect_child_process_pids
-from lightning.app.utilities.tracer import Tracer
-
-logger = Logger(__name__)
-
-
-class PopenPythonScript(LightningWork):
- def on_before_run(self):
- """Called before the python script is executed."""
-
- def on_after_run(self):
- """Called after the python script is executed."""
-
- def configure_tracer(self) -> Tracer:
- """Override this hook to customize your tracer when running PythonScript with ``mode=tracer``."""
- return Tracer()
-
- def __init__(
- self,
- script_path: Union[str, Path],
- script_args: Optional[Union[str, List[str]]] = None,
- env: Optional[Dict] = None,
- **kwargs: Any,
- ):
- """The PopenPythonScript component enables to easily run a python script within a subprocess.
-
- Arguments:
- script_path: Path of the python script to run.
- script_path: The arguments to be passed to the script.
- env: Environment variables to be passed to the script.
- kwargs: LightningWork keyword arguments.
-
- Raises:
- FileNotFoundError: If the provided `script_path` doesn't exists.
-
- Example:
-
- >>> from lightning.app.components.python import PopenPythonScript
- >>> f = open("a.py", "w")
- >>> f.write("print('Hello World !')")
- 22
- >>> f.close()
- >>> python_script = PopenPythonScript("a.py")
- >>> python_script.run()
- >>> os.remove("a.py")
-
- In this example, the script will be launch with the :class:`~subprocess.Popen`.
-
- .. literalinclude:: ../../../../examples/app/components/python/component_popen.py
- :language: python
-
- """
- super().__init__(**kwargs)
- if not os.path.exists(script_path):
- raise FileNotFoundError(f"The provided `script_path` {script_path}` wasn't found.")
- self.script_path = str(script_path)
- if isinstance(script_args, str):
- script_args = script_args.split(" ")
- self.script_args = script_args if script_args else []
- self.env = env
- self.pid = None
- self.exit_code = None
-
- def run(self) -> None:
- self.on_before_run()
- self._run_with_subprocess_popen()
- self.on_after_run()
- return
-
- def _run_with_subprocess_popen(self) -> None:
- cmd = [sys.executable] + [self.script_path] + self.script_args
-
- with subprocess.Popen(
- cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0, close_fds=True, env=self.env
- ) as proc:
- self.pid = proc.pid
- if proc.stdout:
- with proc.stdout:
- for line in iter(proc.stdout.readline, b""):
- logger.info("%s", line.decode().rstrip())
-
- self.exit_code = proc.wait()
- if self.exit_code != 0:
- raise Exception(self.exit_code)
-
- def on_exit(self):
- for child_pid in _collect_child_process_pids(os.getpid()):
- os.kill(child_pid, signal.SIGTERM)
-
-
-__all__ = ["PopenPythonScript"]
diff --git a/src/lightning/app/components/python/tracer.py b/src/lightning/app/components/python/tracer.py
deleted file mode 100644
index a33b8f97c11d0..0000000000000
--- a/src/lightning/app/components/python/tracer.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import signal
-import sys
-from copy import deepcopy
-from typing import Any, Dict, List, Optional, Union
-
-from typing_extensions import TypedDict
-
-from lightning.app.core.work import LightningWork
-from lightning.app.storage.drive import Drive
-from lightning.app.storage.payload import Payload
-from lightning.app.utilities.app_helpers import Logger, _collect_child_process_pids
-from lightning.app.utilities.packaging.tarfile import clean_tarfile, extract_tarfile
-from lightning.app.utilities.tracer import Tracer
-
-logger = Logger(__name__)
-
-
-class Code(TypedDict):
- drive: Drive
- name: str
-
-
-class TracerPythonScript(LightningWork):
- _start_method = "spawn"
-
- def on_before_run(self):
- """Called before the python script is executed."""
-
- def on_after_run(self, res: Any):
- """Called after the python script is executed."""
- for name in self.outputs:
- setattr(self, name, Payload(res[name]))
-
- def configure_tracer(self) -> Tracer:
- """Override this hook to customize your tracer when running PythonScript."""
- return Tracer()
-
- def __init__(
- self,
- script_path: str,
- script_args: Optional[Union[list, str]] = None,
- outputs: Optional[List[str]] = None,
- env: Optional[Dict] = None,
- code: Optional[Code] = None,
- **kwargs: Any,
- ):
- """The TracerPythonScript class enables to easily run a python script.
-
- When subclassing this class, you can configure your own :class:`~lightning.app.utilities.tracer.Tracer`
- by :meth:`~lightning.app.components.python.tracer.TracerPythonScript.configure_tracer` method.
-
- The tracer is quite a magical class. It enables you to inject code into a script execution without changing it.
-
- Arguments:
- script_path: Path of the python script to run.
- script_path: The arguments to be passed to the script.
- outputs: Collection of object names to collect after the script execution.
- env: Environment variables to be passed to the script.
- kwargs: LightningWork Keyword arguments.
-
- Raises:
- FileNotFoundError: If the provided `script_path` doesn't exists.
-
- **How does it work?**
-
- It works by executing the python script with python built-in `runpy
- `_ run_path method.
- This method takes any python globals before executing the script,
- e.g., you can modify classes or function from the script.
-
- Example:
-
- >>> from lightning.app.components.python import TracerPythonScript
- >>> f = open("a.py", "w")
- >>> f.write("print('Hello World !')")
- 22
- >>> f.close()
- >>> python_script = TracerPythonScript("a.py")
- >>> python_script.run()
- Hello World !
- >>> os.remove("a.py")
-
- In the example below, we subclass the :class:`~lightning.app.components.python.TracerPythonScript`
- component and override its configure_tracer method.
-
- Using the Tracer, we are patching the ``__init__`` method of the PyTorch Lightning Trainer.
- Once the script starts running and if a Trainer is instantiated, the provided ``pre_fn`` is
- called and we inject a Lightning callback.
-
- This callback has a reference to the work and on every batch end, we are capturing the
- trainer ``global_step`` and ``best_model_path``.
-
- Even more interesting, this component works for ANY PyTorch Lightning script and
- its state can be used in real time in a UI.
-
- .. literalinclude:: ../../../../examples/app/components/python/component_tracer.py
- :language: python
-
-
- Once implemented, this component can easily be integrated within a larger app
- to execute a specific python script.
-
- .. literalinclude:: ../../../../examples/app/components/python/app.py
- :language: python
-
- """
- super().__init__(**kwargs)
- self.script_path = str(script_path)
- if isinstance(script_args, str):
- script_args = script_args.split(" ")
- self.script_args = script_args if script_args else []
- self.original_args = deepcopy(self.script_args)
- self.env = env
- self.outputs = outputs or []
- for name in self.outputs:
- setattr(self, name, None)
- self.params = None
- self.drive = code.get("drive") if code else None
- self.code_name = code.get("name") if code else None
- self.restart_count = 0
-
- def run(
- self,
- params: Optional[Dict[str, Any]] = None,
- restart_count: Optional[int] = None,
- code_dir: Optional[str] = ".",
- **kwargs: Any,
- ):
- """
- Arguments:
- params: A dictionary of arguments to be be added to script_args.
- restart_count: Passes an incrementing counter to enable the re-execution of LightningWorks.
- code_dir: A path string determining where the source is extracted, default is current directory.
- """
- if restart_count:
- self.restart_count = restart_count
-
- if params:
- self.params = params
- self.script_args = self.original_args + [self._to_script_args(k, v) for k, v in params.items()]
-
- if self.drive:
- assert self.code_name
- if os.path.exists(self.code_name):
- clean_tarfile(self.code_name, "r:gz")
-
- if self.code_name in self.drive.list():
- self.drive.get(self.code_name)
- extract_tarfile(self.code_name, code_dir, "r:gz")
-
- prev_cwd = os.getcwd()
- os.chdir(code_dir)
-
- if not os.path.exists(self.script_path):
- raise FileNotFoundError(f"The provided `script_path` {self.script_path}` wasn't found.")
-
- kwargs = {k: v.value if isinstance(v, Payload) else v for k, v in kwargs.items()}
-
- init_globals = globals()
- init_globals.update(kwargs)
-
- self.on_before_run()
- env_copy = os.environ.copy()
- if self.env:
- os.environ.update(self.env)
- res = self._run_tracer(init_globals)
- os.chdir(prev_cwd)
- os.environ = env_copy
- return self.on_after_run(res)
-
- def _run_tracer(self, init_globals):
- sys.argv = [self.script_path]
- tracer = self.configure_tracer()
- return tracer.trace(self.script_path, *self.script_args, init_globals=init_globals)
-
- def on_exit(self):
- for child_pid in _collect_child_process_pids(os.getpid()):
- os.kill(child_pid, signal.SIGTERM)
-
- @staticmethod
- def _to_script_args(k: str, v: str) -> str:
- return f"{k}={v}"
-
-
-__all__ = ["TracerPythonScript"]
diff --git a/src/lightning/app/components/serve/__init__.py b/src/lightning/app/components/serve/__init__.py
deleted file mode 100644
index bd4cf07082aa1..0000000000000
--- a/src/lightning/app/components/serve/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from lightning.app.components.serve.auto_scaler import AutoScaler
-from lightning.app.components.serve.cold_start_proxy import ColdStartProxy
-from lightning.app.components.serve.gradio_server import ServeGradio
-from lightning.app.components.serve.python_server import Category, Image, Number, PythonServer, Text
-from lightning.app.components.serve.streamlit import ServeStreamlit
-
-__all__ = [
- "ServeGradio",
- "ServeStreamlit",
- "PythonServer",
- "Image",
- "Number",
- "Category",
- "Text",
- "AutoScaler",
- "ColdStartProxy",
-]
diff --git a/src/lightning/app/components/serve/auto_scaler.py b/src/lightning/app/components/serve/auto_scaler.py
deleted file mode 100644
index 129bb4e50b635..0000000000000
--- a/src/lightning/app/components/serve/auto_scaler.py
+++ /dev/null
@@ -1,753 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import logging
-import time
-import uuid
-from itertools import cycle
-from typing import Any, Dict, List, Optional, Tuple, Type, Union
-from typing import SupportsFloat as Numeric
-
-import requests
-import uvicorn
-from fastapi import FastAPI, HTTPException, Request
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi.responses import RedirectResponse
-from pydantic import BaseModel
-from starlette.staticfiles import StaticFiles
-
-from lightning.app.components.serve.cold_start_proxy import ColdStartProxy
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cloud import is_running_in_cloud
-from lightning.app.utilities.imports import _is_aiohttp_available, requires
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-if _is_aiohttp_available():
- import aiohttp
- import aiohttp.client_exceptions
-
-logger = Logger(__name__)
-
-
-class _TrackableFastAPI(FastAPI):
- """A FastAPI subclass that tracks the request metadata."""
-
- def __init__(self, *args: Any, **kwargs: Any):
- super().__init__(*args, **kwargs)
- self.global_request_count = 0
- self.num_current_requests = 0
- self.last_processing_time = 0
-
-
-def _maybe_raise_granular_exception(exception: Exception) -> None:
- """Handle an exception from hitting the model servers."""
- if not isinstance(exception, Exception):
- return
-
- if isinstance(exception, HTTPException):
- raise exception
-
- if isinstance(exception, aiohttp.client_exceptions.ServerDisconnectedError):
- raise HTTPException(500, "Worker Server Disconnected") from exception
-
- if isinstance(exception, aiohttp.client_exceptions.ClientError):
- logging.exception(exception)
- raise HTTPException(500, "Worker Server error") from exception
-
- if isinstance(exception, asyncio.TimeoutError):
- raise HTTPException(408, "Request timed out") from exception
-
- if isinstance(exception, Exception) and exception.args[0] == "Server disconnected":
- raise HTTPException(500, "Worker Server disconnected") from exception
-
- logging.exception(exception)
- raise HTTPException(500, exception.args[0]) from exception
-
-
-class _SysInfo(BaseModel):
- num_workers: int
- servers: List[str]
- num_requests: int
- processing_time: int
- global_request_count: int
-
-
-class _BatchRequestModel(BaseModel):
- inputs: List[Any]
-
-
-def _create_fastapi(title: str) -> _TrackableFastAPI:
- fastapi_app = _TrackableFastAPI(title=title)
-
- fastapi_app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
-
- @fastapi_app.get("/", include_in_schema=False)
- async def docs():
- return RedirectResponse("/docs")
-
- @fastapi_app.get("/num-requests")
- async def num_requests() -> int:
- return fastapi_app.num_current_requests
-
- return fastapi_app
-
-
-class _LoadBalancer(LightningWork):
- r"""The LoadBalancer is a LightningWork component that collects the requests and sends them to the prediciton API
- asynchronously using RoundRobin scheduling. It also performs auto batching of the incoming requests.
-
- After enabling you will require to send username and password from the request header for the private endpoints.
-
- Args:
- input_type: Input type.
- output_type: Output type.
- endpoint: The REST API path.
- max_batch_size: The number of requests processed at once.
- timeout_batching: The number of seconds to wait before sending the requests to process in order to allow for
- requests to be batched. In any case, requests are processed as soon as `max_batch_size` is reached.
- timeout_keep_alive: The number of seconds until it closes Keep-Alive connections if no new data is received.
- timeout_inference_request: The number of seconds to wait for inference.
- api_name: The name to be displayed on the UI. Normally, it is the name of the work class
- cold_start_proxy: The proxy service to use while the work is cold starting.
- **kwargs: Arguments passed to :func:`LightningWork.init` like ``CloudCompute``, ``BuildConfig``, etc.
-
- """
-
- @requires(["aiohttp"])
- def __init__(
- self,
- input_type: Type[BaseModel],
- output_type: Type[BaseModel],
- endpoint: str,
- max_batch_size: int = 8,
- # all timeout args are in seconds
- timeout_batching: float = 1,
- timeout_keep_alive: int = 60,
- timeout_inference_request: int = 60,
- api_name: Optional[str] = "API", # used for displaying the name in the UI
- cold_start_proxy: Union[ColdStartProxy, str, None] = None,
- **kwargs: Any,
- ) -> None:
- super().__init__(cloud_compute=CloudCompute("default"), **kwargs)
- self._input_type = input_type
- self._output_type = output_type
- self._timeout_keep_alive = timeout_keep_alive
- self._timeout_inference_request = timeout_inference_request
- self.servers = []
- self.max_batch_size = max_batch_size
- self.timeout_batching = timeout_batching
- self._iter = None
- self._batch = []
- self._responses = {} # {request_id: response}
- self._last_batch_sent = None
- self._server_status = {}
- self._api_name = api_name
- self.ready = False
-
- if not endpoint.startswith("/"):
- endpoint = "/" + endpoint
-
- self.endpoint = endpoint
- self._fastapi_app = None
-
- self._cold_start_proxy = None
- if cold_start_proxy:
- if isinstance(cold_start_proxy, str):
- self._cold_start_proxy = ColdStartProxy(proxy_url=cold_start_proxy)
- elif isinstance(cold_start_proxy, ColdStartProxy):
- self._cold_start_proxy = cold_start_proxy
- else:
- raise ValueError("cold_start_proxy must be of type ColdStartProxy or str")
-
- def get_internal_url(self) -> str:
- if not self._public_ip:
- raise ValueError("Public IP not set")
- return f"http://{self._public_ip}:{self._port}"
-
- async def send_batch(self, batch: List[Tuple[str, _BatchRequestModel]], server_url: str):
- request_data: List[_LoadBalancer._input_type] = [b[1] for b in batch]
- batch_request_data = _BatchRequestModel(inputs=request_data)
-
- try:
- self._server_status[server_url] = False
- async with aiohttp.ClientSession() as session:
- headers = {
- "accept": "application/json",
- "Content-Type": "application/json",
- }
- async with session.post(
- f"{server_url}{self.endpoint}",
- json=batch_request_data.dict(),
- timeout=self._timeout_inference_request,
- headers=headers,
- ) as response:
- if response.status == 408:
- raise HTTPException(408, "Request timed out")
- response.raise_for_status()
- response = await response.json()
- outputs = response["outputs"]
- if len(batch) != len(outputs):
- raise RuntimeError(f"result has {len(outputs)} items but batch is {len(batch)}")
- result = {request[0]: r for request, r in zip(batch, outputs)}
- self._responses.update(result)
- except Exception as ex:
- result = {request[0]: ex for request in batch}
- self._responses.update(result)
- finally:
- # resetting the server status so other requests can be
- # scheduled on this node
- if server_url in self._server_status:
- # TODO - if the server returns an error, track that so
- # we don't send more requests to it
- self._server_status[server_url] = True
-
- def _find_free_server(self) -> Optional[str]:
- existing = set(self._server_status.keys())
- for server in existing:
- status = self._server_status.get(server, None)
- if status is None:
- logger.error("Server is not found in the status list. This should not happen.")
- if status:
- return server
- return None
-
- async def consumer(self):
- """The consumer process that continuously checks for new requests and sends them to the API.
-
- Two instances of this function should not be running with shared `_state_server` as that would create race
- conditions
-
- """
- while True:
- await asyncio.sleep(0.05)
- batch = self._batch[: self.max_batch_size]
- is_batch_ready = len(batch) == self.max_batch_size
- if len(batch) > 0 and self._last_batch_sent is None:
- self._last_batch_sent = time.time()
-
- if self._last_batch_sent:
- is_batch_timeout = time.time() - self._last_batch_sent > self.timeout_batching
- else:
- is_batch_timeout = False
-
- server_url = self._find_free_server()
- # setting the server status to be busy! This will be reset by
- # the send_batch function after the server responds
- if server_url is None:
- continue
- if batch and (is_batch_ready or is_batch_timeout):
- self._server_status[server_url] = False
- # find server with capacity
- # Saving a reference to the result of this function, protects the task disappearing mid-execution
- # https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task
- task = asyncio.create_task(self.send_batch(batch, server_url)) # noqa: F841
- # resetting the batch array, TODO - not locking the array
- self._batch = self._batch[len(batch) :]
- self._last_batch_sent = time.time()
-
- async def process_request(self, data: BaseModel, request_id=None):
- if request_id is None:
- request_id = uuid.uuid4().hex
- if not self.servers and not self._cold_start_proxy:
- # sleeping to trigger the scale up
- raise HTTPException(503, "None of the workers are healthy!, try again in a few seconds")
-
- # if no servers are available, proxy the request to cold start proxy handler
- if not self.servers and self._cold_start_proxy:
- return await self._cold_start_proxy.handle_request(data)
-
- # if out of capacity, proxy the request to cold start proxy handler
- if not self._has_processing_capacity() and self._cold_start_proxy:
- return await self._cold_start_proxy.handle_request(data)
-
- # if we have capacity, process the request
- self._batch.append((request_id, data))
- while True:
- await asyncio.sleep(0.05)
- if request_id in self._responses:
- result = self._responses[request_id]
- del self._responses[request_id]
- _maybe_raise_granular_exception(result)
- return result
-
- def _has_processing_capacity(self):
- """This function checks if we have processing capacity for one more request or not.
-
- Depends on the value from here, we decide whether we should proxy the request or not
-
- """
- if not self._fastapi_app:
- return False
- active_server_count = len(self.servers)
- max_processable = self.max_batch_size * active_server_count
- current_req_count = self._fastapi_app.num_current_requests
- return current_req_count < max_processable
-
- def run(self):
- logger.info(f"servers: {self.servers}")
-
- self._iter = cycle(self.servers)
-
- fastapi_app = _create_fastapi("Load Balancer")
- fastapi_app.SEND_TASK = None
- self._fastapi_app = fastapi_app
-
- input_type = self._input_type
-
- @fastapi_app.middleware("http")
- async def current_request_counter(request: Request, call_next):
- if request.scope["path"] != self.endpoint:
- return await call_next(request)
- fastapi_app.global_request_count += 1
- fastapi_app.num_current_requests += 1
- start_time = time.time()
- response = await call_next(request)
- processing_time = time.time() - start_time
- fastapi_app.last_processing_time = processing_time
- fastapi_app.num_current_requests -= 1
- return response
-
- @fastapi_app.on_event("startup")
- async def startup_event():
- fastapi_app.SEND_TASK = asyncio.create_task(self.consumer())
-
- @fastapi_app.on_event("shutdown")
- def shutdown_event():
- fastapi_app.SEND_TASK.cancel()
-
- @fastapi_app.get("/system/info", response_model=_SysInfo)
- async def sys_info():
- return _SysInfo(
- num_workers=len(self.servers),
- servers=self.servers,
- num_requests=fastapi_app.num_current_requests,
- processing_time=fastapi_app.last_processing_time,
- global_request_count=fastapi_app.global_request_count,
- )
-
- @fastapi_app.put("/system/update-servers")
- async def update_servers(servers: List[str]):
- self.servers = servers
- self._iter = cycle(self.servers)
- updated_servers = set()
- # do not try to loop over the dict keys as the dict might change from other places
- existing_servers = list(self._server_status.keys())
- for server in servers:
- updated_servers.add(server)
- if server not in existing_servers:
- self._server_status[server] = True
- logger.info(f"Registering server {server}", self._server_status)
- for existing in existing_servers:
- if existing not in updated_servers:
- logger.info(f"De-Registering server {existing}", self._server_status)
- del self._server_status[existing]
-
- @fastapi_app.post(self.endpoint, response_model=self._output_type)
- async def balance_api(inputs: input_type):
- return await self.process_request(inputs)
-
- endpoint_info_page = self._get_endpoint_info_page()
- if endpoint_info_page:
- fastapi_app.mount(
- "/endpoint-info", StaticFiles(directory=endpoint_info_page.serve_dir, html=True), name="static"
- )
-
- logger.info(f"Your load balancer has started. The endpoint is 'http://{self.host}:{self.port}{self.endpoint}'")
- self.ready = True
- uvicorn.run(
- fastapi_app,
- host=self.host,
- port=self.port,
- loop="uvloop",
- timeout_keep_alive=self._timeout_keep_alive,
- access_log=False,
- )
-
- def update_servers(self, server_works: List[LightningWork]):
- """Updates works that load balancer distributes requests to.
-
- AutoScaler uses this method to increase/decrease the number of works.
-
- """
- old_server_urls = set(self.servers)
- current_server_urls = {
- f"http://{server._public_ip}:{server.port}" for server in server_works if server._internal_ip
- }
-
- # doing nothing if no server work has been added/removed
- if old_server_urls == current_server_urls:
- return
-
- # checking if the url is ready or not
- available_urls = set()
- for url in current_server_urls:
- try:
- _ = requests.get(url)
- except requests.exceptions.ConnectionError:
- continue
- else:
- available_urls.add(url)
- if old_server_urls == available_urls:
- return
-
- newly_added = available_urls - old_server_urls
- if newly_added:
- logger.info(f"servers added: {newly_added}")
-
- deleted = old_server_urls - available_urls
- if deleted:
- logger.info(f"servers deleted: {deleted}")
- self.send_request_to_update_servers(list(available_urls))
-
- def send_request_to_update_servers(self, servers: List[str]):
- try:
- internal_url = self.get_internal_url()
- except ValueError:
- logger.warn("Cannot update servers as internal_url is not set")
- return
- response = requests.put(f"{internal_url}/system/update-servers", json=servers, timeout=10)
- response.raise_for_status()
-
- @staticmethod
- def _get_sample_dict_from_datatype(datatype: Any) -> dict:
- if not hasattr(datatype, "schema"):
- # not a pydantic model
- raise TypeError(f"datatype must be a pydantic model, for the UI to be generated. but got {datatype}")
-
- if hasattr(datatype, "get_sample_data"):
- return datatype.get_sample_data()
-
- datatype_props = datatype.schema()["properties"]
- out: Dict[str, Any] = {}
- lut = {"string": "data string", "number": 0.0, "integer": 0, "boolean": False}
- for k, v in datatype_props.items():
- if v["type"] not in lut:
- raise TypeError("Unsupported type")
- out[k] = lut[v["type"]]
- return out
-
- def get_code_sample(self, url: str) -> Optional[str]:
- input_type: Any = self._input_type
- output_type: Any = self._output_type
-
- if not (hasattr(input_type, "request_code_sample") and hasattr(output_type, "response_code_sample")):
- return None
- return f"{input_type.request_code_sample(url)}\n{output_type.response_code_sample()}"
-
- def _get_endpoint_info_page(self) -> Optional["APIAccessFrontend"]: # noqa: F821
- try:
- from lightning_api_access import APIAccessFrontend
- except ModuleNotFoundError:
- logger.warn(
- "Some dependencies to run the UI are missing. To resolve, run `pip install lightning-api-access`"
- )
- return None
-
- if is_running_in_cloud():
- url = f"{self._future_url}{self.endpoint}"
- else:
- url = f"http://localhost:{self.port}{self.endpoint}"
-
- frontend_objects = {"name": self._api_name, "url": url, "method": "POST", "request": None, "response": None}
- code_samples = self.get_code_sample(url)
- if code_samples:
- frontend_objects["code_sample"] = code_samples
- # TODO also set request/response for JS UI
- else:
- try:
- request = self._get_sample_dict_from_datatype(self._input_type)
- response = self._get_sample_dict_from_datatype(self._output_type)
- except TypeError:
- return None
- else:
- frontend_objects["request"] = request
- frontend_objects["response"] = response
- return APIAccessFrontend(apis=[frontend_objects])
-
-
-class AutoScaler(LightningFlow):
- """The ``AutoScaler`` can be used to automatically change the number of replicas of the given server in response to
- changes in the number of incoming requests. Incoming requests will be batched and balanced across the replicas.
-
- Args:
- min_replicas: The number of works to start when app initializes.
- max_replicas: The max number of works to spawn to handle the incoming requests.
- scale_out_interval: The number of seconds to wait before checking whether to increase the number of servers.
- scale_in_interval: The number of seconds to wait before checking whether to decrease the number of servers.
- endpoint: Provide the REST API path.
- max_batch_size: (auto-batching) The number of requests to process at once.
- timeout_batching: (auto-batching) The number of seconds to wait before sending the requests to process.
- input_type: Input type.
- output_type: Output type.
- cold_start_proxy: If provided, the proxy will be used while the worker machines are warming up.
-
- .. testcode::
-
- from lightning.app import LightningApp
- from lightning.app.components import AutoScaler
-
- # Example 1: Auto-scaling serve component out-of-the-box
- app = LightningApp(
- app.components.AutoScaler(
- MyPythonServer,
- min_replicas=1,
- max_replicas=8,
- scale_out_interval=10,
- scale_in_interval=10,
- )
- )
-
-
- # Example 2: Customizing the scaling logic
- class MyAutoScaler(AutoScaler):
- def scale(self, replicas: int, metrics: dict) -> int:
- pending_requests_per_running_or_pending_work = metrics["pending_requests"] / (
- replicas + metrics["pending_works"]
- )
-
- # upscale
- max_requests_per_work = self.max_batch_size
- if pending_requests_per_running_or_pending_work >= max_requests_per_work:
- return replicas + 1
-
- # downscale
- min_requests_per_work = max_requests_per_work * 0.25
- if pending_requests_per_running_or_pending_work < min_requests_per_work:
- return replicas - 1
-
- return replicas
-
-
- app = LightningApp(
- MyAutoScaler(
- MyPythonServer,
- min_replicas=1,
- max_replicas=8,
- scale_out_interval=10,
- scale_in_interval=10,
- max_batch_size=8, # for auto batching
- timeout_batching=1, # for auto batching
- )
- )
-
- """
-
- def __init__(
- self,
- work_cls: Type[LightningWork],
- min_replicas: int = 1,
- max_replicas: int = 4,
- scale_out_interval: Numeric = 10,
- scale_in_interval: Numeric = 10,
- max_batch_size: int = 8,
- timeout_batching: float = 1,
- endpoint: str = "api/predict",
- input_type: Type[BaseModel] = Dict,
- output_type: Type[BaseModel] = Dict,
- cold_start_proxy: Union[ColdStartProxy, str, None] = None,
- *work_args: Any,
- **work_kwargs: Any,
- ) -> None:
- super().__init__()
- self.num_replicas = 0
- self._work_registry = {}
-
- self._work_cls = work_cls
- self._work_args = work_args
- self._work_kwargs = work_kwargs
-
- self._input_type = input_type
- self._output_type = output_type
- self.scale_out_interval = scale_out_interval
- self.scale_in_interval = scale_in_interval
- self.max_batch_size = max_batch_size
-
- if max_replicas < min_replicas:
- raise ValueError(
- f"`max_replicas={max_replicas}` must be less than or equal to `min_replicas={min_replicas}`."
- )
- self.max_replicas = max_replicas
- self.min_replicas = min_replicas
- self._last_autoscale = time.time()
- self.fake_trigger = 0
-
- self.load_balancer = _LoadBalancer(
- input_type=self._input_type,
- output_type=self._output_type,
- endpoint=endpoint,
- max_batch_size=max_batch_size,
- timeout_batching=timeout_batching,
- cache_calls=True,
- parallel=True,
- api_name=self._work_cls.__name__,
- cold_start_proxy=cold_start_proxy,
- )
-
- @property
- def ready(self) -> bool:
- return self.load_balancer.ready
-
- @property
- def workers(self) -> List[LightningWork]:
- return [self.get_work(i) for i in range(self.num_replicas)]
-
- def create_work(self) -> LightningWork:
- """Replicates a LightningWork instance with args and kwargs provided via ``__init__``."""
- cloud_compute = self._work_kwargs.get("cloud_compute", None)
- self._work_kwargs.update({
- "start_with_flow": False,
- "cloud_compute": cloud_compute.clone() if cloud_compute else None,
- })
- return self._work_cls(*self._work_args, **self._work_kwargs)
-
- def add_work(self, work) -> str:
- """Adds a new LightningWork instance.
-
- Returns:
- The name of the new work attribute.
-
- """
- work_attribute = uuid.uuid4().hex
- work_attribute = f"worker_{self.num_replicas}_{str(work_attribute)}"
- setattr(self, work_attribute, work)
- self._work_registry[self.num_replicas] = work_attribute
- self.num_replicas += 1
- return work_attribute
-
- def remove_work(self, index: int) -> str:
- """Removes the ``index`` th LightningWork instance."""
- work_attribute = self._work_registry[index]
- del self._work_registry[index]
- work = getattr(self, work_attribute)
- work.stop()
- self.num_replicas -= 1
- return work_attribute
-
- def get_work(self, index: int) -> LightningWork:
- """Returns the ``LightningWork`` instance with the given index."""
- work_attribute = self._work_registry[index]
- return getattr(self, work_attribute)
-
- def run(self):
- if not self.load_balancer.is_running:
- self.load_balancer.run()
- for work in self.workers:
- work.run()
- if self.load_balancer.url:
- self.fake_trigger += 1 # Note: change state to keep calling `run`.
- self.autoscale()
-
- def scale(self, replicas: int, metrics: dict) -> int:
- """The default scaling logic that users can override.
-
- Args:
- replicas: The number of running works.
- metrics: ``metrics['pending_requests']`` is the total number of requests that are currently pending.
- ``metrics['pending_works']`` is the number of pending works.
-
- Returns:
- The target number of running works. The value will be adjusted after this method runs
- so that it satisfies ``min_replicas<=replicas<=max_replicas``.
-
- """
- pending_requests = metrics["pending_requests"]
- active_or_pending_works = replicas + metrics["pending_works"]
-
- if active_or_pending_works == 0:
- return 1 if pending_requests > 0 else 0
-
- pending_requests_per_running_or_pending_work = pending_requests / active_or_pending_works
-
- # scale out if the number of pending requests exceeds max batch size.
- max_requests_per_work = self.max_batch_size
- if pending_requests_per_running_or_pending_work >= max_requests_per_work:
- return replicas + 1
-
- # scale in if the number of pending requests is below 25% of max_requests_per_work
- min_requests_per_work = max_requests_per_work * 0.25
- if pending_requests_per_running_or_pending_work < min_requests_per_work:
- return replicas - 1
-
- return replicas
-
- @property
- def num_pending_requests(self) -> int:
- """Fetches the number of pending requests via load balancer."""
- try:
- load_balancer_url = self.load_balancer.get_internal_url()
- except ValueError:
- logger.warn("Cannot update servers as internal_url is not set")
- return 0
- return int(requests.get(f"{load_balancer_url}/num-requests").json())
-
- @property
- def num_pending_works(self) -> int:
- """The number of pending works."""
- return sum(work.is_pending for work in self.workers)
-
- def autoscale(self) -> None:
- """Adjust the number of works based on the target number returned by ``self.scale``."""
- metrics = {
- "pending_requests": self.num_pending_requests,
- "pending_works": self.num_pending_works,
- }
-
- # ensure min_replicas <= num_replicas <= max_replicas
- num_target_workers = max(
- self.min_replicas,
- min(self.max_replicas, self.scale(self.num_replicas, metrics)),
- )
-
- # scale-out
- if time.time() - self._last_autoscale > self.scale_out_interval:
- # TODO figuring out number of workers to add only based on num_replicas isn't right because pending works
- # are not added to num_replicas
- num_workers_to_add = num_target_workers - self.num_replicas
- for _ in range(num_workers_to_add):
- logger.info(f"Scaling out from {self.num_replicas} to {self.num_replicas + 1}")
- work = self.create_work()
- # TODO: move works into structures
- new_work_id = self.add_work(work)
- logger.info(f"Work created: '{new_work_id}'")
- if num_workers_to_add > 0:
- self._last_autoscale = time.time()
-
- # scale-in
- if time.time() - self._last_autoscale > self.scale_in_interval:
- # TODO figuring out number of workers to remove only based on num_replicas isn't right because pending works
- # are not added to num_replicas
- num_workers_to_remove = self.num_replicas - num_target_workers
- for _ in range(num_workers_to_remove):
- logger.info(f"Scaling in from {self.num_replicas} to {self.num_replicas - 1}")
- removed_work_id = self.remove_work(self.num_replicas - 1)
- logger.info(f"Work removed: '{removed_work_id}'")
- if num_workers_to_remove > 0:
- self._last_autoscale = time.time()
-
- self.load_balancer.update_servers(self.workers)
-
- def configure_layout(self):
- return [
- {"name": "Endpoint Info", "content": f"{self.load_balancer.url}/endpoint-info"},
- {"name": "Swagger", "content": self.load_balancer.url},
- ]
diff --git a/src/lightning/app/components/serve/catimage.png b/src/lightning/app/components/serve/catimage.png
deleted file mode 100644
index a76a35bdb88fb..0000000000000
Binary files a/src/lightning/app/components/serve/catimage.png and /dev/null differ
diff --git a/src/lightning/app/components/serve/cold_start_proxy.py b/src/lightning/app/components/serve/cold_start_proxy.py
deleted file mode 100644
index 2b56e3cd0ca44..0000000000000
--- a/src/lightning/app/components/serve/cold_start_proxy.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-from typing import Any
-
-from fastapi import HTTPException
-from pydantic import BaseModel
-
-from lightning.app.utilities.imports import _is_aiohttp_available, requires
-
-if _is_aiohttp_available():
- import aiohttp
- import aiohttp.client_exceptions
-
-
-class ColdStartProxy:
- """ColdStartProxy allows users to configure the load balancer to use a proxy service while the work is cold
- starting. This is useful with services that gets realtime requests but startup time for workers is high.
-
- If the request body is same and the method is POST for the proxy service,
- then the default implementation of `handle_request` can be used. In that case
- initialize the proxy with the proxy url. Otherwise, the user can override the `handle_request`
-
- Args:
- proxy_url (str): The url of the proxy service
-
- """
-
- @requires(["aiohttp"])
- def __init__(self, proxy_url: str):
- self.proxy_url = proxy_url
- self.proxy_timeout = 50
- if not asyncio.iscoroutinefunction(self.handle_request):
- raise TypeError("handle_request must be an `async` function")
-
- async def handle_request(self, request: BaseModel) -> Any:
- """This method is called when the request is received while the work is cold starting. The default
- implementation of this method is to forward the request body to the proxy service with POST method but the user
- can override this method to handle the request in any way.
-
- Args:
- request: The request body, a pydantic model that is being forwarded by load balancer which
- is a FastAPI service
-
- """
- try:
- async with aiohttp.ClientSession() as session:
- headers = {
- "accept": "application/json",
- "Content-Type": "application/json",
- }
- async with session.post(
- self.proxy_url,
- json=request.dict(),
- timeout=self.proxy_timeout,
- headers=headers,
- ) as response:
- return await response.json()
- except Exception as ex:
- raise HTTPException(status_code=500, detail=f"Error in proxy: {ex}")
diff --git a/src/lightning/app/components/serve/gradio_server.py b/src/lightning/app/components/serve/gradio_server.py
deleted file mode 100644
index a413136fc6432..0000000000000
--- a/src/lightning/app/components/serve/gradio_server.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from functools import partial
-from types import ModuleType
-from typing import Any, List, Optional
-
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.imports import _is_gradio_available, requires
-
-if _is_gradio_available():
- import gradio
-else:
- gradio = ModuleType("gradio")
- gradio.themes = ModuleType("gradio.themes")
-
- class __DummyBase:
- pass
-
- gradio.themes.Base = __DummyBase
-
-
-class ServeGradio(LightningWork, abc.ABC):
- """The ServeGradio Class enables to quickly create a ``gradio`` based UI for your LightningApp.
-
- In the example below, the ``ServeGradio`` is subclassed to deploy ``AnimeGANv2``.
-
- .. literalinclude:: ../../../../examples/app/components/serve/gradio/app.py
- :language: python
-
- The result would be the following:
-
- .. image:: https://pl-public-data.s3.amazonaws.com/assets_lightning/anime_gan.gif
- :alt: Animation showing how to AnimeGANv2 UI would looks like.
-
- """
-
- inputs: Any
- outputs: Any
- examples: Optional[List] = None
- enable_queue: bool = False
- title: Optional[str] = None
- description: Optional[str] = None
-
- _start_method = "spawn"
-
- def __init__(self, *args: Any, theme: Optional[gradio.themes.Base] = None, **kwargs: Any):
- requires("gradio")(super().__init__(*args, **kwargs))
- assert self.inputs
- assert self.outputs
- self._model = None
- self._theme = theme or ServeGradio.__get_lightning_gradio_theme()
-
- self.ready = False
-
- @property
- def model(self):
- return self._model
-
- @abc.abstractmethod
- def predict(self, *args: Any, **kwargs: Any):
- """Override with your logic to make a prediction."""
-
- @abc.abstractmethod
- def build_model(self) -> Any:
- """Override to instantiate and return your model.
-
- The model would be accessible under self.model
-
- """
-
- def run(self, *args: Any, **kwargs: Any):
- if self._model is None:
- self._model = self.build_model()
- fn = partial(self.predict, *args, **kwargs)
- fn.__name__ = self.predict.__name__
- self.ready = True
- gradio.Interface(
- fn=fn,
- inputs=self.inputs,
- outputs=self.outputs,
- examples=self.examples,
- title=self.title,
- description=self.description,
- theme=self._theme,
- ).launch(
- server_name=self.host,
- server_port=self.port,
- enable_queue=self.enable_queue,
- )
-
- def configure_layout(self) -> str:
- return self.url
-
- @staticmethod
- def __get_lightning_gradio_theme():
- return gradio.themes.Default(
- primary_hue=gradio.themes.Color(
- "#ffffff",
- "#e9d5ff",
- "#d8b4fe",
- "#c084fc",
- "#fcfcfc",
- "#a855f7",
- "#9333ea",
- "#8823e1",
- "#6b21a8",
- "#2c2730",
- "#1c1c1c",
- ),
- secondary_hue=gradio.themes.Color(
- "#c3a1e8",
- "#e9d5ff",
- "#d3bbec",
- "#c795f9",
- "#9174af",
- "#a855f7",
- "#9333ea",
- "#6700c2",
- "#000000",
- "#991ef1",
- "#33243d",
- ),
- neutral_hue=gradio.themes.Color(
- "#ede9fe",
- "#ddd6fe",
- "#c4b5fd",
- "#a78bfa",
- "#fafafa",
- "#8b5cf6",
- "#7c3aed",
- "#6d28d9",
- "#6130b0",
- "#8a4ce6",
- "#3b3348",
- ),
- ).set(
- body_background_fill="*primary_50",
- body_background_fill_dark="*primary_950",
- body_text_color_dark="*primary_100",
- body_text_size="*text_sm",
- body_text_color_subdued_dark="*primary_100",
- background_fill_primary="*primary_50",
- background_fill_primary_dark="*primary_950",
- background_fill_secondary="*primary_50",
- background_fill_secondary_dark="*primary_950",
- border_color_accent="*primary_400",
- border_color_accent_dark="*primary_900",
- border_color_primary="*primary_600",
- border_color_primary_dark="*primary_800",
- color_accent="*primary_400",
- color_accent_soft="*primary_300",
- color_accent_soft_dark="*primary_700",
- link_text_color="*primary_500",
- link_text_color_dark="*primary_50",
- link_text_color_active="*secondary_800",
- link_text_color_active_dark="*primary_500",
- link_text_color_hover="*primary_400",
- link_text_color_hover_dark="*primary_400",
- link_text_color_visited="*primary_500",
- link_text_color_visited_dark="*secondary_100",
- block_background_fill="*primary_50",
- block_background_fill_dark="*primary_900",
- block_border_color_dark="*primary_800",
- checkbox_background_color="*primary_50",
- checkbox_background_color_dark="*primary_50",
- checkbox_background_color_focus="*primary_100",
- checkbox_background_color_focus_dark="*primary_100",
- checkbox_background_color_hover="*primary_400",
- checkbox_background_color_hover_dark="*primary_500",
- checkbox_background_color_selected="*primary_300",
- checkbox_background_color_selected_dark="*primary_500",
- checkbox_border_color_dark="*primary_200",
- checkbox_border_radius="*radius_md",
- input_background_fill="*primary_50",
- input_background_fill_dark="*primary_900",
- input_radius="*radius_xxl",
- slider_color="*primary_600",
- slider_color_dark="*primary_700",
- button_large_radius="*radius_xxl",
- button_large_text_size="*text_md",
- button_small_radius="*radius_xxl",
- button_primary_background_fill_dark="*primary_800",
- button_primary_background_fill_hover_dark="*primary_700",
- button_primary_border_color_dark="*primary_800",
- button_secondary_background_fill="*neutral_200",
- button_secondary_background_fill_dark="*primary_600",
- )
diff --git a/src/lightning/app/components/serve/python_server.py b/src/lightning/app/components/serve/python_server.py
deleted file mode 100644
index 4c1621de7197d..0000000000000
--- a/src/lightning/app/components/serve/python_server.py
+++ /dev/null
@@ -1,328 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import asyncio
-import base64
-import os
-import platform
-from typing import TYPE_CHECKING, Any, Dict, Optional
-
-import requests
-import uvicorn
-from fastapi import FastAPI
-from lightning_utilities.core.imports import compare_version, module_available
-from pydantic import BaseModel
-
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.imports import _is_torch_available, requires
-
-if TYPE_CHECKING:
- from lightning.app.frontend.frontend import Frontend
-
-logger = Logger(__name__)
-
-# Skip doctests if requirements aren't available
-if not module_available("lightning_api_access") or not _is_torch_available():
- __doctest_skip__ = ["PythonServer", "PythonServer.*"]
-
-
-def _get_device():
- import operator
-
- import torch
-
- _TORCH_GREATER_EQUAL_1_12 = compare_version("torch", operator.ge, "1.12.0")
-
- local_rank = int(os.getenv("LOCAL_RANK", "0"))
-
- if _TORCH_GREATER_EQUAL_1_12 and torch.backends.mps.is_available() and platform.processor() in ("arm", "arm64"):
- return torch.device("mps", local_rank)
-
- return torch.device(f"cuda:{local_rank}" if torch.cuda.is_available() else "cpu")
-
-
-class _DefaultInputData(BaseModel):
- payload: str
-
-
-class _DefaultOutputData(BaseModel):
- prediction: str
-
-
-class Image(BaseModel):
- image: Optional[str] = None
-
- @staticmethod
- def get_sample_data() -> Dict[Any, Any]:
- url = "https://raw.githubusercontent.com/Lightning-AI/LAI-Triton-Server-Component/main/catimage.png"
- img = requests.get(url).content
- img = base64.b64encode(img).decode("UTF-8")
- return {"image": img}
-
- @staticmethod
- def request_code_sample(url: str) -> str:
- return f"""
-import base64
-from pathlib import Path
-import requests
-
-imgurl = "https://raw.githubusercontent.com/Lightning-AI/LAI-Triton-Server-Component/main/catimage.png"
-img = requests.get(imgurl).content
-img = base64.b64encode(img).decode("UTF-8")
-response = requests.post('{url}', json={{"image": img}})
-# If you are using basic authentication for your app, you should add your credentials to the request:
-# auth = requests.auth.HTTPBasicAuth('your_username', 'your_password')
-# response = requests.post('{url}', json={{"image": img}}, auth=auth)
-"""
-
- @staticmethod
- def response_code_sample() -> str:
- return """img = response.json()["image"]
-img = base64.b64decode(img.encode("utf-8"))
-Path("response.png").write_bytes(img)
-"""
-
-
-class Category(BaseModel):
- category: Optional[int] = None
-
- @staticmethod
- def get_sample_data() -> Dict[Any, Any]:
- return {"category": 463}
-
- @staticmethod
- def response_code_sample() -> str:
- return """print("Predicted category is: ", response.json()["category"])
-"""
-
-
-class Text(BaseModel):
- text: Optional[str] = None
-
- @staticmethod
- def get_sample_data() -> Dict[Any, Any]:
- return {"text": "A portrait of a person looking away from the camera"}
-
- @staticmethod
- def request_code_sample(url: str) -> str:
- return f"""
-import base64
-from pathlib import Path
-import requests
-
-response = requests.post('{url}', json={{
- "text": "A portrait of a person looking away from the camera"
-}})
-# If you are using basic authentication for your app, you should add your credentials to the request:
-# response = requests.post('{url}', json={{
-# "text": "A portrait of a person looking away from the camera"
-# }}, auth=requests.auth.HTTPBasicAuth('your_username', 'your_password'))
-"""
-
-
-class Number(BaseModel):
- # deprecated - TODO remove this in favour of Category
- prediction: Optional[int] = None
-
- @staticmethod
- def get_sample_data() -> Dict[Any, Any]:
- return {"prediction": 463}
-
-
-class PythonServer(LightningWork, abc.ABC):
- _start_method = "spawn"
-
- @requires(["torch"])
- def __init__( # type: ignore
- self,
- input_type: type = _DefaultInputData,
- output_type: type = _DefaultOutputData,
- **kwargs: Any,
- ):
- """The PythonServer Class enables to easily get your machine learning server up and running.
-
- Arguments:
- input_type: Optional `input_type` to be provided. This needs to be a pydantic BaseModel class.
- The default data type is good enough for the basic usecases and it expects the data
- to be a json object that has one key called `payload`
-
- .. code-block:: python
-
- input_data = {"payload": "some data"}
-
- and this can be accessed as `request.payload` in the `predict` method.
-
- .. code-block:: python
-
- def predict(self, request):
- data = request.payload
-
- output_type: Optional `output_type` to be provided. This needs to be a pydantic BaseModel class.
- The default data type is good enough for the basic usecases. It expects the return value of
- the `predict` method to be a dictionary with one key called `prediction`.
-
- .. code-block:: python
-
- def predict(self, request):
- # some code
- return {"prediction": "some data"}
-
- and this can be accessed as `response.json()["prediction"]` in the client if
- you are using requests library
-
- Example:
-
- >>> from lightning.app.components.serve.python_server import PythonServer
- >>> from lightning.app import LightningApp
- ...
- >>> class SimpleServer(PythonServer):
- ...
- ... def setup(self):
- ... self._model = lambda x: x + " " + x
- ...
- ... def predict(self, request):
- ... return {"prediction": self._model(request.image)}
- ...
- >>> app = LightningApp(SimpleServer())
-
- """
- super().__init__(parallel=True, **kwargs)
- if not issubclass(input_type, BaseModel):
- raise TypeError("input_type must be a pydantic BaseModel class")
- if not issubclass(output_type, BaseModel):
- raise TypeError("output_type must be a pydantic BaseModel class")
- self._input_type = input_type
- self._output_type = output_type
-
- self.ready = False
-
- def setup(self, *args: Any, **kwargs: Any) -> None:
- """This method is called before the server starts. Override this if you need to download the model or
- initialize the weights, setting up pipelines etc.
-
- Note that this will be called exactly once on every work machines. So if you have multiple machines for serving,
- this will be called on each of them.
-
- """
- return
-
- def configure_input_type(self) -> type:
- return self._input_type
-
- def configure_output_type(self) -> type:
- return self._output_type
-
- @abc.abstractmethod
- def predict(self, request: Any) -> Any:
- """This method is called when a request is made to the server.
-
- This method must be overriden by the user with the prediction logic. The pre/post processing, actual prediction
- using the model(s) etc goes here
-
- """
- pass
-
- @staticmethod
- def _get_sample_dict_from_datatype(datatype: Any) -> dict:
- if hasattr(datatype, "get_sample_data"):
- return datatype.get_sample_data()
-
- datatype_props = datatype.schema()["properties"]
- out: Dict[str, Any] = {}
- for k, v in datatype_props.items():
- if v["type"] == "string":
- out[k] = "data string"
- elif v["type"] == "number":
- out[k] = 0.0
- elif v["type"] == "integer":
- out[k] = 0
- elif v["type"] == "boolean":
- out[k] = False
- else:
- raise TypeError("Unsupported type")
- return out
-
- def _attach_predict_fn(self, fastapi_app: FastAPI) -> None:
- input_type: type = self.configure_input_type()
- output_type: type = self.configure_output_type()
-
- def predict_fn_sync(request: input_type): # type: ignore
- return self.predict(request)
-
- async def async_predict_fn(request: input_type): # type: ignore
- return await self.predict(request)
-
- if asyncio.iscoroutinefunction(self.predict):
- fastapi_app.post("/predict", response_model=output_type)(async_predict_fn)
- else:
- fastapi_app.post("/predict", response_model=output_type)(predict_fn_sync)
-
- def get_code_sample(self, url: str) -> Optional[str]:
- input_type: Any = self.configure_input_type()
- output_type: Any = self.configure_output_type()
-
- if not (hasattr(input_type, "request_code_sample") and hasattr(output_type, "response_code_sample")):
- return None
- return f"{input_type.request_code_sample(url)}\n{output_type.response_code_sample()}"
-
- def configure_layout(self) -> Optional["Frontend"]:
- try:
- from lightning_api_access import APIAccessFrontend
- except ModuleNotFoundError:
- logger.warn(
- "Some dependencies to run the UI are missing. To resolve, run `pip install lightning-api-access`"
- )
- return None
-
- class_name = self.__class__.__name__
- url = f"{self.url}/predict"
-
- try:
- request = self._get_sample_dict_from_datatype(self.configure_input_type())
- response = self._get_sample_dict_from_datatype(self.configure_output_type())
- except TypeError:
- return None
-
- frontend_payload = {
- "name": class_name,
- "url": url,
- "method": "POST",
- "request": request,
- "response": response,
- }
-
- code_sample = self.get_code_sample(url)
- if code_sample:
- frontend_payload["code_sample"] = code_sample
-
- return APIAccessFrontend(apis=[frontend_payload])
-
- def run(self, *args: Any, **kwargs: Any) -> Any:
- """Run method takes care of configuring and setting up a FastAPI server behind the scenes.
-
- Normally, you don't need to override this method.
-
- """
- self.setup(*args, **kwargs)
-
- fastapi_app = FastAPI()
- self._attach_predict_fn(fastapi_app)
-
- self.ready = True
- logger.info(
- f"Your {self.__class__.__qualname__} has started. View it in your browser: http://{self.host}:{self.port}"
- )
- uvicorn.run(app=fastapi_app, host=self.host, port=self.port, log_level="error")
diff --git a/src/lightning/app/components/serve/serve.py b/src/lightning/app/components/serve/serve.py
deleted file mode 100644
index 0ae40302293f3..0000000000000
--- a/src/lightning/app/components/serve/serve.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import inspect
-import os
-import pydoc
-import subprocess
-import sys
-from typing import Any, Callable, Optional
-
-import fastapi # noqa E511
-import uvicorn
-from fastapi import FastAPI
-from fastapi.responses import JSONResponse
-
-from lightning.app.components.serve.types import _DESERIALIZER, _SERIALIZER
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-fastapi_service = FastAPI()
-
-
-class _InferenceCallable:
- def __init__(
- self,
- deserialize: Callable,
- predict: Callable,
- serialize: Callable,
- ):
- self.deserialize = deserialize
- self.predict = predict
- self.serialize = serialize
-
- async def run(self, data) -> Any:
- return self.serialize(self.predict(self.deserialize(data)))
-
-
-class ModelInferenceAPI(LightningWork, abc.ABC):
- def __init__(
- self,
- input: Optional[str] = None,
- output: Optional[str] = None,
- host: str = "127.0.0.1",
- port: int = 7777,
- workers: int = 0,
- ):
- """The ModelInferenceAPI Class enables to easily get your model served.
-
- Arguments:
- input: Optional `input` to be provided. This would make provide a built-in deserializer.
- output: Optional `output` to be provided. This would make provide a built-in serializer.
- host: Address to be used to serve the model.
- port: Port to be used to serve the model.
- workers: Number of workers for the uvicorn. Warning, this won't work if your subclass takes more arguments.
-
- """
- super().__init__(parallel=True, host=host, port=port)
- if input and input not in _DESERIALIZER:
- raise Exception(f"Only input in {_DESERIALIZER.keys()} are supported.")
- if output and output not in _SERIALIZER:
- raise Exception(f"Only output in {_SERIALIZER.keys()} are supported.")
- self.input = input
- self.output = output
- self.workers = workers
- self._model = None
-
- self.ready = False
-
- @property
- def model(self):
- return self._model
-
- @abc.abstractmethod
- def build_model(self) -> Any:
- """Override to define your model."""
-
- def deserialize(self, data) -> Any:
- return data
-
- @abc.abstractmethod
- def predict(self, data) -> Any:
- """Override to add your predict logic."""
-
- def serialize(self, data) -> Any:
- return data
-
- def run(self):
- global fastapi_service
- if self.workers > 1:
- # TODO: This is quite limitated
- # Find a more reliable solution to enable multi workers serving.
- env = os.environ.copy()
- module = inspect.getmodule(self).__file__
- env["LIGHTNING_MODEL_INFERENCE_API_FILE"] = module
- env["LIGHTNING_MODEL_INFERENCE_API_CLASS_NAME"] = self.__class__.__name__
- if self.input:
- env["LIGHTNING_MODEL_INFERENCE_API_INPUT"] = self.input
- if self.output:
- env["LIGHTNING_MODEL_INFERENCE_API_OUTPUT"] = self.output
- command = [
- sys.executable,
- "-m",
- "uvicorn",
- "--workers",
- str(self.workers),
- "--host",
- str(self.host),
- "--port",
- str(self.port),
- "serve:fastapi_service",
- ]
- process = subprocess.Popen(command, env=env, cwd=os.path.dirname(__file__))
- self.ready = True
- process.wait()
- else:
- self._populate_app(fastapi_service)
- self.ready = True
- self._launch_server(fastapi_service)
-
- def _populate_app(self, fastapi_service: FastAPI):
- self._model = self.build_model()
-
- fastapi_service.post("/predict", response_class=JSONResponse)(
- _InferenceCallable(
- deserialize=_DESERIALIZER[self.input] if self.input else self.deserialize,
- predict=self.predict,
- serialize=_SERIALIZER[self.output] if self.output else self.serialize,
- ).run
- )
-
- def _launch_server(self, fastapi_service: FastAPI):
- logger.info(f"Your app has started. View it in your browser: http://{self.host}:{self.port}")
- uvicorn.run(app=fastapi_service, host=self.host, port=self.port, log_level="error")
-
- def configure_layout(self) -> str:
- return f"{self.url}/docs"
-
-
-def _maybe_create_instance() -> Optional[ModelInferenceAPI]:
- """This function tries to re-create the user `ModelInferenceAPI` if the environment associated with multi workers
- are present."""
- render_fn_name = os.getenv("LIGHTNING_MODEL_INFERENCE_API_CLASS_NAME", None)
- render_fn_module_file = os.getenv("LIGHTNING_MODEL_INFERENCE_API_FILE", None)
- if render_fn_name is None or render_fn_module_file is None:
- return None
- module = pydoc.importfile(render_fn_module_file)
- cls = getattr(module, render_fn_name)
- input = os.getenv("LIGHTNING_MODEL_INFERENCE_API_INPUT", None)
- output = os.getenv("LIGHTNING_MODEL_INFERENCE_API_OUTPUT", None)
- return cls(input=input, output=output)
-
-
-instance = _maybe_create_instance()
-if instance:
- instance._populate_app(fastapi_service)
diff --git a/src/lightning/app/components/serve/streamlit.py b/src/lightning/app/components/serve/streamlit.py
deleted file mode 100644
index ff0ea9ed8a8bb..0000000000000
--- a/src/lightning/app/components/serve/streamlit.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import inspect
-import os
-import pydoc
-import subprocess
-import sys
-from typing import Any, Callable, Type
-
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.app_helpers import StreamLitStatePlugin
-from lightning.app.utilities.state import AppState
-
-
-class ServeStreamlit(LightningWork, abc.ABC):
- """The ``ServeStreamlit`` work allows you to use streamlit from a work.
-
- You can optionally build a model in the ``build_model`` hook, which will only be called once per session.
-
- """
-
- def __init__(self, *args: Any, **kwargs: Any):
- super().__init__(*args, **kwargs)
-
- self.ready = False
-
- self._process = None
-
- @property
- def model(self) -> Any:
- return getattr(self, "_model", None)
-
- @abc.abstractmethod
- def render(self) -> None:
- """Override with your streamlit render function."""
-
- def build_model(self) -> Any:
- """Optionally override to instantiate and return your model.
-
- The model will be accessible under ``self.model``.
-
- """
- return None
-
- def run(self) -> None:
- env = os.environ.copy()
- env["LIGHTNING_COMPONENT_NAME"] = self.name
- env["LIGHTNING_WORK"] = self.__class__.__name__
- env["LIGHTNING_WORK_MODULE_FILE"] = inspect.getmodule(self).__file__
- self._process = subprocess.Popen(
- [
- sys.executable,
- "-m",
- "streamlit",
- "run",
- __file__,
- "--server.address",
- str(self.host),
- "--server.port",
- str(self.port),
- "--server.headless",
- "true", # do not open the browser window when running locally
- ],
- env=env,
- )
- self.ready = True
- self._process.wait()
-
- def on_exit(self) -> None:
- if self._process is not None:
- self._process.kill()
-
- def configure_layout(self) -> str:
- return self.url
-
-
-class _PatchedWork:
- """The ``_PatchedWork`` is used to emulate a work instance from a subprocess. This is acheived by patching the self
- reference in methods an properties to point to the AppState.
-
- Args:
- state: The work state to patch
- work_class: The work class to emulate
-
- """
-
- def __init__(self, state: AppState, work_class: Type):
- super().__init__()
- self._state = state
- self._work_class = work_class
-
- def __getattr__(self, name: str) -> Any:
- try:
- return getattr(self._state, name)
- except AttributeError:
- # The name isn't in the state, so check if it's a callable or a property
- attribute = inspect.getattr_static(self._work_class, name)
- if callable(attribute):
- attribute = attribute.__get__(self, self._work_class)
- return attribute
- if isinstance(attribute, (staticmethod, property)):
- return attribute.__get__(self, self._work_class)
-
- # Look for the name in the instance (e.g. for private variables)
- return object.__getattribute__(self, name)
-
- def __setattr__(self, name: str, value: Any) -> None:
- if name in ["_state", "_work_class"]:
- return object.__setattr__(self, name, value)
-
- if hasattr(self._state, name):
- return setattr(self._state, name, value)
- return object.__setattr__(self, name, value)
-
-
-def _reduce_to_component_scope(state: AppState, component_name: str) -> AppState:
- """Given the app state, this utility traverses down to the level of the given component name."""
- component_name_parts = component_name.split(".")[1:] # exclude root
- component_state = state
- for part in component_name_parts:
- component_state = getattr(component_state, part)
- return component_state
-
-
-def _get_work_class() -> Callable:
- """Import the work class specified in the environment."""
- work_name = os.environ["LIGHTNING_WORK"]
- work_module_file = os.environ["LIGHTNING_WORK_MODULE_FILE"]
- module = pydoc.importfile(work_module_file)
- return getattr(module, work_name)
-
-
-def _build_model(work: ServeStreamlit) -> None:
- import streamlit as st
-
- # Build the model (once per session, equivalent to gradio when enable_queue is Flase)
- if "_model" not in st.session_state:
- with st.spinner("Building model..."):
- st.session_state["_model"] = work.build_model()
-
- work._model = st.session_state["_model"]
-
-
-def _main() -> None:
- # Get the AppState
- app_state = AppState(plugin=StreamLitStatePlugin())
- work_state = _reduce_to_component_scope(app_state, os.environ["LIGHTNING_COMPONENT_NAME"])
-
- # Create the patched work
- work_class = _get_work_class()
- patched_work = _PatchedWork(work_state, work_class)
-
- # Build and attach the model
- _build_model(patched_work)
-
- # Render
- patched_work.render()
-
-
-if __name__ == "__main__":
- _main()
diff --git a/src/lightning/app/components/serve/types/__init__.py b/src/lightning/app/components/serve/types/__init__.py
deleted file mode 100644
index 059ca53b07682..0000000000000
--- a/src/lightning/app/components/serve/types/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from lightning.app.components.serve.types.image import Image
-
-_SERIALIZER = {"image": Image.serialize}
-_DESERIALIZER = {"image": Image.deserialize}
diff --git a/src/lightning/app/components/serve/types/image.py b/src/lightning/app/components/serve/types/image.py
deleted file mode 100644
index 32f123fe8b63b..0000000000000
--- a/src/lightning/app/components/serve/types/image.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import base64
-from io import BytesIO
-
-from lightning.app.components.serve.types.type import BaseType
-from lightning.app.utilities.imports import _is_pil_available, _is_torch_available
-
-if _is_torch_available():
- from torch import Tensor
-
-if _is_pil_available():
- from PIL import Image as PILImage
-
-
-class Image(BaseType):
- @staticmethod
- def deserialize(data: dict):
- encoded_with_padding = (data + "===").encode("ascii")
- img = base64.b64decode(encoded_with_padding)
- buffer = BytesIO(img)
- return PILImage.open(buffer, mode="r")
-
- @staticmethod
- def serialize(tensor: "Tensor") -> str:
- tensor = tensor.squeeze(0).numpy()
- print(tensor.shape)
- image = PILImage.fromarray(tensor)
- buffer = BytesIO()
- image.save(buffer, format="PNG")
- buffer.seek(0)
- encoded = buffer.getvalue()
- return base64.b64encode(encoded).decode("ascii")
diff --git a/src/lightning/app/components/serve/types/type.py b/src/lightning/app/components/serve/types/type.py
deleted file mode 100644
index 157940a60f8e7..0000000000000
--- a/src/lightning/app/components/serve/types/type.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from typing import Any
-
-
-class BaseType(abc.ABCMeta):
- """Base class for Types."""
-
- @abc.abstractmethod
- def serialize(self, data): # pragma: no cover
- """Serialize the incoming data to send it through the network."""
-
- @abc.abstractmethod
- def deserialize(self, *args: Any, **kwargs: Any): # pragma: no cover
- """Take the inputs from the network and deserilize/convert them them.
-
- Output from this method will go to the exposed method as arguments.
-
- """
diff --git a/src/lightning/app/components/training.py b/src/lightning/app/components/training.py
deleted file mode 100644
index ba198f063fb42..0000000000000
--- a/src/lightning/app/components/training.py
+++ /dev/null
@@ -1,203 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from typing import Any, Dict, List, Optional, Tuple, Type, Union
-
-from lightning.app.components.python import TracerPythonScript
-from lightning.app.core.flow import LightningFlow
-from lightning.app.storage.path import Path
-from lightning.app.structures import List as _List
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-_logger = Logger(__name__)
-
-
-class PyTorchLightningScriptRunner(TracerPythonScript):
- def __init__(
- self,
- script_path: str,
- script_args: Optional[Union[list, str]] = None,
- node_rank: int = 1,
- num_nodes: int = 1,
- sanity_serving: bool = False,
- cloud_compute: Optional[CloudCompute] = None,
- parallel: bool = True,
- raise_exception: bool = True,
- env: Optional[Dict[str, Any]] = None,
- **kwargs: Any,
- ):
- super().__init__(
- script_path,
- script_args,
- raise_exception=raise_exception,
- parallel=parallel,
- cloud_compute=cloud_compute,
- **kwargs,
- )
- self.node_rank = node_rank
- self.num_nodes = num_nodes
- self.best_model_path = None
- self.best_model_score = None
- self.monitor = None
- self.sanity_serving = sanity_serving
- self.has_finished = False
- self.env = env
-
- def configure_tracer(self):
- from lightning.pytorch import Trainer
-
- tracer = super().configure_tracer()
- tracer.add_traced(Trainer, "__init__", pre_fn=self._trainer_init_pre_middleware)
- return tracer
-
- def run(self, internal_urls: Optional[List[Tuple[str, str]]] = None, **kwargs: Any) -> None:
- if not internal_urls:
- # Note: This is called only once.
- _logger.info(f"The node {self.node_rank} started !")
- return None
-
- if self.env:
- os.environ.update(self.env)
-
- distributed_env_vars = {
- "MASTER_ADDR": internal_urls[0][0],
- "MASTER_PORT": str(internal_urls[0][1]),
- "NODE_RANK": str(self.node_rank),
- "PL_TRAINER_NUM_NODES": str(self.num_nodes),
- "PL_TRAINER_DEVICES": "auto",
- "PL_TRAINER_ACCELERATOR": "auto",
- }
-
- os.environ.update(distributed_env_vars)
- return super().run(**kwargs)
-
- def on_after_run(self, script_globals):
- from lightning.pytorch import Trainer
- from lightning.pytorch.cli import LightningCLI
-
- for v in script_globals.values():
- if isinstance(v, LightningCLI):
- trainer = v.trainer
- break
- if isinstance(v, Trainer):
- trainer = v
- break
- else:
- raise RuntimeError("No trainer instance found.")
-
- self.monitor = trainer.checkpoint_callback.monitor
-
- if trainer.checkpoint_callback.best_model_score:
- self.best_model_path = Path(trainer.checkpoint_callback.best_model_path)
- self.best_model_score = float(trainer.checkpoint_callback.best_model_score)
- else:
- self.best_model_path = Path(trainer.checkpoint_callback.last_model_path)
-
- self.has_finished = True
-
- def _trainer_init_pre_middleware(self, trainer, *args: Any, **kwargs: Any):
- if self.node_rank != 0:
- return {}, args, kwargs
-
- from lightning.pytorch.serve import ServableModuleValidator
-
- callbacks = kwargs.get("callbacks", [])
- if self.sanity_serving:
- callbacks = callbacks + [ServableModuleValidator()]
- kwargs["callbacks"] = callbacks
- return {}, args, kwargs
-
- @property
- def is_running_in_cloud(self) -> bool:
- return "LIGHTNING_APP_STATE_URL" in os.environ
-
-
-class LightningTrainerScript(LightningFlow):
- def __init__(
- self,
- script_path: str,
- script_args: Optional[Union[list, str]] = None,
- num_nodes: int = 1,
- cloud_compute: CloudCompute = CloudCompute("default"),
- sanity_serving: bool = False,
- script_runner: Type[TracerPythonScript] = PyTorchLightningScriptRunner,
- **script_runner_kwargs,
- ):
- """This component enables performing distributed multi-node multi-device training.
-
- Example::
-
- from lightning.app import LightningApp
- from lightning.app.components.training import LightningTrainerScript
- from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
- app = LightningApp(
- LightningTrainerScript(
- "train.py",
- num_nodes=2,
- cloud_compute=CloudCompute("gpu"),
- ),
- )
-
- Arguments:
- script_path: Path to the script to be executed.
- script_args: The arguments to be pass to the script.
- num_nodes: Number of nodes.
- cloud_compute: The cloud compute object used in the cloud.
- sanity_serving: Whether to validate that the model correctly implements
- the ServableModule API
-
- """
- super().__init__()
- self.script_path = script_path
- self.script_args = script_args
- self.num_nodes = num_nodes
- self.sanity_serving = sanity_serving
- self._script_runner = script_runner
- self._script_runner_kwargs = script_runner_kwargs
-
- self.ws = _List()
- for node_rank in range(self.num_nodes):
- self.ws.append(
- self._script_runner(
- script_path=self.script_path,
- script_args=self.script_args,
- cloud_compute=cloud_compute,
- node_rank=node_rank,
- sanity_serving=self.sanity_serving,
- num_nodes=self.num_nodes,
- **self._script_runner_kwargs,
- )
- )
-
- def run(self, **run_kwargs):
- for work in self.ws:
- if all(w.internal_ip for w in self.ws):
- internal_urls = [(w.internal_ip, w.port) for w in self.ws]
- work.run(internal_urls=internal_urls, **run_kwargs)
- if all(w.has_finished for w in self.ws):
- for w in self.ws:
- w.stop()
- else:
- work.run()
-
- @property
- def best_model_score(self) -> Optional[float]:
- return self.ws[0].best_model_score
-
- @property
- def best_model_paths(self) -> List[Optional[Path]]:
- return [self.ws[node_idx].best_mode_path for node_idx in range(len(self.ws))]
diff --git a/src/lightning/app/core/__init__.py b/src/lightning/app/core/__init__.py
deleted file mode 100644
index cdf8b6aee1029..0000000000000
--- a/src/lightning/app/core/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from lightning.app.core.app import LightningApp
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.work import LightningWork
-
-__all__ = ["LightningApp", "LightningFlow", "LightningWork"]
diff --git a/src/lightning/app/core/api.py b/src/lightning/app/core/api.py
deleted file mode 100644
index 5f50c6faa0a2b..0000000000000
--- a/src/lightning/app/core/api.py
+++ /dev/null
@@ -1,498 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import contextlib
-import json
-import os
-import queue
-import socket
-import sys
-import traceback
-from copy import deepcopy
-from multiprocessing import Queue
-from pathlib import Path
-from tempfile import TemporaryDirectory
-from threading import Event, Lock, Thread
-from time import sleep
-from typing import Dict, List, Mapping, Optional, Union
-
-import uvicorn
-from deepdiff import DeepDiff, Delta
-from fastapi import FastAPI, File, HTTPException, Request, Response, UploadFile, WebSocket, status
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi.params import Header
-from fastapi.responses import HTMLResponse, JSONResponse
-from fastapi.staticfiles import StaticFiles
-from fastapi.templating import Jinja2Templates
-from pydantic import BaseModel
-from websockets.exceptions import ConnectionClosed
-
-from lightning.app.api.http_methods import _HttpMethod
-from lightning.app.api.request_types import _DeltaRequest
-from lightning.app.core.constants import (
- ENABLE_PULLING_STATE_ENDPOINT,
- ENABLE_PUSHING_STATE_ENDPOINT,
- ENABLE_STATE_WEBSOCKET,
- ENABLE_UPLOAD_ENDPOINT,
- FRONTEND_DIR,
- get_cloud_queue_type,
-)
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.core.work import LightningWork
-from lightning.app.storage import Drive
-from lightning.app.utilities.app_helpers import InMemoryStateStore, Logger, StateStore
-from lightning.app.utilities.app_status import AppStatus
-from lightning.app.utilities.cloud import is_running_in_cloud
-from lightning.app.utilities.component import _context
-from lightning.app.utilities.enum import ComponentContext, OpenAPITags
-
-# TODO: fixed uuid for now, it will come from the FastAPI session
-TEST_SESSION_UUID = "1234"
-
-STATE_EVENT = "State changed"
-
-frontend_static_dir = os.path.join(FRONTEND_DIR, "static")
-
-api_app_delta_queue: Optional[Queue] = None
-
-template: dict = {"ui": {}, "app": {}}
-templates = Jinja2Templates(directory=FRONTEND_DIR)
-
-# TODO: try to avoid using global var for state store
-global_app_state_store = InMemoryStateStore()
-global_app_state_store.add(TEST_SESSION_UUID)
-
-lock = Lock()
-
-app_spec: Optional[List] = None
-app_status: Optional[AppStatus] = None
-app_annotations: Optional[List] = None
-
-# In the future, this would be abstracted to support horizontal scaling.
-responses_store = {}
-
-logger = Logger(__name__)
-
-# This can be replaced with a consumer that publishes states in a kv-store
-# in a serverless architecture
-
-
-class UIRefresher(Thread):
- def __init__(
- self,
- api_publish_state_queue: Queue,
- api_response_queue: Queue,
- refresh_interval: float = 0.1,
- ) -> None:
- super().__init__(daemon=True)
- self.api_publish_state_queue = api_publish_state_queue
- self.api_response_queue = api_response_queue
- self._exit_event = Event()
- self.refresh_interval = refresh_interval
-
- def run(self) -> None:
- # TODO: Create multiple threads to handle the background logic
- # TODO: Investigate the use of `parallel=True`
- try:
- while not self._exit_event.is_set():
- self.run_once()
- # Note: Sleep to reduce queue calls.
- sleep(self.refresh_interval)
- except Exception as ex:
- traceback.print_exc()
- raise ex
-
- def run_once(self) -> None:
- with contextlib.suppress(queue.Empty):
- global app_status
- state, app_status = self.api_publish_state_queue.get(timeout=0)
- with lock:
- global_app_state_store.set_app_state(TEST_SESSION_UUID, state)
-
- with contextlib.suppress(queue.Empty):
- responses = self.api_response_queue.get(timeout=0)
- with lock:
- # TODO: Abstract the responses store to support horizontal scaling.
- global responses_store
- for response in responses:
- responses_store[response["id"]] = response["response"]
-
- def join(self, timeout: Optional[float] = None) -> None:
- self._exit_event.set()
- super().join(timeout)
-
-
-class StateUpdate(BaseModel):
- state: dict = {}
-
-
-openapi_tags = [
- {
- "name": OpenAPITags.APP_CLIENT_COMMAND,
- "description": "The App Endpoints to be triggered exclusively from the CLI",
- },
- {
- "name": OpenAPITags.APP_COMMAND,
- "description": "The App Endpoints that can be triggered equally from the CLI or from a Http Request",
- },
- {
- "name": OpenAPITags.APP_API,
- "description": "The App Endpoints that can be triggered exclusively from a Http Request",
- },
-]
-
-app = FastAPI(openapi_tags=openapi_tags)
-
-fastapi_service = FastAPI()
-
-fastapi_service.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-
-# General sequence is:
-# * an update is generated in the UI
-# * the value and the location in the state (or the whole state, easier)
-# is sent to the REST API along with the session UID
-# * the previous state is loaded from the cache, the delta is generated
-# * the previous state is set as set_state, the delta is provided as
-# delta
-# * the app applies the delta and runs the entry_fn, which eventually
-# leads to another state
-# * the new state is published through the API
-# * the UI is updated with the new value of the state
-# Before the above happens, we need to refactor App so that it doesn't
-# rely on timeouts, but on sequences of updates (and alignments between
-# ranks)
-@fastapi_service.get("/api/v1/state", response_class=JSONResponse)
-async def get_state(
- response: Response,
- x_lightning_type: Optional[str] = Header(None),
- x_lightning_session_uuid: Optional[str] = Header(None),
- x_lightning_session_id: Optional[str] = Header(None),
-) -> Mapping:
- if x_lightning_session_uuid is None:
- raise Exception("Missing X-Lightning-Session-UUID header")
- if x_lightning_session_id is None:
- raise Exception("Missing X-Lightning-Session-ID header")
-
- if not ENABLE_PULLING_STATE_ENDPOINT:
- response.status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- return {"status": "failure", "reason": "This endpoint is disabled."}
-
- with lock:
- x_lightning_session_uuid = TEST_SESSION_UUID
- state = global_app_state_store.get_app_state(x_lightning_session_uuid)
- global_app_state_store.set_served_state(x_lightning_session_uuid, state)
- return state
-
-
-def _get_component_by_name(component_name: str, state: dict) -> Union[LightningFlow, LightningWork]:
- child = state
- for child_name in component_name.split(".")[1:]:
- try:
- child = child["flows"][child_name]
- except KeyError:
- child = child["structures"][child_name]
-
- if isinstance(child["vars"]["_layout"], list):
- assert len(child["vars"]["_layout"]) == 1
- return child["vars"]["_layout"][0]["target"]
- return child["vars"]["_layout"]["target"]
-
-
-@fastapi_service.get("/api/v1/layout", response_class=JSONResponse)
-async def get_layout() -> str:
- with lock:
- x_lightning_session_uuid = TEST_SESSION_UUID
- state = global_app_state_store.get_app_state(x_lightning_session_uuid)
- global_app_state_store.set_served_state(x_lightning_session_uuid, state)
- layout = deepcopy(state["vars"]["_layout"])
- for la in layout:
- if la["content"].startswith("root."):
- la["content"] = _get_component_by_name(la["content"], state)
- return json.dumps(layout)
-
-
-@fastapi_service.get("/api/v1/spec", response_class=JSONResponse)
-async def get_spec(
- response: Response,
- x_lightning_session_uuid: Optional[str] = Header(None),
- x_lightning_session_id: Optional[str] = Header(None),
-) -> Union[List, Dict]:
- if x_lightning_session_uuid is None:
- raise Exception("Missing X-Lightning-Session-UUID header")
- if x_lightning_session_id is None:
- raise Exception("Missing X-Lightning-Session-ID header")
-
- if not ENABLE_PULLING_STATE_ENDPOINT:
- response.status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- return {"status": "failure", "reason": "This endpoint is disabled."}
-
- global app_spec
- return app_spec or []
-
-
-@fastapi_service.post("/api/v1/delta")
-async def post_delta(
- request: Request,
- response: Response,
- x_lightning_type: Optional[str] = Header(None),
- x_lightning_session_uuid: Optional[str] = Header(None),
- x_lightning_session_id: Optional[str] = Header(None),
-) -> Optional[Dict]:
- """This endpoint is used to make an update to the app state using delta diff, mainly used by streamlit to update
- the state."""
-
- if x_lightning_session_uuid is None:
- raise Exception("Missing X-Lightning-Session-UUID header")
- if x_lightning_session_id is None:
- raise Exception("Missing X-Lightning-Session-ID header")
-
- if not ENABLE_PUSHING_STATE_ENDPOINT:
- response.status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- return {"status": "failure", "reason": "This endpoint is disabled."}
-
- body: Dict = await request.json()
- assert api_app_delta_queue is not None
- api_app_delta_queue.put(_DeltaRequest(delta=Delta(body["delta"])))
- return None
-
-
-@fastapi_service.post("/api/v1/state")
-async def post_state(
- request: Request,
- response: Response,
- x_lightning_type: Optional[str] = Header(None),
- x_lightning_session_uuid: Optional[str] = Header(None),
- x_lightning_session_id: Optional[str] = Header(None),
-) -> Optional[Dict]:
- if x_lightning_session_uuid is None:
- raise Exception("Missing X-Lightning-Session-UUID header")
- if x_lightning_session_id is None:
- raise Exception("Missing X-Lightning-Session-ID header")
- # This needs to be sent so that it can be set as last state
- # in app (see sequencing above)
- # Actually: we need to make sure last_state is actually
- # the latest state seen by the UI, that is, the last state
- # ui to the UI from the API, not the last state
- # obtained by the app.
- body: Dict = await request.json()
- x_lightning_session_uuid = TEST_SESSION_UUID
-
- if not ENABLE_PUSHING_STATE_ENDPOINT:
- response.status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- return {"status": "failure", "reason": "This endpoint is disabled."}
-
- if "stage" in body:
- last_state = global_app_state_store.get_served_state(x_lightning_session_uuid)
- state = deepcopy(last_state)
- state["app_state"]["stage"] = body["stage"]
- deep_diff = DeepDiff(last_state, state, verbose_level=2)
- else:
- state = body["state"]
- last_state = global_app_state_store.get_served_state(x_lightning_session_uuid)
- deep_diff = DeepDiff(last_state, state, verbose_level=2)
- assert api_app_delta_queue is not None
- api_app_delta_queue.put(_DeltaRequest(delta=Delta(deep_diff)))
- return None
-
-
-@fastapi_service.put("/api/v1/upload_file/{filename}")
-async def upload_file(response: Response, filename: str, uploaded_file: UploadFile = File(...)) -> Union[str, dict]:
- if not ENABLE_UPLOAD_ENDPOINT:
- response.status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- return {"status": "failure", "reason": "This endpoint is disabled."}
-
- with TemporaryDirectory() as tmp:
- drive = Drive(
- "lit://uploaded_files",
- component_name="file_server",
- allow_duplicates=True,
- root_folder=tmp,
- )
- tmp_file = os.path.join(tmp, filename)
-
- with open(tmp_file, "wb") as f:
- done = False
- while not done:
- # Note: The 8192 number doesn't have a strong reason.
- content = await uploaded_file.read(8192)
- f.write(content)
- done = content == b""
-
- with _context(str(ComponentContext.WORK)):
- drive.put(filename)
- return f"Successfully uploaded '{filename}' to the Drive"
-
-
-@fastapi_service.get("/api/v1/status", response_model=AppStatus)
-async def get_status() -> AppStatus:
- """Get the current status of the app and works."""
- global app_status
- if app_status is None:
- raise HTTPException(
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="App status hasn't been reported yet."
- )
- return app_status
-
-
-@fastapi_service.get("/api/v1/annotations", response_class=JSONResponse)
-async def get_annotations() -> Union[List, Dict]:
- """Get the annotations associated with this app."""
- global app_annotations
- return app_annotations or []
-
-
-@fastapi_service.get("/healthz", status_code=200)
-async def healthz(response: Response) -> dict:
- """Health check endpoint used in the cloud FastAPI servers to check the status periodically."""
- # check the queue status only if running in cloud
- if is_running_in_cloud():
- queue_obj = QueuingSystem(get_cloud_queue_type()).get_queue(queue_name="healthz")
- # this is only being implemented on Redis Queue. For HTTP Queue, it doesn't make sense to have every single
- # app checking the status of the Queue server
- if not queue_obj.is_running:
- response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- return {"status": "failure", "reason": "Redis is not available"}
- x_lightning_session_uuid = TEST_SESSION_UUID
- state = global_app_state_store.get_app_state(x_lightning_session_uuid)
- global_app_state_store.set_served_state(x_lightning_session_uuid, state)
- if not state:
- response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- return {"status": "failure", "reason": f"State is empty {state}"}
- return {"status": "ok"}
-
-
-# Creates session websocket connection to notify client about any state changes
-# The websocket instance needs to be stored based on session id so it is accessible in the api layer
-@fastapi_service.websocket("/api/v1/ws")
-async def websocket_endpoint(websocket: WebSocket) -> None:
- await websocket.accept()
- if not ENABLE_STATE_WEBSOCKET:
- await websocket.close()
- return
- try:
- counter = global_app_state_store.counter
- while True:
- if global_app_state_store.counter != counter:
- await websocket.send_text(f"{global_app_state_store.counter}")
- counter = global_app_state_store.counter
- logger.debug("Updated websocket.")
- await asyncio.sleep(0.01)
- except ConnectionClosed:
- logger.debug("Websocket connection closed")
- await websocket.close()
-
-
-async def api_catch_all(request: Request, full_path: str) -> None:
- raise HTTPException(status_code=404, detail="Not found")
-
-
-# Serve frontend from a static directory using FastAPI
-fastapi_service.mount("/static", StaticFiles(directory=frontend_static_dir, check_dir=False), name="static")
-
-
-async def frontend_route(request: Request, full_path: str): # type: ignore[no-untyped-def]
- if "pytest" in sys.modules:
- return ""
- return templates.TemplateResponse("index.html", {"request": request})
-
-
-def register_global_routes() -> None:
- # Catch-all for nonexistent API routes (since we define a catch-all for client-side routing)
- fastapi_service.get("/api{full_path:path}", response_class=JSONResponse)(api_catch_all)
- fastapi_service.get("/{full_path:path}", response_class=HTMLResponse)(frontend_route)
-
-
-class LightningUvicornServer(uvicorn.Server):
- has_started_queue: Optional[Queue] = None
-
- def run(self, sockets: Optional[List[socket.socket]] = None) -> None:
- self.config.setup_event_loop()
- loop = asyncio.get_event_loop()
- asyncio.ensure_future(self.serve(sockets=sockets))
- if self.has_started_queue:
- asyncio.ensure_future(self.check_is_started(self.has_started_queue))
- loop.run_forever()
-
- async def check_is_started(self, queue: Queue) -> None:
- while not self.started:
- await asyncio.sleep(0.1)
- queue.put("SERVER_HAS_STARTED")
-
-
-def start_server(
- api_publish_state_queue: Queue,
- api_delta_queue: Queue,
- api_response_queue: Queue,
- has_started_queue: Optional[Queue] = None,
- host: str = "127.0.0.1",
- port: int = 8000,
- root_path: str = "",
- uvicorn_run: bool = True,
- spec: Optional[List] = None,
- apis: Optional[List[_HttpMethod]] = None,
- app_state_store: Optional[StateStore] = None,
-) -> UIRefresher:
- global api_app_delta_queue
- global global_app_state_store
- global app_spec
- global app_annotations
-
- app_spec = spec
- api_app_delta_queue = api_delta_queue
-
- if app_state_store is not None:
- global_app_state_store = app_state_store # type: ignore[assignment]
-
- global_app_state_store.add(TEST_SESSION_UUID)
-
- # Load annotations
- annotations_path = Path("lightning-annotations.json").resolve()
- if annotations_path.exists():
- with open(annotations_path) as f:
- app_annotations = json.load(f)
-
- refresher = UIRefresher(api_publish_state_queue, api_response_queue)
- refresher.setDaemon(True)
- refresher.start()
-
- if uvicorn_run:
- host = host.split("//")[-1] if "//" in host else host
- if host == "0.0.0.0": # noqa: S104
- logger.info("Your app has started.")
- else:
- logger.info(f"Your app has started. View it in your browser: http://{host}:{port}/view")
- if has_started_queue:
- LightningUvicornServer.has_started_queue = has_started_queue
- # uvicorn is doing some uglyness by replacing uvicorn.main by click command.
- sys.modules["uvicorn.main"].Server = LightningUvicornServer
-
- # Register the user API.
- if apis:
- for api in apis:
- api.add_route(fastapi_service, api_app_delta_queue, responses_store)
-
- register_global_routes()
-
- uvicorn.run(app=fastapi_service, host=host, port=port, log_level="error", root_path=root_path)
-
- return refresher
diff --git a/src/lightning/app/core/app.py b/src/lightning/app/core/app.py
deleted file mode 100644
index e1da6adee32ba..0000000000000
--- a/src/lightning/app/core/app.py
+++ /dev/null
@@ -1,746 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import logging
-import os
-import pickle
-import queue
-import threading
-import warnings
-from copy import deepcopy
-from time import time
-from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
-
-from deepdiff import DeepDiff, Delta
-from lightning_utilities.core.apply_func import apply_to_collection
-
-import lightning.app
-from lightning.app import _console
-from lightning.app.api.request_types import _APIRequest, _CommandRequest, _DeltaRequest
-from lightning.app.core.constants import (
- BATCH_DELTA_COUNT,
- CHECK_ERROR_QUEUE_INTERVAL,
- DEBUG_ENABLED,
- FLOW_DURATION_SAMPLES,
- FLOW_DURATION_THRESHOLD,
- FRONTEND_DIR,
- SHOULD_START_WORKS_WITH_FLOW,
- STATE_ACCUMULATE_WAIT,
-)
-from lightning.app.core.queues import BaseQueue
-from lightning.app.core.work import LightningWork
-from lightning.app.frontend import Frontend
-from lightning.app.storage import Drive, Path, Payload
-from lightning.app.storage.path import _storage_root_dir
-from lightning.app.utilities import frontend
-from lightning.app.utilities.app_helpers import (
- Logger,
- _delta_to_app_state_delta,
- _LightningAppRef,
- _should_dispatch_app,
-)
-from lightning.app.utilities.app_status import AppStatus
-from lightning.app.utilities.commands.base import _process_requests
-from lightning.app.utilities.component import _convert_paths_after_init, _validate_root_flow
-from lightning.app.utilities.enum import AppStage, CacheCallsKeys
-from lightning.app.utilities.exceptions import CacheMissException, ExitAppException, LightningFlowException
-from lightning.app.utilities.layout import _collect_layout
-from lightning.app.utilities.proxies import ComponentDelta
-from lightning.app.utilities.scheduler import SchedulerThread
-from lightning.app.utilities.tree import breadth_first
-from lightning.app.utilities.warnings import LightningFlowWarning
-
-if TYPE_CHECKING:
- from lightning.app.core.flow import LightningFlow
- from lightning.app.runners.backends.backend import Backend, WorkManager
- from lightning.app.runners.runtime import Runtime
- from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-
-logger = Logger(__name__)
-
-
-class LightningApp:
- def __init__(
- self,
- root: Union["LightningFlow", LightningWork],
- flow_cloud_compute: Optional["CloudCompute"] = None,
- log_level: str = "info",
- info: Optional[frontend.AppInfo] = None,
- root_path: str = "",
- ) -> None:
- """The Lightning App, or App in short runs a tree of one or more components that interact to create end-to-end
- applications. There are two kinds of components: :class:`~lightning.app.core.flow.LightningFlow` and
- :class:`~lightning.app.core.work.LightningWork`. This modular design enables you to reuse components created by
- other users.
-
- The Lightning App alternatively run an event loop triggered by delta changes sent from
- either :class:`~lightning.app.core.work.LightningWork` or from the Lightning UI.
- Once deltas are received, the Lightning App runs
- the :class:`~lightning.app.core.flow.LightningFlow` provided.
-
- Arguments:
- root: The root ``LightningFlow`` or ``LightningWork`` component, that defines all the app's nested
- components, running infinitely. It must define a `run()` method that the app can call.
- flow_cloud_compute: The default Cloud Compute used for flow, Rest API and frontend's.
- log_level: The log level for the app, one of [`info`, `debug`].
- This can be helpful when reporting bugs on Lightning repo.
- info: Provide additional info about the app which will be used to update html title,
- description and image meta tags and specify any additional tags as list of html strings.
- root_path: Set this to `/path` if you want to run your app behind a proxy at `/path` leave empty for "/".
- For instance, if you want to run your app at `https://customdomain.com/myapp`,
- set `root_path` to `/myapp`.
- You can learn more about proxy `here `_.
-
- """
-
- self.root_path = root_path # when running behind a proxy
- self.info = info
-
- from lightning.app.core.flow import _RootFlow
-
- if isinstance(root, LightningWork):
- root = _RootFlow(root)
-
- _validate_root_flow(root)
- self._root = root
- self.flow_cloud_compute = flow_cloud_compute or lightning.app.CloudCompute(name="flow-lite")
-
- # queues definition.
- self.delta_queue: Optional[BaseQueue] = None
- self.readiness_queue: Optional[BaseQueue] = None
- self.api_response_queue: Optional[BaseQueue] = None
- self.api_publish_state_queue: Optional[BaseQueue] = None
- self.api_delta_queue: Optional[BaseQueue] = None
- self.error_queue: Optional[BaseQueue] = None
- self.request_queues: Optional[Dict[str, BaseQueue]] = None
- self.response_queues: Optional[Dict[str, BaseQueue]] = None
- self.copy_request_queues: Optional[Dict[str, BaseQueue]] = None
- self.copy_response_queues: Optional[Dict[str, BaseQueue]] = None
- self.caller_queues: Optional[Dict[str, BaseQueue]] = None
- self.flow_to_work_delta_queues: Optional[Dict[str, BaseQueue]] = None
- self.work_queues: Optional[Dict[str, BaseQueue]] = None
- self.commands: Optional[List] = None
-
- self.should_publish_changes_to_api = False
- self.component_affiliation = None
- self.backend: Optional["Backend"] = None
- _LightningAppRef.connect(self)
- self.processes: Dict[str, "WorkManager"] = {}
- self.frontends: Dict[str, Frontend] = {}
- self.stage = AppStage.RUNNING
- self._has_updated: bool = True
- self._schedules: Dict[str, Dict] = {}
- self.threads: List[threading.Thread] = []
- self.exception = None
- self.collect_changes: bool = True
- self._should_start_works_with_flow: bool = SHOULD_START_WORKS_WITH_FLOW
-
- self.status: Optional[AppStatus] = None
- # TODO: Enable ready locally for opening the UI.
- self.ready = False
-
- # NOTE: Checkpointing is disabled by default for the time being. We
- # will enable it when resuming from full checkpoint is supported. Also,
- # we will need to revisit the logic at _should_snapshot, since right now
- # we are writing checkpoints too often, and this is expensive.
- self.checkpointing: bool = False
-
- self._update_layout()
- self._update_status()
-
- self.is_headless: Optional[bool] = None
-
- self._original_state: Optional[dict] = None
- self._last_state: dict = self.state
- self.state_accumulate_wait = STATE_ACCUMULATE_WAIT
-
- self._last_run_time: float = 0.0
- self._run_times: list = []
- self._last_check_error_queue: float = 0.0
-
- # Path attributes can't get properly attached during the initialization, because the full name
- # is only available after all Flows and Works have been instantiated.
- _convert_paths_after_init(self.root) # type: ignore[arg-type]
-
- if log_level not in ("debug", "info"):
- raise Exception(f"Log Level should be in ['debug', 'info']. Found {log_level}")
-
- # Lazily enable debugging.
- if log_level == "debug" or DEBUG_ENABLED:
- if not DEBUG_ENABLED:
- os.environ["LIGHTNING_DEBUG"] = "2"
- _console.setLevel(logging.DEBUG)
-
- logger.debug(f"ENV: {os.environ}")
-
- if _should_dispatch_app():
- os.environ["LIGHTNING_DISPATCHED"] = "1"
- from lightning.app.runners import MultiProcessRuntime
-
- MultiProcessRuntime(self).dispatch()
-
- def _update_index_file(self) -> None:
- # update index.html,
- # this should happen once for all apps before the ui server starts running.
- frontend.update_index_file(FRONTEND_DIR, info=self.info, root_path=self.root_path)
-
- def get_component_by_name(self, component_name: str) -> Union["LightningFlow", LightningWork]:
- """Returns the instance corresponding to the given component name."""
- from lightning.app.structures import Dict as LightningDict
- from lightning.app.structures import List as LightningList
- from lightning.app.utilities.types import ComponentTuple
-
- if component_name == "root":
- return self.root
- if not component_name.startswith("root."):
- raise ValueError(f"Invalid component name {component_name}. Name must start with 'root'")
-
- current = self.root
- for child_name in component_name.split(".")[1:]:
- if isinstance(current, LightningDict):
- child = current[child_name]
- elif isinstance(current, LightningList):
- child = current[int(child_name)]
- else:
- child = getattr(current, child_name, None)
- if not isinstance(child, ComponentTuple):
- raise AttributeError(f"Component '{current.name}' has no child component with name '{child_name}'.")
- current = child # type: ignore[assignment]
- return current
-
- def _reset_original_state(self) -> None:
- assert self._original_state is not None
- self.set_state(self._original_state)
-
- @property
- def root(self) -> Union["LightningFlow", LightningWork]:
- """Returns the root component of the application."""
- return self._root
-
- @property
- def state(self) -> dict:
- """Return the current state of the application."""
- state = self.root.state
- state["app_state"] = {"stage": self.stage.value}
- return state
-
- @property
- def state_vars(self) -> dict:
- """Return the current state restricted to the user defined variables of the application."""
- state_vars = self.root.state_vars
- state_vars["app_state"] = {"stage": self.stage.value}
- return state_vars
-
- @property
- def state_with_changes(self) -> dict:
- """Return the current state with the new changes of the application."""
- state_with_changes = self.root.state_with_changes
- state_with_changes["app_state"] = {"stage": self.stage.value}
- return state_with_changes
-
- def set_state(self, state: dict) -> None:
- """Method to set a new app state set to the application."""
- self.set_last_state(state)
- self.root.set_state(state)
- self.stage = AppStage(state["app_state"]["stage"])
-
- @property
- def last_state(self) -> dict:
- """Returns the latest state."""
- return self._last_state
-
- @property
- def checkpoint_dir(self) -> str:
- return os.path.join(str(_storage_root_dir()), "checkpoints")
-
- def remove_changes_(self, state: dict) -> None:
- for _, child in state["flows"].items():
- self.remove_changes(child)
- state["changes"] = {}
-
- def remove_changes(self, state: dict) -> dict:
- state = deepcopy(state)
- for _, child in state["flows"].items():
- self.remove_changes_(child)
- state["changes"] = {}
- return state
-
- def set_last_state(self, state: dict) -> None:
- self._last_state = self.remove_changes(state)
-
- @staticmethod
- def populate_changes(last_state: dict, new_state: dict) -> dict:
- diff = DeepDiff(last_state, new_state, view="tree", verbose_level=2)
-
- changes_categories = [diff[key] for key in diff.to_dict()]
-
- if not changes_categories:
- return new_state
-
- for change_category in changes_categories:
- for entry in change_category:
- state_el = new_state
- change = entry.path(output_format="list")
- if "vars" not in change:
- continue
- for change_el in change:
- if change_el == "vars":
- if "changes" not in state_el:
- state_el["changes"] = {}
- state_el["changes"][change[-1]] = {"from": entry.t1, "to": entry.t2}
- break
- # move down in the dictionary
- state_el = state_el[change_el]
- return new_state
-
- @staticmethod
- def get_state_changed_from_queue(q: BaseQueue, timeout: Optional[float] = None) -> Optional[dict]:
- try:
- timeout = timeout or q.default_timeout
- return q.get(timeout=timeout)
- except queue.Empty:
- return None
-
- @staticmethod
- def batch_get_state_changed_from_queue(q: BaseQueue, timeout: Optional[float] = None) -> List[dict]:
- try:
- timeout = timeout or q.default_timeout
- return q.batch_get(timeout=timeout, count=BATCH_DELTA_COUNT)
- except queue.Empty:
- return []
-
- def check_error_queue(self) -> None:
- if (time() - self._last_check_error_queue) > CHECK_ERROR_QUEUE_INTERVAL:
- exception: Exception = self.get_state_changed_from_queue(self.error_queue) # type: ignore[assignment,arg-type]
- if isinstance(exception, Exception):
- self.exception = exception
- self.stage = AppStage.FAILED
- self._last_check_error_queue = time()
-
- @property
- def flows(self) -> List[Union[LightningWork, "LightningFlow"]]:
- """Returns all the flows defined within this application."""
- return [self.root] + list(self.root.flows.values())
-
- @property
- def works(self) -> List[LightningWork]:
- """Returns all the works defined within this application."""
- return self.root.works(recurse=True)
-
- @property
- def named_works(self) -> List[Tuple[str, LightningWork]]:
- """Returns all the works defined within this application with their names."""
- return self.root.named_works(recurse=True)
-
- def _collect_deltas_from_ui_and_work_queues(self) -> List[Union[Delta, _APIRequest, _CommandRequest]]:
- # The aggregation would try to get as many deltas as possible
- # from both the `api_delta_queue` and `delta_queue`
- # during the `state_accumulate_wait` time.
- # The while loop can exit sooner if both queues are empty.
-
- deltas = []
- api_or_command_request_deltas = []
- t0 = time()
-
- while (time() - t0) < self.state_accumulate_wait:
- # TODO: Fetch all available deltas at once to reduce queue calls.
- received_deltas: List[Union[_DeltaRequest, _APIRequest, _CommandRequest, ComponentDelta]] = (
- self.batch_get_state_changed_from_queue(
- self.delta_queue # type: ignore[assignment,arg-type]
- )
- )
- if len(received_deltas) == []:
- break
-
- for delta in received_deltas:
- if isinstance(delta, _DeltaRequest):
- deltas.append(delta.delta)
- elif isinstance(delta, ComponentDelta):
- logger.debug(f"Received from {delta.id} : {delta.delta.to_dict()}")
- work = None
- try:
- work = self.get_component_by_name(delta.id)
- except (KeyError, AttributeError) as ex:
- logger.error(f"The component {delta.id} couldn't be accessed. Exception: {ex}")
-
- if work:
- delta = _delta_to_app_state_delta(
- self.root, # type: ignore[arg-type]
- work,
- deepcopy(delta.delta),
- )
- deltas.append(delta)
- else:
- api_or_command_request_deltas.append(delta)
-
- if api_or_command_request_deltas:
- _process_requests(self, api_or_command_request_deltas)
-
- for delta in deltas:
- # When aggregating deltas from the UI and the Works, and over the accumulation time window,
- # it can happen that deltas from these different sources disagree. Since deltas are computed on the Work
- # and UI side separately, correctness of the aggregation can only be guaranteed if both components compute
- # the delta based on the same base state. But this assumption does not hold in general, and there is no way
- # for the Flow to reject or resolve these deltas properly at the moment. Hence, we decide to ignore
- # errors coming from deepdiff when adding deltas together by setting:
- delta.log_errors = False # type: ignore[union-attr]
- delta.raise_errors = False # type: ignore[union-attr]
- return deltas
-
- def maybe_apply_changes(self) -> Optional[bool]:
- """Get the deltas from both the flow queue and the work queue, merge the two deltas and update the state."""
- self._send_flow_to_work_deltas(self.state)
-
- if not self.collect_changes:
- return None
-
- deltas = self._collect_deltas_from_ui_and_work_queues()
-
- if not deltas:
- # Path and Drive aren't processed by DeepDiff, so we need to convert them to dict.
- last_state = apply_to_collection(self.last_state, (Path, Drive), lambda x: x.to_dict())
- state = apply_to_collection(self.state, (Path, Drive), lambda x: x.to_dict())
- # When no deltas are received from the Rest API or work queues,
- # we need to check if the flow modified the state and populate changes.
- deep_diff = DeepDiff(last_state, state, verbose_level=2)
-
- if "unprocessed" in deep_diff:
- # pop the unprocessed key.
- unprocessed = deep_diff.pop("unprocessed")
- logger.warn(f"It seems delta differentiation resulted in {unprocessed}. Open an issue on Github.")
-
- if deep_diff:
- # TODO: Resolve changes with ``CacheMissException``.
- # new_state = self.populate_changes(self.last_state, self.state)
- self.set_last_state(self.state)
- self._has_updated = True
- return False
-
- logger.debug(f"Received {[d.to_dict() for d in deltas]}")
-
- # 2: Collect the state
- state = self.state
-
- # 3: Apply the state delta
- for delta in deltas:
- try:
- state += delta
- except Exception as ex:
- raise Exception(f"Current State {state}, {delta.to_dict()}") from ex
-
- # new_state = self.populate_changes(self.last_state, state)
- self.set_state(state)
- self._has_updated = True
- return None
-
- def run_once(self) -> bool:
- """Method used to collect changes and run the root Flow once."""
- done = False
- self._last_run_time = 0.0
-
- if self.backend is not None:
- self.backend.update_work_statuses(self.works)
-
- self._update_layout()
- self._update_status()
- self.maybe_apply_changes()
-
- if self.checkpointing and self._should_snapshot():
- self._dump_checkpoint()
-
- if self.stage == AppStage.BLOCKING:
- return done
-
- if self.stage in (AppStage.STOPPING, AppStage.FAILED):
- return True
-
- if self.stage == AppStage.RESTARTING:
- return self._apply_restarting()
-
- t0 = time()
-
- try:
- self.check_error_queue()
- # Execute the flow only if:
- # - There are state changes
- # - It is the first execution of the flow
- if self._has_updated:
- self.root.run()
- except CacheMissException:
- self._on_cache_miss_exception()
- except LightningFlowException:
- done = True
- self.stage = AppStage.FAILED
- except (ExitAppException, KeyboardInterrupt):
- done = True
- self.stage = AppStage.STOPPING
-
- if not self.ready:
- self.ready = self.root.ready
-
- self._last_run_time = time() - t0
-
- self.on_run_once_end()
- return done
-
- def _reset_run_time_monitor(self) -> None:
- self._run_times = [0.0] * FLOW_DURATION_SAMPLES
-
- def _update_run_time_monitor(self) -> None:
- self._run_times[:-1] = self._run_times[1:]
- self._run_times[-1] = self._last_run_time
-
- # Here we underestimate during the first FLOW_DURATION_SAMPLES
- # iterations, but that's ok for our purposes
- avg_elapsed_time = sum(self._run_times) / FLOW_DURATION_SAMPLES
-
- if avg_elapsed_time > FLOW_DURATION_THRESHOLD:
- warnings.warn(
- "The execution of the `run` method of the root flow is taking too long. "
- "Flow is supposed to only host coordination logic, while currently it is"
- "likely to contain long-running calls, code that performs meaningful "
- "computations or that makes blocking or asynchronous calls to third-party "
- "services. If that is the case, you should move those pieces to a Work, "
- "and make sure Flow can complete its execution in under a second.",
- LightningFlowWarning,
- )
-
- def _run(self) -> bool:
- """Entry point of the LightningApp.
-
- This would be dispatched by the Runtime objects.
-
- """
- self._original_state = deepcopy(self.state)
- done = False
-
- self.ready = self.root.ready
-
- self._start_with_flow_works()
-
- if self.should_publish_changes_to_api and self.api_publish_state_queue is not None:
- self.api_publish_state_queue.put((self.state_vars, self.status))
-
- self._reset_run_time_monitor()
-
- while not done:
- done = self.run_once()
-
- self._update_run_time_monitor()
-
- if self._has_updated and self.should_publish_changes_to_api and self.api_publish_state_queue is not None:
- self.api_publish_state_queue.put((self.state_vars, self.status))
-
- self._has_updated = False
-
- self._on_run_end()
-
- return True
-
- def _update_layout(self) -> None:
- if self.backend:
- self.backend.resolve_url(self, base_url=None)
-
- for component in breadth_first(self.root, types=(lightning.app.LightningFlow,)): # type: ignore[arg-type]
- layout = _collect_layout(self, component)
- component._layout = layout
-
- def _update_status(self) -> None:
- old_status = self.status
-
- work_statuses = {}
- assert self.root is not None
- for work in breadth_first(self.root, types=(lightning.app.LightningWork,)): # type: ignore[arg-type]
- work_statuses[work.name] = work.status
-
- self.status = AppStatus(
- is_ui_ready=self.ready,
- work_statuses=work_statuses,
- )
-
- # If the work statuses changed, the state delta will trigger an update.
- # If ready has changed, we trigger an update manually.
- if self.status != old_status:
- self._has_updated = True
-
- def _apply_restarting(self) -> bool:
- self._reset_original_state()
- # apply stage after restoring the original state.
- self.stage = AppStage.BLOCKING
- return False
-
- def _has_work_finished(self, work: LightningWork) -> bool:
- latest_call_hash = work._calls[CacheCallsKeys.LATEST_CALL_HASH]
- if latest_call_hash is None:
- return False
- return "ret" in work._calls[latest_call_hash]
-
- def _collect_work_finish_status(self) -> dict:
- work_finished_status = {work.name: self._has_work_finished(work) for work in self.works}
- assert len(work_finished_status) == len(self.works)
- return work_finished_status
-
- def _should_snapshot(self) -> bool:
- if len(self.works) == 0:
- return True
- if self._has_updated:
- work_finished_status = self._collect_work_finish_status()
- if work_finished_status:
- return all(work_finished_status.values())
- return True
- return False
-
- def state_dict(self) -> Dict:
- return self.state
-
- def load_state_dict(self, state: Dict) -> None:
- self.set_state(state)
-
- def load_state_dict_from_checkpoint_dir(
- self,
- checkpoints_dir: str,
- version: Optional[int] = None,
- ) -> None:
- if not os.path.exists(checkpoints_dir):
- raise FileNotFoundError(f"The provided directory `{checkpoints_dir}` doesn't exist.")
- checkpoints = [f for f in os.listdir(checkpoints_dir) if f.startswith("v_") and f.endswith(".json")]
- if not checkpoints:
- raise Exception(f"No checkpoints where found in `{checkpoints_dir}`.")
-
- if version is None:
- # take the latest checkpoint.
- version = sorted(int(c.split("_")[1]) for c in checkpoints)[-1]
-
- available_checkpoints = [c for c in checkpoints if c.startswith(f"v_{version}_")]
- if not available_checkpoints:
- raise FileNotFoundError(f"The version `{version}` wasn't found in {checkpoints}.")
- if len(available_checkpoints) > 1:
- raise Exception(f"Found 2 checkpoints `{available_checkpoints}`with the same version.")
- checkpoint_path = os.path.join(checkpoints_dir, available_checkpoints[0])
- with open(checkpoint_path, "rb") as fo:
- state = pickle.load(fo)
- self.load_state_dict(state)
-
- def _dump_checkpoint(self) -> Optional[str]:
- checkpoints_dir = self.checkpoint_dir
- # TODO: Add supports to remotely saving checkpoints.
- if checkpoints_dir.startswith("s3:"):
- return None
- os.makedirs(checkpoints_dir, exist_ok=True)
-
- # Get all current version within the provided folder and sort them
- checkpoint_versions = sorted(
- int(f.split("_")[1]) for f in os.listdir(checkpoints_dir) if f.startswith("v_") and f.endswith(".json")
- )
-
- previous_version = checkpoint_versions[-1] if checkpoint_versions else -1
-
- checkpoint_path = os.path.join(checkpoints_dir, f"v_{previous_version + 1}_{time()}.json")
-
- with open(checkpoint_path, "wb") as f:
- pickle.dump(self.state_dict(), f)
- return checkpoint_path
-
- def connect(self, runtime: "Runtime") -> None:
- """Override to customize your application to the runtime."""
- pass
-
- def _on_cache_miss_exception(self) -> None:
- if self._has_updated:
- self._update_layout()
-
- def _register_schedule(self, schedule_hash: str, schedule_metadata: Dict) -> None:
- # create a thread only if a user uses the flow's schedule method.
- if not self._schedules:
- scheduler_thread = SchedulerThread(self)
- scheduler_thread.setDaemon(True)
- self.threads.append(scheduler_thread)
- self.threads[-1].start()
- self._schedules[schedule_hash] = deepcopy(schedule_metadata)
-
- def on_run_once_end(self) -> None:
- if not self._schedules:
- return
- # disable any flow schedules.
- for flow in self.flows:
- flow._disable_running_schedules()
-
- def _on_run_end(self) -> None:
- if os.getenv("LIGHTNING_DEBUG") == "2":
- del os.environ["LIGHTNING_DEBUG"]
- _console.setLevel(logging.INFO)
-
- @staticmethod
- def _extract_vars_from_component_name(component_name: str, state: dict) -> Optional[dict]:
- child = state
- for child_name in component_name.split(".")[1:]:
- if child_name in child["flows"]:
- child = child["flows"][child_name]
- elif "structures" in child and child_name in child["structures"]:
- child = child["structures"][child_name]
- elif child_name in child["works"]:
- child = child["works"][child_name]
- else:
- return None
-
- # Filter private keys and drives
- return {
- k: v
- for k, v in child["vars"].items()
- if (
- not k.startswith("_")
- and not (isinstance(v, dict) and v.get("type", None) == "__drive__")
- and not (isinstance(v, (Payload, Path)))
- )
- }
-
- def _send_flow_to_work_deltas(self, state: dict) -> None:
- if not self.flow_to_work_delta_queues:
- return
-
- for w in self.works:
- if not w.has_started:
- continue
-
- # Don't send changes when the state has been just sent.
- if w.run.has_sent:
- continue
-
- state_work = self._extract_vars_from_component_name(w.name, state)
- last_state_work = self._extract_vars_from_component_name(w.name, self._last_state)
-
- # Note: The work was dynamically created or deleted.
- if state_work is None or last_state_work is None:
- continue
-
- deep_diff = DeepDiff(last_state_work, state_work, verbose_level=2).to_dict()
-
- if "unprocessed" in deep_diff:
- deep_diff.pop("unprocessed")
-
- if deep_diff:
- logger.debug(f"Sending deep_diff to {w.name} : {deep_diff}")
- self.flow_to_work_delta_queues[w.name].put(deep_diff)
-
- def _start_with_flow_works(self) -> None:
- if not self._should_start_works_with_flow:
- return
-
- for w in self.works:
- if w._start_with_flow:
- parallel = w.parallel
- w._parallel = True
- w.start()
- w._parallel = parallel
diff --git a/src/lightning/app/core/constants.py b/src/lightning/app/core/constants.py
deleted file mode 100644
index caf6996857417..0000000000000
--- a/src/lightning/app/core/constants.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import json
-import os
-from pathlib import Path
-from typing import Any, Optional
-
-import lightning_cloud.env
-
-
-def get_lightning_cloud_url() -> str:
- # detect local development
- if os.getenv("VSCODE_PROXY_URI", "").startswith("http://localhost:9800"):
- return "http://localhost:9800"
- # DO NOT CHANGE!
- return os.getenv("LIGHTNING_CLOUD_URL", "https://lightning.ai")
-
-
-SUPPORTED_PRIMITIVE_TYPES = (type(None), str, int, float, bool)
-STATE_UPDATE_TIMEOUT = 0.001
-STATE_ACCUMULATE_WAIT = 0.15
-# Duration in seconds of a moving average of a full flow execution
-# beyond which an exception is raised.
-FLOW_DURATION_THRESHOLD = 1.0
-# Number of samples for the moving average of the duration of flow execution
-FLOW_DURATION_SAMPLES = 5
-
-APP_SERVER_HOST = os.getenv("LIGHTNING_APP_STATE_URL", "http://127.0.0.1")
-APP_SERVER_IN_CLOUD = "http://lightningapp" in APP_SERVER_HOST
-APP_SERVER_PORT = 7501
-APP_STATE_MAX_SIZE_BYTES = 1024 * 1024 # 1 MB
-
-WARNING_QUEUE_SIZE = 1000
-# different flag because queue debug can be very noisy, and almost always not useful unless debugging the queue itself.
-QUEUE_DEBUG_ENABLED = bool(int(os.getenv("LIGHTNING_QUEUE_DEBUG_ENABLED", "0")))
-
-REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
-REDIS_PORT = int(os.getenv("REDIS_PORT", 6379))
-REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", None)
-REDIS_QUEUES_READ_DEFAULT_TIMEOUT = 0.005
-
-HTTP_QUEUE_URL = os.getenv("LIGHTNING_HTTP_QUEUE_URL", "http://localhost:9801")
-HTTP_QUEUE_REFRESH_INTERVAL = float(os.getenv("LIGHTNING_HTTP_QUEUE_REFRESH_INTERVAL", "1"))
-HTTP_QUEUE_TOKEN = os.getenv("LIGHTNING_HTTP_QUEUE_TOKEN", None)
-HTTP_QUEUE_REQUESTS_PER_SECOND = float(os.getenv("LIGHTNING_HTTP_QUEUE_REQUESTS_PER_SECOND", "0.5"))
-
-USER_ID = os.getenv("USER_ID", "1234")
-FRONTEND_DIR = str(Path(__file__).parent.parent / "ui")
-PACKAGE_LIGHTNING = os.getenv("PACKAGE_LIGHTNING", None)
-CLOUD_UPLOAD_WARNING = int(os.getenv("CLOUD_UPLOAD_WARNING", "2"))
-DISABLE_DEPENDENCY_CACHE = bool(int(os.getenv("DISABLE_DEPENDENCY_CACHE", "0")))
-# Project under which the resources need to run in cloud. If this env is not set,
-# cloud runner will try to get the default project from the cloud
-LIGHTNING_CLOUD_PROJECT_ID = os.getenv("LIGHTNING_CLOUD_PROJECT_ID")
-LIGHTNING_CLOUD_PRINT_SPECS = os.getenv("LIGHTNING_CLOUD_PRINT_SPECS")
-LIGHTNING_DIR = os.getenv("LIGHTNING_DIR", str(Path.home() / ".lightning"))
-LIGHTNING_CREDENTIAL_PATH = os.getenv("LIGHTNING_CREDENTIAL_PATH", str(Path(LIGHTNING_DIR) / "credentials.json"))
-DOT_IGNORE_FILENAME = ".lightningignore"
-LIGHTNING_COMPONENT_PUBLIC_REGISTRY = "https://lightning.ai/v1/components"
-LIGHTNING_APPS_PUBLIC_REGISTRY = "https://lightning.ai/v1/apps"
-LIGHTNING_MODELS_PUBLIC_REGISTRY = "https://lightning.ai/v1/models"
-ENABLE_ORCHESTRATOR = bool(int(os.getenv("ENABLE_ORCHESTRATOR", "1")))
-
-LIGHTNING_CLOUDSPACE_HOST = os.getenv("LIGHTNING_CLOUDSPACE_HOST")
-LIGHTNING_CLOUDSPACE_EXPOSED_PORT_COUNT = int(os.getenv("LIGHTNING_CLOUDSPACE_EXPOSED_PORT_COUNT", "0"))
-
-# EXPERIMENTAL: ENV VARIABLES TO ENABLE MULTIPLE WORKS IN THE SAME MACHINE
-DEFAULT_NUMBER_OF_EXPOSED_PORTS = int(os.getenv("DEFAULT_NUMBER_OF_EXPOSED_PORTS", "50"))
-ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER = bool(
- int(os.getenv("ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER", "0"))
-) # This isn't used in the cloud yet.
-
-# env var trigger running setup commands in the app
-ENABLE_APP_COMMENT_COMMAND_EXECUTION = bool(int(os.getenv("ENABLE_APP_COMMENT_COMMAND_EXECUTION", "0")))
-
-
-DEBUG: bool = lightning_cloud.env.DEBUG
-DEBUG_ENABLED = bool(int(os.getenv("LIGHTNING_DEBUG", "0")))
-ENABLE_PULLING_STATE_ENDPOINT = bool(int(os.getenv("ENABLE_PULLING_STATE_ENDPOINT", "1")))
-ENABLE_PUSHING_STATE_ENDPOINT = ENABLE_PULLING_STATE_ENDPOINT and bool(
- int(os.getenv("ENABLE_PUSHING_STATE_ENDPOINT", "1"))
-)
-ENABLE_STATE_WEBSOCKET = bool(int(os.getenv("ENABLE_STATE_WEBSOCKET", "1")))
-ENABLE_UPLOAD_ENDPOINT = bool(int(os.getenv("ENABLE_UPLOAD_ENDPOINT", "1")))
-
-# directory where system customization sync files stored
-SYS_CUSTOMIZATIONS_SYNC_ROOT = "/tmp/sys-customizations-sync" # todo
-# directory where system customization sync files will be copied to be packed into app tarball
-SYS_CUSTOMIZATIONS_SYNC_PATH = ".sys-customizations-sync"
-
-BATCH_DELTA_COUNT = int(os.getenv("BATCH_DELTA_COUNT", "128"))
-CHECK_ERROR_QUEUE_INTERVAL = float(os.getenv("CHECK_ERROR_QUEUE_INTERVAL", "30"))
-SHOULD_START_WORKS_WITH_FLOW = bool(int(os.getenv("SHOULD_START_WORKS_WITH_FLOW", "1")))
-IS_RUNNING_IN_FLOW = os.getenv("LIGHTNING_CLOUD_WORK_NAME", None) is None
-
-
-class DistributedPluginChecker:
- def __init__(self) -> None:
- self.distributed_arguments = os.getenv("DISTRIBUTED_ARGUMENTS", None)
- if self.distributed_arguments:
- self.distributed_arguments = json.loads(self.distributed_arguments)
-
- self.running_distributed_plugin = False
-
- if self.distributed_arguments and os.getenv("LIGHTNING_CLOUD_WORK_NAME"):
- self.running_distributed_plugin = True
-
- def __bool__(self) -> bool:
- return self.running_distributed_plugin
-
- def should_create_work(self, work: Any) -> bool:
- if not self.distributed_arguments:
- return True
-
- num_nodes = self.distributed_arguments.get("num_instances", 0)
- node_rank = int(work.name.split(".")[-1])
-
- # Only the start with flow works are skipped for performance purposes
- return node_rank >= num_nodes
-
-
-# TODO (tchaton): Add LitData and JobPlugin optimizations
-PLUGIN_CHECKER = IS_DISTRIBUTED_PLUGIN = DistributedPluginChecker()
-
-
-def enable_multiple_works_in_default_container() -> bool:
- return bool(int(os.getenv("ENABLE_MULTIPLE_WORKS_IN_DEFAULT_CONTAINER", "0")))
-
-
-def get_cloud_queue_type() -> Optional[str]:
- value = os.getenv("LIGHTNING_CLOUD_QUEUE_TYPE", None)
- if value is None and enable_interruptible_works():
- value = "http"
- return value
-
-
-# Number of seconds to wait between filesystem checks when waiting for files in remote storage
-REMOTE_STORAGE_WAIT = 0.5
-
-
-# interruptible support
-def enable_interruptible_works() -> bool:
- return bool(int(os.getenv("LIGHTNING_INTERRUPTIBLE_WORKS", "0")))
-
-
-def get_cluster_driver() -> Optional[str]:
- return "direct"
diff --git a/src/lightning/app/core/flow.py b/src/lightning/app/core/flow.py
deleted file mode 100644
index 5f749f1ab9aed..0000000000000
--- a/src/lightning/app/core/flow.py
+++ /dev/null
@@ -1,866 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import inspect
-import warnings
-from copy import deepcopy
-from datetime import datetime
-from types import FrameType
-from typing import TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
-
-from deepdiff import DeepHash
-
-from lightning.app.core.work import LightningWork
-from lightning.app.frontend import Frontend
-from lightning.app.storage.drive import Drive, _maybe_create_drive
-from lightning.app.storage.path import Path
-from lightning.app.utilities.app_helpers import _is_json_serializable, _LightningAppRef, _set_child_name, is_overridden
-from lightning.app.utilities.component import _sanitize_state
-from lightning.app.utilities.exceptions import ExitAppException, LightningFlowException
-from lightning.app.utilities.introspection import _is_init_context, _is_run_context
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute, _maybe_create_cloud_compute
-
-if TYPE_CHECKING:
- from lightning.app.runners.backends.backend import Backend
-
-
-class LightningFlow:
- _INTERNAL_STATE_VARS = {
- # Internal protected variables that are still part of the state (even though they are prefixed with "_")
- "_paths",
- "_layout",
- }
-
- def __init__(self) -> None:
- """The LightningFlow is used by the :class:`~lightning.app.core.app.LightningApp` to coordinate and manage
- long- running jobs contained, the :class:`~lightning.app.core.work.LightningWork`.
-
- A LightningFlow is characterized by:
-
- * A set of state variables.
- * Long-running jobs (:class:`~lightning.app.core.work.LightningWork`).
- * Its children ``LightningFlow`` or ``LightningWork`` with their state variables.
-
- **State variables**
-
- The LightningFlow are special classes whose attributes require to be
- json-serializable (e.g., int, float, bool, list, dict, ...).
-
- They also may not reach into global variables unless they are constant.
-
- The attributes need to be all defined in `__init__` method,
- and eventually assigned to different values throughout the lifetime of the object.
- However, defining new attributes outside of `__init__` is not allowed.
-
- Attributes taken together represent the state of the component.
- Components are capable of retrieving their state and that of their
- children recursively at any time. They are also capable of setting
- an externally provided state recursively to its children.
-
- **Execution model and work**
-
- The entry point for execution is the ``run`` method at the root component.
- The ``run`` method of the root component may call the ``run`` method of its children, and the children
- may call the ``run`` methods of their children and so on.
-
- The ``run`` method of the root component is called repeatedly in a while loop forever until the app gets
- terminated. In this programming model (reminiscent of React, Vue or Streamlit from the JavaScript world),
- the values of the state variables, or their changes, are translated into actions throughout the component
- hierarchy. This means the flow of execution will only be affected by state changes in a component or one of
- its children, and otherwise remain idempotent.
-
- The actions themselves are self-contained within :class:`~lightning.app.core.work.LightningWork`.
- The :class:`~lightning.app.core.work.LightningWork` are typically used for long-running jobs,
- like downloading a dataset, performing a query, starting a computationally heavy script.
- While one may access any state variable in a LightningWork from a LightningFlow, one may not
- directly call methods of other components from within a LightningWork as LightningWork can't have any children.
- This limitation allows applications to be distributed at scale.
-
- **Component hierarchy and App**
-
- Given the above characteristics, a root LightningFlow, potentially containing
- children components, can be passed to an App object and its execution
- can be distributed (each LightningWork will be run within its own process
- or different arrangements).
-
- Example:
-
- >>> from lightning.app import LightningFlow
- >>> class RootFlow(LightningFlow):
- ... def __init__(self):
- ... super().__init__()
- ... self.counter = 0
- ... def run(self):
- ... self.counter += 1
- ...
- >>> flow = RootFlow()
- >>> flow.run()
- >>> assert flow.counter == 1
- >>> assert flow.state["vars"]["counter"] == 1
-
- """
- self._state: set = set()
- self._name: str = ""
- self._flows: set = set()
- self._works: set = set()
- self._structures: set = set()
- self._calls: dict = {}
- self._changes: dict = {}
- self._layout: Union[List[Dict], Dict] = {}
- self._paths: dict = {}
- self._backend: Optional["Backend"] = None
- # tuple instead of a list so that it cannot be modified without using the setter
- self._lightningignore: Tuple[str, ...] = ()
-
- @property
- def name(self) -> str:
- """Return the current LightningFlow name."""
- return self._name or "root"
-
- def __setattr__(self, name: str, value: Any) -> None:
- attr = getattr(self.__class__, name, None)
- if isinstance(attr, property) and attr.fset is not None:
- return attr.fset(self, value)
-
- from lightning.app.structures import Dict as ComponentDict
- from lightning.app.structures import List as ComponentList
-
- if (
- not _is_init_context(self)
- and name not in self._state
- and name not in self._paths
- and (
- not isinstance(value, (LightningWork, LightningFlow))
- or (isinstance(value, (LightningWork, LightningFlow)) and not _is_run_context(self))
- )
- and name not in self._works.union(self._flows)
- and self._is_state_attribute(name)
- ):
- raise AttributeError(f"Cannot set attributes that were not defined in __init__: {name}")
-
- if isinstance(value, str) and value.startswith("lit://"):
- value = Path(value)
-
- if self._is_state_attribute(name):
- if hasattr(self, name):
- if name in self._flows and value != getattr(self, name):
- raise AttributeError(f"Cannot set attributes as the flow can't be changed once defined: {name}")
-
- if name in self._works and value != getattr(self, name):
- raise AttributeError(f"Cannot set attributes as the work can't be changed once defined: {name}")
-
- if isinstance(value, (list, dict)) and value:
- _type = (LightningFlow, LightningWork, ComponentList, ComponentDict)
- if isinstance(value, list) and all(isinstance(va, _type) for va in value):
- value = ComponentList(*value)
-
- if isinstance(value, dict) and all(isinstance(va, _type) for va in value.values()):
- value = ComponentDict(**value)
-
- if isinstance(value, LightningFlow):
- self._flows.add(name)
- _set_child_name(self, value, name)
- if name in self._state:
- self._state.remove(name)
- # Attach the backend to the flow and its children work.
- if self._backend:
- LightningFlow._attach_backend(value, self._backend)
- for work in value.works():
- work._register_cloud_compute()
-
- elif isinstance(value, LightningWork):
- self._works.add(name)
- _set_child_name(self, value, name)
- if name in self._state:
- self._state.remove(name)
- if self._backend:
- self._backend._wrap_run_method(_LightningAppRef().get_current(), value) # type: ignore[arg-type]
- value._register_cloud_compute()
-
- elif isinstance(value, (ComponentDict, ComponentList)):
- self._structures.add(name)
- _set_child_name(self, value, name)
-
- _backend = getattr(self, "backend", None)
- if _backend is not None:
- value._backend = _backend
-
- for flow in value.flows:
- if _backend is not None:
- LightningFlow._attach_backend(flow, _backend)
-
- for work in value.works:
- work._register_cloud_compute()
- if _backend is not None:
- _backend._wrap_run_method(_LightningAppRef().get_current(), work)
-
- elif isinstance(value, Path):
- # In the init context, the full name of the Flow and Work is not known, i.e., we can't serialize
- # the path without losing the information of origin and consumer. Hence, we delay the serialization
- # of the path object until the app is instantiated.
- if not _is_init_context(self):
- self._paths[name] = value.to_dict()
- self._state.add(name)
-
- elif isinstance(value, Drive):
- value = deepcopy(value)
- value.component_name = self.name
- self._state.add(name)
-
- elif isinstance(value, CloudCompute):
- self._state.add(name)
-
- elif _is_json_serializable(value):
- self._state.add(name)
-
- if not isinstance(value, Path) and hasattr(self, "_paths") and name in self._paths:
- # The attribute changed type from Path to another
- self._paths.pop(name)
-
- else:
- raise AttributeError(
- f"Only JSON-serializable attributes are currently supported"
- f" (str, int, float, bool, tuple, list, dict etc.) to be part of {self} state. "
- f"Found the attribute {name} with {value} instead. \n"
- "HINT: Private attributes defined as follows `self._x = y` won't be shared between components "
- "and therefore don't need to be JSON-serializable."
- )
-
- super().__setattr__(name, value)
- return None
-
- @staticmethod
- def _attach_backend(flow: "LightningFlow", backend: "Backend") -> None:
- """Attach the backend to all flows and its children."""
- flow._backend = backend
-
- for name in flow._structures:
- getattr(flow, name)._backend = backend
-
- for child_flow in flow.flows.values():
- child_flow._backend = backend
- for name in child_flow._structures:
- getattr(child_flow, name)._backend = backend
-
- app = _LightningAppRef().get_current()
-
- for child_work in flow.works():
- child_work._backend = backend
- backend._wrap_run_method(app, child_work) # type: ignore[arg-type]
-
- def __getattr__(self, item: str) -> Any:
- if item in self.__dict__.get("_paths", {}):
- return Path.from_dict(self._paths[item])
- return self.__getattribute__(item)
-
- @property
- def ready(self) -> bool:
- """Override to customize when your App should be ready."""
- flows = self.flows
- return all(flow.ready for flow in flows.values()) if flows else True
-
- @property
- def changes(self) -> dict:
- return self._changes.copy()
-
- @property
- def state(self) -> dict:
- """Returns the current flow state along its children."""
- children_state = {child: getattr(self, child).state for child in self._flows}
- works_state = {work: getattr(self, work).state for work in self._works}
- return {
- "vars": _sanitize_state({el: getattr(self, el) for el in self._state}),
- # this may have the challenge that ret cannot be pickled, we'll need to handle this
- "calls": self._calls.copy(),
- "flows": children_state,
- "works": works_state,
- "structures": {child: getattr(self, child).state for child in self._structures},
- "changes": {},
- }
-
- @property
- def state_vars(self) -> dict:
- children_state = {child: getattr(self, child).state_vars for child in self._flows}
- works_state = {work: getattr(self, work).state_vars for work in self._works}
- return {
- "vars": _sanitize_state({el: getattr(self, el) for el in self._state}),
- "flows": children_state,
- "works": works_state,
- "structures": {child: getattr(self, child).state_vars for child in self._structures},
- }
-
- @property
- def state_with_changes(self) -> dict:
- children_state = {child: getattr(self, child).state_with_changes for child in self._flows}
- works_state = {work: getattr(self, work).state_with_changes for work in self._works}
- return {
- "vars": _sanitize_state({el: getattr(self, el) for el in self._state}),
- # this may have the challenge that ret cannot be pickled, we'll need to handle this
- "calls": self._calls.copy(),
- "flows": children_state,
- "works": works_state,
- "structures": {child: getattr(self, child).state_with_changes for child in self._structures},
- "changes": self.changes,
- }
-
- @property
- def flows(self) -> Dict[str, "LightningFlow"]:
- """Return its children LightningFlow."""
- flows = {}
- for el in sorted(self._flows):
- flow = getattr(self, el)
- flows[flow.name] = flow
- flows.update(flow.flows)
- for struct_name in sorted(self._structures):
- flows.update(getattr(self, struct_name).flows)
- return flows
-
- @property
- def lightningignore(self) -> Tuple[str, ...]:
- """Programmatic equivalent of the ``.lightningignore`` file."""
- return self._lightningignore
-
- @lightningignore.setter
- def lightningignore(self, lightningignore: Tuple[str, ...]) -> None:
- if self._backend is not None:
- raise RuntimeError(
- f"Your app has been already dispatched, so modifying the `{self.name}.lightningignore` does not have an"
- " effect"
- )
- self._lightningignore = lightningignore
-
- def works(self, recurse: bool = True) -> List[LightningWork]:
- """Return its :class:`~lightning.app.core.work.LightningWork`."""
- works = [getattr(self, el) for el in sorted(self._works)]
- if not recurse:
- return works
- for child_name in sorted(self._flows):
- for w in getattr(self, child_name).works(recurse=recurse):
- works.append(w)
- for struct_name in sorted(self._structures):
- for w in getattr(self, struct_name).works:
- works.append(w)
- return works
-
- def named_works(self, recurse: bool = True) -> List[Tuple[str, LightningWork]]:
- """Return its :class:`~lightning.app.core.work.LightningWork` with their names."""
- return [(w.name, w) for w in self.works(recurse=recurse)]
-
- def set_state(self, provided_state: Dict, recurse: bool = True) -> None:
- """Method to set the state to this LightningFlow, its children and
- :class:`~lightning.app.core.work.LightningWork`.
-
- Arguments:
- provided_state: The state to be reloaded
- recurse: Whether to apply the state down children.
-
- """
- for k, v in provided_state["vars"].items():
- if isinstance(v, Dict):
- v = _maybe_create_drive(self.name, v)
- if isinstance(v, Dict):
- v = _maybe_create_cloud_compute(v)
- setattr(self, k, v)
- self._changes = provided_state["changes"]
- self._calls.update(provided_state["calls"])
-
- if not recurse:
- return
-
- for child, state in provided_state["flows"].items():
- getattr(self, child).set_state(state)
- for work, state in provided_state["works"].items():
- getattr(self, work).set_state(state)
- for structure, state in provided_state["structures"].items():
- getattr(self, structure).set_state(state)
-
- def stop(self, end_msg: str = "") -> None:
- """Method used to exit the application."""
- if end_msg:
- print(end_msg)
- raise ExitAppException
-
- def fail(self, end_msg: str = "") -> None:
- """Method used to exit and fail the application."""
- if end_msg:
- print(end_msg)
- raise LightningFlowException
-
- def _exit(self, end_msg: str = "") -> None:
- """Used to exit the application.
-
- Private method.
-
- .. deprecated:: 1.9.0
- This function is deprecated and will be removed in 2.0.0. Use :meth:`stop` instead.
-
- """
- warnings.warn(
- DeprecationWarning(
- "This function is deprecated and will be removed in 2.0.0. Use `LightningFlow.stop` instead."
- )
- )
-
- return self.stop(end_msg=end_msg)
-
- @staticmethod
- def _is_state_attribute(name: str) -> bool:
- """Every public attribute is part of the state by default and all protected (prefixed by '_') or private
- (prefixed by '__') attributes are not.
-
- Exceptions are listed in the `_INTERNAL_STATE_VARS` class variable.
-
- """
- return name in LightningFlow._INTERNAL_STATE_VARS or not name.startswith("_")
-
- def run(self, *args: Any, **kwargs: Any) -> None:
- """Override with your own logic."""
- pass
-
- def schedule(
- self, cron_pattern: str, start_time: Optional[datetime] = None, user_key: Optional[str] = None
- ) -> bool:
- """The schedule method is used to run a part of the flow logic on timely manner.
-
- .. code-block:: python
-
- from lightning.app import LightningFlow
-
-
- class Flow(LightningFlow):
- def run(self):
- if self.schedule("hourly"):
- print("run some code every hour")
-
- Arguments:
- cron_pattern: The cron pattern to provide. Learn more at https://crontab.guru/.
- start_time: The start time of the cron job.
- user_key: Optional key used to improve the caching mechanism.
-
- A best practice is to avoid running a dynamic flow or work under the self.schedule method.
- Instead, instantiate them within the condition, but run them outside.
-
- .. code-block:: python
-
- from lightning.app import LightningFlow
- from lightning.app.structures import List
-
-
- class SchedulerDAG(LightningFlow):
- def __init__(self):
- super().__init__()
- self.dags = List()
-
- def run(self):
- if self.schedule("hourly"):
- self.dags.append(DAG(...))
-
- for dag in self.dags:
- payload = dag.run()
-
- **Learn more about Scheduling**
-
- .. raw:: html
-
-
-
-
- .. displayitem::
- :header: Schedule your components
- :description: Learn more scheduling.
- :col_css: col-md-4
- :button_link: ../../../glossary/scheduling.html
- :height: 180
- :tag: Basic
-
- .. displayitem::
- :header: Build your own DAG
- :description: Learn more DAG scheduling with examples.
- :col_css: col-md-4
- :button_link: ../../../examples/app/dag/dag.html
- :height: 180
- :tag: Basic
-
- .. raw:: html
-
-
-
-
-
- """
- if not user_key:
- frame = cast(FrameType, inspect.currentframe()).f_back
- assert frame is not None
- cache_key = f"{cron_pattern}.{frame.f_code.co_filename}.{frame.f_lineno}"
- else:
- cache_key = user_key
-
- call_hash = f"{self.schedule.__name__}:{DeepHash(cache_key)[cache_key]}"
-
- if "scheduling" not in self._calls:
- self._calls["scheduling"] = {}
-
- entered = call_hash in self._calls["scheduling"]
-
- expr_aliases = {
- "midnight": "@midnight",
- "hourly": "@hourly",
- "daily": "@daily",
- "weekly": "@weekly",
- "monthly": "@monthly",
- "yearly": "@yearly",
- "annually": "@annually",
- }
-
- if cron_pattern in expr_aliases:
- cron_pattern = expr_aliases[cron_pattern]
-
- if not entered:
- if not start_time:
- start_time = datetime.now()
-
- schedule_metadata = {
- "running": False,
- "cron_pattern": cron_pattern,
- "start_time": str(start_time.isoformat()),
- "name": self.name,
- }
-
- self._calls["scheduling"][call_hash] = schedule_metadata
- app = _LightningAppRef().get_current()
- if app:
- app._register_schedule(call_hash, schedule_metadata)
- return True
-
- return self._calls["scheduling"][call_hash]["running"]
-
- def _enable_schedule(self, call_hash: str) -> None:
- self._calls["scheduling"][call_hash]["running"] = True
-
- def _disable_running_schedules(self) -> None:
- if "scheduling" not in self._calls:
- return
- for call_hash in self._calls["scheduling"]:
- self._calls["scheduling"][call_hash]["running"] = False
-
- def configure_layout(self) -> Union[Dict[str, Any], List[Dict[str, Any]], Frontend]:
- """Configure the UI layout of this LightningFlow.
-
- You can either
-
- 1. Return a single :class:`~lightning.app.frontend.frontend.Frontend` object to serve a user interface
- for this Flow.
- 2. Return a single dictionary to expose the UI of a child flow.
- 3. Return a list of dictionaries to arrange the children of this flow in one or multiple tabs.
-
- **Example:** Serve a static directory (with at least a file index.html inside).
-
- .. code-block:: python
-
- from lightning.app.frontend import StaticWebFrontend
-
-
- class Flow(LightningFlow):
- ...
-
- def configure_layout(self):
- return StaticWebFrontend("path/to/folder/to/serve")
-
- **Example:** Serve a streamlit UI (needs the streamlit package to be installed).
-
- .. code-block:: python
-
- from lightning.app.frontend import StaticWebFrontend
-
-
- class Flow(LightningFlow):
- ...
-
- def configure_layout(self):
- return StreamlitFrontend(render_fn=my_streamlit_ui)
-
-
- def my_streamlit_ui(state):
- # add your streamlit code here!
- import streamlit as st
-
-
- **Example:** Arrange the UI of my children in tabs (default UI by Lightning).
-
- .. code-block:: python
-
- class Flow(LightningFlow):
- def configure_layout(self):
- return [
- dict(name="First Tab", content=self.child0),
- dict(name="Second Tab", content=self.child1),
- dict(name="Lightning", content="https://lightning.ai"),
- ]
-
- If you don't implement ``configure_layout``, Lightning will collect all children and display their UI in a tab
- (if they have their own ``configure_layout`` implemented).
-
- Note:
- This hook gets called at the time of app creation and then again as part of the loop. If desired, the
- returned layout configuration can depend on the state. The only exception are the flows that return a
- :class:`~lightning.app.frontend.frontend.Frontend`. These need to be provided at the time of app creation
- in order for the runtime to start the server.
-
- **Learn more about adding UI**
-
- .. raw:: html
-
-
-
-
- .. displayitem::
- :header: Add a web user interface (UI)
- :description: Learn more how to integrate several UIs.
- :col_css: col-md-4
- :button_link: ../../../workflows/add_web_ui/index.html
- :height: 180
- :tag: Basic
-
- .. raw:: html
-
-
-
-
-
- """
- return [{"name": name, "content": component} for (name, component) in self.flows.items()]
-
- def experimental_iterate(self, iterable: Iterable, run_once: bool = True, user_key: str = "") -> Generator:
- """This method should always be used with any kind of iterable to ensure its fault tolerant.
-
- If you want your iterable to always be consumed from scratch, you shouldn't use this method.
-
- Arguments:
- iterable: Iterable to iterate over. The iterable shouldn't have side effects or be random.
- run_once: Whether to run the entire iteration only once.
- Otherwise, it would restart from the beginning.
- user_key: Key to be used to track the caching mechanism.
-
- """
- if not isinstance(iterable, Iterable):
- raise TypeError(f"An iterable should be provided to `self.iterate` method. Found {iterable}")
-
- # TODO: Find a better way. Investigated using __reduce__, but state change invalidate the cache.
- if not user_key:
- frame = cast(FrameType, inspect.currentframe()).f_back
- assert frame is not None
- cache_key = f"{frame.f_code.co_filename}.{frame.f_code.co_firstlineno}"
- else:
- cache_key = user_key
-
- call_hash = f"{self.experimental_iterate.__name__}:{DeepHash(cache_key)[cache_key]}"
- entered = call_hash in self._calls
- has_started = entered and self._calls[call_hash]["counter"] > 0
- has_finished = entered and self._calls[call_hash]["has_finished"]
-
- if has_finished:
- if not run_once:
- self._calls[call_hash].update({"counter": 0, "has_finished": False})
- else:
- return range(0)
-
- if not has_started:
- self._calls[call_hash] = {
- "name": self.experimental_iterate.__name__,
- "call_hash": call_hash,
- "counter": 0,
- "has_finished": False,
- }
-
- skip_counter = max(self._calls[call_hash]["counter"], 0)
-
- for counter, value in enumerate(iterable):
- if skip_counter:
- skip_counter -= 1
- continue
- self._calls[call_hash].update({"counter": counter})
- yield value
-
- self._calls[call_hash].update({"has_finished": True})
-
- def configure_commands(self) -> None:
- """Configure the commands of this LightningFlow.
-
- Returns a list of dictionaries mapping a command name to a flow method.
-
- .. code-block:: python
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.names = []
-
- def configure_commands(self):
- return {"my_command_name": self.my_remote_method}
-
- def my_remote_method(self, name):
- self.names.append(name)
-
- Once the app is running with the following command:
-
- .. code-block:: bash
-
- lightning_app run app app.py
-
- .. code-block:: bash
-
- lightning_app my_command_name --args name=my_own_name
-
- """
- raise NotImplementedError
-
- def configure_api(self) -> None:
- """Configure the API routes of the LightningFlow.
-
- Returns a list of HttpMethod such as Post or Get.
-
- .. code-block:: python
-
- from lightning.app import LightningFlow
- from lightning.app.api import Post
-
- from pydantic import BaseModel
-
-
- class HandlerModel(BaseModel):
- name: str
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.names = []
-
- def handler(self, config: HandlerModel) -> None:
- self.names.append(config.name)
-
- def configure_api(self):
- return [Post("/v1/api/request", self.handler)]
-
- Once the app is running, you can access the Swagger UI of the app
- under the ``/docs`` route.
-
- """
- raise NotImplementedError
-
- def state_dict(self) -> dict:
- """Returns the current flow state but not its children."""
- return {
- "vars": _sanitize_state({el: getattr(self, el) for el in self._state}),
- "calls": self._calls.copy(),
- "changes": {},
- "flows": {},
- "works": {},
- "structures": {},
- }
-
- def load_state_dict(
- self,
- flow_state: Dict[str, Any],
- children_states: Dict[str, Any],
- strict: bool = True,
- ) -> None:
- """Reloads the state of this flow and its children.
-
- .. code-block:: python
-
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
- class Flow(LightningFlow):
- def run(self):
- # dynamically create a work.
- if not getattr(self, "w", None):
- self.w = WorkReload()
-
- self.w.run()
-
- def load_state_dict(self, flow_state, children_states, strict) -> None:
- # 1: Re-instantiate the dynamic work
- self.w = Work()
-
- # 2: Make any states modification / migration.
- ...
-
- # 3: Call the parent ``load_state_dict`` to
- # recursively reload the states.
- super().load_state_dict(
- flow_state,
- children_states,
- strict,
- )
-
- Arguments:
- flow_state: The state of the current flow.
- children_states: The state of the dynamic children of this flow.
- strict: Whether to raise an exception if a dynamic
- children hasn't been re-created.
-
- """
- self.set_state(flow_state, recurse=False)
- direct_children_states = {k: v for k, v in children_states.items() if "." not in k}
- for child_name, state in direct_children_states.items():
- child = getattr(self, child_name, None)
- if isinstance(child, LightningFlow):
- lower_children_states = {
- k.replace(child_name + ".", ""): v
- for k, v in children_states.items()
- if k.startswith(child_name) and k != child_name
- }
- child.load_state_dict(state, lower_children_states, strict=strict)
- elif isinstance(child, LightningWork):
- child.set_state(state)
- elif strict:
- raise ValueError(f"The component {child_name} wasn't instantiated for the component {self.name}")
-
- def stop_works(self, works: List[Any]) -> None:
- if self._backend is None:
- raise RuntimeError("Your flow should have a backend attached. Found None.")
- self._backend.stop_works(works)
-
-
-class _RootFlow(LightningFlow):
- def __init__(self, work: LightningWork) -> None:
- super().__init__()
- self.work = work
-
- @property
- def ready(self) -> bool:
- ready = getattr(self.work, "ready", None)
- if ready is not None:
- return ready
- return self.work.url != ""
-
- def run(self) -> None:
- if self.work.has_succeeded:
- self.work.stop()
- self.stop()
- self.work.run()
-
- def configure_layout(self) -> list:
- if is_overridden("configure_layout", self.work):
- return [{"name": "Main", "content": self.work}]
- return []
diff --git a/src/lightning/app/core/queues.py b/src/lightning/app/core/queues.py
deleted file mode 100644
index f04447320cc3f..0000000000000
--- a/src/lightning/app/core/queues.py
+++ /dev/null
@@ -1,585 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import base64
-import multiprocessing
-import pickle
-import queue # needed as import instead from/import for mocking in tests
-import time
-from abc import ABC, abstractmethod
-from enum import Enum
-from pathlib import Path
-from typing import Any, List, Optional, Tuple
-from urllib.parse import urljoin
-
-import backoff
-import msgpack
-import requests
-from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
-
-from lightning.app.core.constants import (
- BATCH_DELTA_COUNT,
- HTTP_QUEUE_REFRESH_INTERVAL,
- HTTP_QUEUE_REQUESTS_PER_SECOND,
- HTTP_QUEUE_TOKEN,
- HTTP_QUEUE_URL,
- IS_RUNNING_IN_FLOW,
- LIGHTNING_DIR,
- QUEUE_DEBUG_ENABLED,
- REDIS_HOST,
- REDIS_PASSWORD,
- REDIS_PORT,
- REDIS_QUEUES_READ_DEFAULT_TIMEOUT,
- STATE_UPDATE_TIMEOUT,
-)
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.imports import _is_redis_available, requires
-from lightning.app.utilities.network import HTTPClient
-
-if _is_redis_available():
- import redis
-
-logger = Logger(__name__)
-
-
-READINESS_QUEUE_CONSTANT = "READINESS_QUEUE"
-ERROR_QUEUE_CONSTANT = "ERROR_QUEUE"
-DELTA_QUEUE_CONSTANT = "DELTA_QUEUE"
-HAS_SERVER_STARTED_CONSTANT = "HAS_SERVER_STARTED_QUEUE"
-CALLER_QUEUE_CONSTANT = "CALLER_QUEUE"
-API_STATE_PUBLISH_QUEUE_CONSTANT = "API_STATE_PUBLISH_QUEUE"
-API_DELTA_QUEUE_CONSTANT = "API_DELTA_QUEUE"
-API_REFRESH_QUEUE_CONSTANT = "API_REFRESH_QUEUE"
-ORCHESTRATOR_REQUEST_CONSTANT = "ORCHESTRATOR_REQUEST"
-ORCHESTRATOR_RESPONSE_CONSTANT = "ORCHESTRATOR_RESPONSE"
-ORCHESTRATOR_COPY_REQUEST_CONSTANT = "ORCHESTRATOR_COPY_REQUEST"
-ORCHESTRATOR_COPY_RESPONSE_CONSTANT = "ORCHESTRATOR_COPY_RESPONSE"
-WORK_QUEUE_CONSTANT = "WORK_QUEUE"
-API_RESPONSE_QUEUE_CONSTANT = "API_RESPONSE_QUEUE"
-FLOW_TO_WORKS_DELTA_QUEUE_CONSTANT = "FLOW_TO_WORKS_DELTA_QUEUE"
-
-
-class QueuingSystem(Enum):
- MULTIPROCESS = "multiprocess"
- REDIS = "redis"
- HTTP = "http"
-
- def get_queue(self, queue_name: str) -> "BaseQueue":
- if self == QueuingSystem.MULTIPROCESS:
- return MultiProcessQueue(queue_name, default_timeout=STATE_UPDATE_TIMEOUT)
- if self == QueuingSystem.REDIS:
- return RedisQueue(queue_name, default_timeout=REDIS_QUEUES_READ_DEFAULT_TIMEOUT)
-
- queue = HTTPQueue(queue_name, default_timeout=STATE_UPDATE_TIMEOUT)
-
- # In the flow, don't rate limit the caller queue. Otherwise, startup time would be slow with lot of works.
- if CALLER_QUEUE_CONSTANT in queue_name and IS_RUNNING_IN_FLOW:
- return queue
-
- return RateLimitedQueue(queue, HTTP_QUEUE_REQUESTS_PER_SECOND)
-
- def get_api_response_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{API_RESPONSE_QUEUE_CONSTANT}" if queue_id else API_RESPONSE_QUEUE_CONSTANT
- return self.get_queue(queue_name)
-
- def get_readiness_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{READINESS_QUEUE_CONSTANT}" if queue_id else READINESS_QUEUE_CONSTANT
- return self.get_queue(queue_name)
-
- def get_delta_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{DELTA_QUEUE_CONSTANT}" if queue_id else DELTA_QUEUE_CONSTANT
- return self.get_queue(queue_name)
-
- def get_error_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{ERROR_QUEUE_CONSTANT}" if queue_id else ERROR_QUEUE_CONSTANT
- return self.get_queue(queue_name)
-
- def get_has_server_started_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{HAS_SERVER_STARTED_CONSTANT}" if queue_id else HAS_SERVER_STARTED_CONSTANT
- return self.get_queue(queue_name)
-
- def get_caller_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{CALLER_QUEUE_CONSTANT}_{work_name}" if queue_id else f"{CALLER_QUEUE_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
- def get_api_state_publish_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{API_STATE_PUBLISH_QUEUE_CONSTANT}" if queue_id else API_STATE_PUBLISH_QUEUE_CONSTANT
- return self.get_queue(queue_name)
-
- # TODO: This is hack, so we can remove this queue entirely when fully optimized.
- def get_api_delta_queue(self, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = f"{queue_id}_{DELTA_QUEUE_CONSTANT}" if queue_id else DELTA_QUEUE_CONSTANT
- return self.get_queue(queue_name)
-
- def get_orchestrator_request_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{ORCHESTRATOR_REQUEST_CONSTANT}_{work_name}"
- if queue_id
- else f"{ORCHESTRATOR_REQUEST_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
- def get_orchestrator_response_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{ORCHESTRATOR_RESPONSE_CONSTANT}_{work_name}"
- if queue_id
- else f"{ORCHESTRATOR_RESPONSE_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
- def get_orchestrator_copy_request_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{ORCHESTRATOR_COPY_REQUEST_CONSTANT}_{work_name}"
- if queue_id
- else f"{ORCHESTRATOR_COPY_REQUEST_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
- def get_orchestrator_copy_response_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{ORCHESTRATOR_COPY_RESPONSE_CONSTANT}_{work_name}"
- if queue_id
- else f"{ORCHESTRATOR_COPY_RESPONSE_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
- def get_work_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{WORK_QUEUE_CONSTANT}_{work_name}" if queue_id else f"{WORK_QUEUE_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
- def get_flow_to_work_delta_queue(self, work_name: str, queue_id: Optional[str] = None) -> "BaseQueue":
- queue_name = (
- f"{queue_id}_{FLOW_TO_WORKS_DELTA_QUEUE_CONSTANT}_{work_name}"
- if queue_id
- else f"{FLOW_TO_WORKS_DELTA_QUEUE_CONSTANT}_{work_name}"
- )
- return self.get_queue(queue_name)
-
-
-class BaseQueue(ABC):
- """Base Queue class that has a similar API to the Queue class in python."""
-
- @abstractmethod
- def __init__(self, name: str, default_timeout: float):
- self.name = name
- self.default_timeout = default_timeout
-
- @abstractmethod
- def put(self, item: Any) -> None:
- pass
-
- @abstractmethod
- def get(self, timeout: Optional[float] = None) -> Any:
- """Returns the left most element of the queue.
-
- Parameters
- ----------
- timeout:
- Read timeout in seconds, in case of input timeout is 0, the `self.default_timeout` is used.
- A timeout of None can be used to block indefinitely.
-
- """
- pass
-
- @abstractmethod
- def batch_get(self, timeout: Optional[float] = None, count: Optional[int] = None) -> List[Any]:
- """Returns the left most elements of the queue.
-
- Parameters
- ----------
- timeout:
- Read timeout in seconds, in case of input timeout is 0, the `self.default_timeout` is used.
- A timeout of None can be used to block indefinitely.
- count:
- The number of element to get from the queue
-
- """
-
- @property
- def is_running(self) -> bool:
- """Returns True if the queue is running, False otherwise.
-
- Child classes should override this property and implement custom logic as required
-
- """
- return True
-
-
-class MultiProcessQueue(BaseQueue):
- def __init__(self, name: str, default_timeout: float) -> None:
- self.name = name
- self.default_timeout = default_timeout
- context = multiprocessing.get_context("spawn")
- self.queue = context.Queue()
-
- def put(self, item: Any) -> None:
- self.queue.put(item)
-
- def get(self, timeout: Optional[float] = None) -> Any:
- if timeout == 0:
- timeout = self.default_timeout
- return self.queue.get(timeout=timeout, block=(timeout is None))
-
- def batch_get(self, timeout: Optional[float] = None, count: Optional[int] = None) -> List[Any]:
- if timeout == 0:
- timeout = self.default_timeout
- # For multiprocessing, we can simply collect the latest upmost element
- return [self.queue.get(timeout=timeout, block=(timeout is None))]
-
-
-class RedisQueue(BaseQueue):
- @requires("redis")
- def __init__(
- self,
- name: str,
- default_timeout: float,
- host: Optional[str] = None,
- port: Optional[int] = None,
- password: Optional[str] = None,
- ):
- """
- Parameters
- ----------
- name:
- The name of the list to use
- default_timeout:
- Default timeout for redis read
- host:
- The hostname of the redis server
- port:
- The port of the redis server
- password:
- Redis password
- """
- if name is None:
- raise ValueError("You must specify a name for the queue")
- self.host = host or REDIS_HOST
- self.port = port or REDIS_PORT
- self.password = password or REDIS_PASSWORD
- self.name = name
- self.default_timeout = default_timeout
- self.redis = redis.Redis(host=self.host, port=self.port, password=self.password)
-
- def put(self, item: Any) -> None:
- from lightning.app.core.work import LightningWork
-
- is_work = isinstance(item, LightningWork)
-
- # TODO: Be careful to handle with a lock if another thread needs
- # to access the work backend one day.
- # The backend isn't picklable
- # Raises a TypeError: cannot pickle '_thread.RLock' object
- if is_work:
- backend = item._backend
- item._backend = None
-
- value = pickle.dumps(item)
- try:
- self.redis.rpush(self.name, value)
- except redis.exceptions.ConnectionError:
- raise ConnectionError(
- "Your app failed because it couldn't connect to Redis. "
- "Please try running your app again. "
- "If the issue persists, please contact support@lightning.ai"
- )
-
- # The backend isn't pickable.
- if is_work:
- item._backend = backend
-
- def get(self, timeout: Optional[float] = None) -> Any:
- """Returns the left most element of the redis queue.
-
- Parameters
- ----------
- timeout:
- Read timeout in seconds, in case of input timeout is 0, the `self.default_timeout` is used.
- A timeout of None can be used to block indefinitely.
-
- """
- if timeout is None:
- # this means it's blocking in redis
- timeout = 0
- elif timeout == 0:
- timeout = self.default_timeout
-
- try:
- out = self.redis.blpop([self.name], timeout=timeout)
- except redis.exceptions.ConnectionError:
- raise ConnectionError(
- "Your app failed because it couldn't connect to Redis. "
- "Please try running your app again. "
- "If the issue persists, please contact support@lightning.ai"
- )
-
- if out is None:
- raise queue.Empty
- return pickle.loads(out[1])
-
- def batch_get(self, timeout: Optional[float] = None, count: Optional[int] = None) -> Any:
- return [self.get(timeout=timeout)]
-
- def clear(self) -> None:
- """Clear all elements in the queue."""
- self.redis.delete(self.name)
-
- def length(self) -> int:
- """Returns the number of elements in the queue."""
- try:
- return self.redis.llen(self.name)
- except redis.exceptions.ConnectionError:
- raise ConnectionError(
- "Your app failed because it couldn't connect to Redis. "
- "Please try running your app again. "
- "If the issue persists, please contact support@lightning.ai"
- )
-
- @property
- def is_running(self) -> bool:
- """Pinging the redis server to see if it is alive."""
- try:
- return self.redis.ping()
- except redis.exceptions.ConnectionError:
- return False
-
- def to_dict(self) -> dict:
- return {
- "type": "redis",
- "name": self.name,
- "default_timeout": self.default_timeout,
- "host": self.host,
- "port": self.port,
- "password": self.password,
- }
-
- @classmethod
- def from_dict(cls, state: dict) -> "RedisQueue":
- return cls(**state)
-
-
-class RateLimitedQueue(BaseQueue):
- def __init__(self, queue: BaseQueue, requests_per_second: float):
- """This is a queue wrapper that will block on get or put calls if they are made too quickly.
-
- Args:
- queue: The queue to wrap.
- requests_per_second: The target number of get or put requests per second.
-
- """
- self.name = queue.name
- self.default_timeout = queue.default_timeout
-
- self._queue = queue
- self._seconds_per_request = 1 / requests_per_second
-
- self._last_get = 0.0
-
- @property
- def is_running(self) -> bool:
- return self._queue.is_running
-
- def _wait_until_allowed(self, last_time: float) -> None:
- t = time.time()
- diff = t - last_time
- if diff < self._seconds_per_request:
- time.sleep(self._seconds_per_request - diff)
-
- def get(self, timeout: Optional[float] = None) -> Any:
- self._wait_until_allowed(self._last_get)
- self._last_get = time.time()
- return self._queue.get(timeout=timeout)
-
- def batch_get(self, timeout: Optional[float] = None, count: Optional[int] = None) -> Any:
- self._wait_until_allowed(self._last_get)
- self._last_get = time.time()
- return self._queue.batch_get(timeout=timeout)
-
- def put(self, item: Any) -> None:
- return self._queue.put(item)
-
-
-class HTTPQueue(BaseQueue):
- def __init__(self, name: str, default_timeout: float) -> None:
- """
- Parameters
- ----------
- name:
- The name of the Queue to use. In the current implementation, we expect the name to be of the format
- `appID_queueName`. Based on this assumption, we try to fetch the app id and the queue name by splitting
- the `name` argument.
- default_timeout:
- Default timeout for redis read
- """
- if name is None:
- raise ValueError("You must specify a name for the queue")
- self.app_id, self._name_suffix = self._split_app_id_and_queue_name(name)
- self.name = name # keeping the name for debugging
- self.default_timeout = default_timeout
- self.client = HTTPClient(base_url=HTTP_QUEUE_URL, auth_token=HTTP_QUEUE_TOKEN, log_callback=debug_log_callback)
-
- @property
- def is_running(self) -> bool:
- """Pinging the http redis server to see if it is alive."""
- try:
- url = urljoin(HTTP_QUEUE_URL, "health")
- resp = requests.get(
- url,
- headers={"Authorization": f"Bearer {HTTP_QUEUE_TOKEN}"},
- timeout=1,
- )
- if resp.status_code == 200:
- return True
- except (ConnectionError, ConnectTimeout, ReadTimeout):
- return False
- return False
-
- @backoff.on_exception(
- backoff.expo, (RuntimeError, requests.exceptions.HTTPError, requests.exceptions.ChunkedEncodingError)
- )
- def get(self, timeout: Optional[float] = None) -> Any:
- logger.debug(f"get {self.name}")
- if not self.app_id:
- raise ValueError(f"App ID couldn't be extracted from the queue name: {self.name}")
-
- # it's a blocking call, we need to loop and call the backend to mimic this behavior
- if timeout is None:
- while True:
- try:
- try:
- return self._get()
- except requests.exceptions.HTTPError:
- pass
- except queue.Empty:
- time.sleep(HTTP_QUEUE_REFRESH_INTERVAL)
-
- # make one request and return the result
- if timeout == 0:
- try:
- return self._get()
- except requests.exceptions.HTTPError:
- return None
-
- # timeout is some value - loop until the timeout is reached
- start_time = time.time()
- while (time.time() - start_time) < timeout:
- try:
- try:
- return self._get()
- except requests.exceptions.HTTPError:
- if timeout > self.default_timeout:
- return None
- raise queue.Empty
- except queue.Empty:
- # Note: In theory, there isn't a need for a sleep as the queue shouldn't
- # block the flow if the queue is empty.
- # However, as the Http Server can saturate,
- # let's add a sleep here if a higher timeout is provided
- # than the default timeout
- if timeout > self.default_timeout:
- time.sleep(0.05)
- return None
-
- def _get(self) -> Any:
- try:
- resp = self.client.post(f"v1/{self.app_id}/{self._name_suffix}", query_params={"action": "pop"})
- if resp.status_code == 204:
- raise queue.Empty
-
- if self._use_pickle():
- return pickle.loads(resp.content)
- return msgpack.unpackb(resp.content)
- except ConnectionError:
- # Note: If the Http Queue service isn't available,
- # we consider the queue is empty to avoid failing the app.
- raise queue.Empty
-
- def batch_get(self, timeout: Optional[float] = None, count: Optional[int] = None) -> List[Any]:
- logger.debug(f"batch_get {self.name}")
- try:
- resp = self.client.post(
- f"v1/{self.app_id}/{self._name_suffix}",
- query_params={"action": "popCount", "count": str(count or BATCH_DELTA_COUNT)},
- )
- if resp.status_code == 204:
- raise queue.Empty
-
- if self._use_pickle():
- return [pickle.loads(base64.b64decode(data)) for data in resp.json()]
- return [msgpack.unpackb(base64.b64decode(data)) for data in resp.json()]
- except ConnectionError:
- # Note: If the Http Queue service isn't available,
- # we consider the queue is empty to avoid failing the app.
- raise queue.Empty
-
- @backoff.on_exception(
- backoff.expo, (RuntimeError, requests.exceptions.HTTPError, requests.exceptions.ChunkedEncodingError)
- )
- def put(self, item: Any) -> None:
- logger.debug(f"put {self.name}")
- if not self.app_id:
- raise ValueError(f"The Lightning App ID couldn't be extracted from the queue name: {self.name}")
-
- value = pickle.dumps(item, protocol=pickle.HIGHEST_PROTOCOL) if self._use_pickle() else msgpack.packb(item)
- resp = self.client.post(f"v1/{self.app_id}/{self._name_suffix}", data=value, query_params={"action": "push"})
- if resp.status_code != 201:
- raise RuntimeError(f"Failed to push to queue: {self._name_suffix}")
-
- def length(self) -> int:
- if not self.app_id:
- raise ValueError(f"App ID couldn't be extracted from the queue name: {self.name}")
-
- try:
- val = self.client.get(f"/v1/{self.app_id}/{self._name_suffix}/length")
- return int(val.text)
- except requests.exceptions.HTTPError:
- return 0
-
- @staticmethod
- def _split_app_id_and_queue_name(queue_name: str) -> Tuple[str, str]:
- """This splits the app id and the queue name into two parts.
-
- This can be brittle, as if the queue name creation logic changes, the response values from here wouldn't be
- accurate. Remove this eventually and let the Queue class take app id and name of the queue as arguments
-
- """
- if "_" not in queue_name:
- return "", queue_name
- app_id, queue_name = queue_name.split("_", 1)
- return app_id, queue_name
-
- def to_dict(self) -> dict:
- return {
- "type": "http",
- "name": self.name,
- "default_timeout": self.default_timeout,
- }
-
- @classmethod
- def from_dict(cls, state: dict) -> "HTTPQueue":
- return cls(**state)
-
- def _use_pickle(self) -> bool:
- # Note: msgpack is faster than pickle to serialize and deserialize simple JSON
- return (
- WORK_QUEUE_CONSTANT in self.name or DELTA_QUEUE_CONSTANT in self.name or ERROR_QUEUE_CONSTANT in self.name
- )
-
-
-def debug_log_callback(message: str, *args: Any, **kwargs: Any) -> None:
- if QUEUE_DEBUG_ENABLED or (Path(LIGHTNING_DIR) / "QUEUE_DEBUG_ENABLED").exists():
- logger.info(message, *args, **kwargs)
diff --git a/src/lightning/app/core/work.py b/src/lightning/app/core/work.py
deleted file mode 100644
index 8764c118e15ee..0000000000000
--- a/src/lightning/app/core/work.py
+++ /dev/null
@@ -1,772 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import sys
-import time
-import warnings
-from copy import deepcopy
-from functools import partial, wraps
-from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type, Union
-
-from deepdiff import DeepHash, Delta
-
-from lightning.app.core.queues import BaseQueue
-from lightning.app.storage.drive import Drive, _maybe_create_drive
-from lightning.app.storage.path import Path
-from lightning.app.storage.payload import Payload
-from lightning.app.utilities.app_helpers import _is_json_serializable, _LightningAppRef, is_overridden
-from lightning.app.utilities.app_status import WorkStatus
-from lightning.app.utilities.component import _is_flow_context, _sanitize_state
-from lightning.app.utilities.enum import (
- CacheCallsKeys,
- WorkFailureReasons,
- WorkStageStatus,
- WorkStopReasons,
- make_status,
-)
-from lightning.app.utilities.exceptions import LightningWorkException
-from lightning.app.utilities.introspection import _is_init_context
-from lightning.app.utilities.network import find_free_network_port
-from lightning.app.utilities.packaging.build_config import BuildConfig
-from lightning.app.utilities.packaging.cloud_compute import (
- _CLOUD_COMPUTE_STORE,
- CloudCompute,
- _CloudComputeStore,
- _maybe_create_cloud_compute,
-)
-from lightning.app.utilities.proxies import Action, LightningWorkSetAttrProxy, ProxyWorkRun, WorkRunExecutor, unwrap
-
-if TYPE_CHECKING:
- from lightning.app.frontend import Frontend
-
-
-class LightningWork:
- _INTERNAL_STATE_VARS = (
- # Internal protected variables that are still part of the state (even though they are prefixed with "_")
- "_paths",
- "_host",
- "_port",
- "_url",
- "_restarting",
- "_internal_ip",
- "_public_ip",
- )
-
- _run_executor_cls: Type[WorkRunExecutor] = WorkRunExecutor
- # TODO: Move to spawn for all Operating System.
- _start_method = "spawn" if sys.platform in ("darwin", "win32") else "fork"
-
- def __init__(
- self,
- parallel: bool = False,
- cache_calls: bool = True,
- raise_exception: bool = True,
- host: str = "127.0.0.1",
- port: Optional[int] = None,
- local_build_config: Optional[BuildConfig] = None,
- cloud_build_config: Optional[BuildConfig] = None,
- cloud_compute: Optional[CloudCompute] = None,
- run_once: Optional[bool] = None, # TODO: Remove run_once
- start_with_flow: bool = True,
- ):
- """LightningWork, or Work in short, is a building block for long-running jobs.
-
- The LightningApp runs its :class:`~lightning.app.core.flow.LightningFlow` component
- within an infinite loop and track the ``LightningWork`` status update.
-
- Use LightningWork for third-party services or for launching heavy jobs such as
- downloading data, training or serving a model.
-
- Each LightningWork is running in its own independent process. Works are self-isolated from the rest,
- e.g any state changes happening within the work will be reflected within the flow but not the other way around.
-
- Arguments:
- parallel: Whether to run in parallel mode or not. When False, the flow waits for the work to finish.
- cache_calls: Whether the ``run`` method should cache its input arguments and not run again when provided
- with the same arguments in subsequent calls.
- raise_exception: Whether to re-raise an exception in the flow when raised from within the work run method.
- host: Bind socket to this host
- port: Bind socket to this port. Be default, this is None and should be called within your run method.
- local_build_config: The local BuildConfig isn't used until Lightning supports DockerRuntime.
- cloud_build_config: The cloud BuildConfig enables user to easily configure machine before running this work.
- run_once: Deprecated in favor of cache_calls. This will be removed soon.
- start_with_flow: Whether the work should be started at the same time as the root flow. Only applies to works
- defined in ``__init__``.
-
- **Learn More About Lightning Work Inner Workings**
-
- .. raw:: html
-
-
-
-
- .. displayitem::
- :header: The Lightning Work inner workings.
- :description: Learn more Lightning Work.
- :col_css: col-md-4
- :button_link: ../../core_api/lightning_work/index.html
- :height: 180
- :tag: Basic
-
- .. raw:: html
-
-
-
-
-
- """
- from lightning.app.runners.backends.backend import Backend
-
- if run_once is not None:
- warnings.warn(
- "The `run_once` argument to LightningWork is deprecated in favor of `cache_calls` and will be removed"
- " in the next version. Use `cache_calls` instead."
- )
- self._cache_calls = run_once if run_once is not None else cache_calls
- self._state = {
- "_host",
- "_port",
- "_url",
- "_future_url",
- "_internal_ip",
- "_public_ip",
- "_restarting",
- "_cloud_compute",
- "_display_name",
- }
- self._parallel: bool = parallel
- self._host: str = host
- self._port: Optional[int] = port
- self._url: str = ""
- self._future_url: str = "" # The cache URL is meant to defer resolving the url values.
- self._internal_ip: str = ""
- self._public_ip: str = ""
- # setattr_replacement is used by the multiprocessing runtime to send the latest changes to the main coordinator
- self._setattr_replacement: Optional[Callable[[str, Any], None]] = None
- self._name: str = ""
- self._display_name: str = ""
- # The ``self._calls`` is used to track whether the run
- # method with a given set of input arguments has already been called.
- # Example of its usage:
- # {
- # 'latest_call_hash': '167fe2e',
- # '167fe2e': {
- # 'statuses': [
- # {'stage': 'pending', 'timestamp': 1659433519.851271},
- # {'stage': 'running', 'timestamp': 1659433519.956482},
- # {'stage': 'stopped', 'timestamp': 1659433520.055768}]}
- # ]
- # },
- # ...
- # }
- self._calls: dict = {CacheCallsKeys.LATEST_CALL_HASH: None}
- self._changes: dict = {}
- self._raise_exception = raise_exception
- self._paths: dict = {}
- self._request_queue: Optional[BaseQueue] = None
- self._response_queue: Optional[BaseQueue] = None
- self._restarting: bool = False
- self._start_with_flow = start_with_flow
- self._local_build_config = local_build_config or BuildConfig()
- self._cloud_build_config = cloud_build_config or BuildConfig()
- self._cloud_compute = cloud_compute or CloudCompute()
- # tuple instead of a list so that it cannot be modified without using the setter
- self._lightningignore: Tuple[str, ...] = ()
- self._backend: Optional[Backend] = None
- self._check_run_is_implemented()
- self._on_init_end()
-
- @property
- def url(self) -> str:
- """Returns the current url of the work."""
- return self._url
-
- @url.setter
- def url(self, url: str) -> None:
- self._url = url
-
- @property
- def host(self) -> str:
- """Returns the current host of the work."""
- return self._host
-
- @property
- def port(self) -> int:
- if self._port is None:
- self._port = find_free_network_port()
- return self._port
-
- @property
- def internal_ip(self) -> str:
- """The internal ip address of this LightningWork, reachable by other Work locally and in the cloud.
-
- By default, this attribute returns the empty string and the ip address will only be returned once the work runs.
- Locally, the address is 127.0.0.1 and in the cloud it will be determined by the cluster.
-
- """
- return self._internal_ip
-
- @property
- def public_ip(self) -> str:
- """The public ip address of this LightningWork, reachable from the internet.
-
- By default, this attribute returns the empty string and the ip address will only be returned once the work runs.
- Locally, this address is undefined (empty string) and in the cloud it will be determined by the cluster.
-
- """
- return self._public_ip
-
- def _on_init_end(self) -> None:
- self._local_build_config.on_work_init(self)
- self._cloud_build_config.on_work_init(self, self._cloud_compute)
-
- @staticmethod
- def _is_state_attribute(name: str) -> bool:
- """Every public attribute is part of the state by default and all protected (prefixed by '_') or private
- (prefixed by '__') attributes are not.
-
- Exceptions are listed in the `_INTERNAL_STATE_VARS` class variable.
-
- """
- return name in LightningWork._INTERNAL_STATE_VARS or not name.startswith("_")
-
- @property
- def name(self) -> str:
- """Returns the name of the LightningWork."""
- return self._name
-
- @property
- def display_name(self) -> str:
- """Returns the display name of the LightningWork in the cloud.
-
- The display name needs to set before the run method of the work is called.
-
- """
- return self._display_name
-
- @display_name.setter
- def display_name(self, display_name: str) -> None:
- """Sets the display name of the LightningWork in the cloud."""
- if not self.has_started:
- self._display_name = display_name
- elif self._display_name != display_name:
- raise RuntimeError("The display name can be set only before the work has started.")
-
- @property
- def cache_calls(self) -> bool:
- """Returns whether the ``run`` method should cache its input arguments and not run again when provided with the
- same arguments in subsequent calls."""
- return self._cache_calls
-
- @property
- def parallel(self) -> bool:
- """Whether to run in parallel mode or not.
-
- When parallel is False, the flow waits for the work to finish.
-
- """
- return self._parallel
-
- @property
- def local_build_config(self) -> BuildConfig:
- return self._local_build_config
-
- @local_build_config.setter
- def local_build_config(self, build_config: BuildConfig) -> None:
- self._local_build_config = build_config
- self._local_build_config.on_work_init(self)
-
- @property
- def cloud_build_config(self) -> BuildConfig:
- """Returns the cloud build config used to prepare the selected cloud hardware."""
- return self._cloud_build_config
-
- @cloud_build_config.setter
- def cloud_build_config(self, build_config: BuildConfig) -> None:
- self._cloud_build_config = build_config
- self._cloud_build_config.on_work_init(self, cloud_compute=self._cloud_compute)
-
- @property
- def cloud_compute(self) -> CloudCompute:
- return self._cloud_compute
-
- @cloud_compute.setter
- def cloud_compute(self, cloud_compute: CloudCompute) -> None:
- """Returns the cloud compute used to select the cloud hardware."""
- # A new ID
- current_id = self._cloud_compute.id
- new_id = cloud_compute.id
- if current_id != new_id:
- compute_store: _CloudComputeStore = _CLOUD_COMPUTE_STORE[current_id]
- compute_store.remove(self.name)
- self._cloud_compute = cloud_compute
-
- @property
- def lightningignore(self) -> Tuple[str, ...]:
- """Programmatic equivalent of the ``.lightningignore`` file."""
- return self._lightningignore
-
- @lightningignore.setter
- def lightningignore(self, lightningignore: Tuple[str, ...]) -> None:
- if self._backend is not None:
- raise RuntimeError(
- f"Your app has been already dispatched, so modifying the `{self.name}.lightningignore` does not have an"
- " effect"
- )
- self._lightningignore = lightningignore
-
- @property
- def status(self) -> WorkStatus:
- """Return the current status of the work.
-
- All statuses are stored in the state.
-
- """
- call_hash = self._calls[CacheCallsKeys.LATEST_CALL_HASH]
- if call_hash in self._calls:
- statuses = self._calls[call_hash]["statuses"]
- # deltas aren't necessarily coming in the expected order.
- statuses = sorted(statuses, key=lambda x: x["timestamp"])
- latest_status = statuses[-1]
- if latest_status.get("reason") == WorkFailureReasons.TIMEOUT:
- return self._aggregate_status_timeout(statuses)
- return WorkStatus(**latest_status)
- return WorkStatus(stage=WorkStageStatus.NOT_STARTED, timestamp=time.time())
-
- @property
- def statuses(self) -> List[WorkStatus]:
- """Return all the status of the work."""
- call_hash = self._calls[CacheCallsKeys.LATEST_CALL_HASH]
- if call_hash in self._calls:
- statuses = self._calls[call_hash]["statuses"]
- # deltas aren't necessarily coming in the expected order.
- statuses = sorted(statuses, key=lambda x: x["timestamp"])
- return [WorkStatus(**status) for status in statuses]
- return []
-
- @property
- def has_started(self) -> bool:
- """Return whether the work has started."""
- return self.status.stage != WorkStageStatus.NOT_STARTED
-
- @property
- def has_stopped(self) -> bool:
- """Return whether the work has stopped."""
- return self.status.stage == WorkStageStatus.STOPPED
-
- @property
- def has_succeeded(self) -> bool:
- """Return whether the work has succeeded."""
- return self.status.stage == WorkStageStatus.SUCCEEDED
-
- @property
- def has_failed(self) -> bool:
- """Return whether the work has failed."""
- return self.status.stage == WorkStageStatus.FAILED
-
- @property
- def has_timeout(self) -> bool:
- """Return whether the work has time-out."""
- return self.has_failed and self.status.reason == WorkFailureReasons.TIMEOUT
-
- @property
- def is_running(self) -> bool:
- """Return whether the work is running."""
- return self.status.stage == WorkStageStatus.RUNNING
-
- @property
- def is_pending(self) -> bool:
- """Return whether the work is pending."""
- return self.status.stage == WorkStageStatus.PENDING
-
- @property
- def num_timeouts(self) -> int:
- """Return the number of timeout status since the lastest succeeded run."""
- status = self.status
- if status.reason == WorkFailureReasons.TIMEOUT:
- return status.count
- return 0
-
- @property
- def num_successes(self) -> int:
- """Returns the number of successful runs."""
- # FIXME: Resolve this within single process runtime.
- run_keys = [key for key in self._calls if key.startswith("run:")]
- if not run_keys:
- return 0
-
- has_succeeded_counter = 0
- for run_key in run_keys:
- c = len([s for s in self._calls[run_key]["statuses"] if s["stage"] == WorkStageStatus.SUCCEEDED])
- has_succeeded_counter += c
-
- return has_succeeded_counter
-
- def _get_property_if_exists(self, name: str) -> Union[property, None]:
- attr = getattr(self.__class__, name, None)
- return attr if isinstance(attr, property) else None
-
- def __setattr__(self, name: str, value: Any) -> None:
- property_object = self._get_property_if_exists(name)
- if property_object is not None and property_object.fset is not None:
- property_object.fset(self, value)
- else:
- setattr_fn = getattr(self, "_setattr_replacement", None) or self._default_setattr
- setattr_fn(name, value)
-
- def _default_setattr(self, name: str, value: Any) -> None:
- from lightning.app.core.flow import LightningFlow
-
- # Allow the run method to be patched with ProxyWorkRun (done by certain Runtime implementations).
- allowed_to_set_run = name == "run" and (
- isinstance(value, ProxyWorkRun)
- or (unwrap(value) == unwrap(self.run))
- or (isinstance(value, partial) and value.func.__name__ == "_dynamic_run_wrapper")
- )
-
- is_proxy_setattr = isinstance(value, LightningWorkSetAttrProxy)
- is_init_context = _is_init_context(self)
-
- if (
- not is_init_context
- and name not in self._state
- and name not in self._paths
- and self._is_state_attribute(name)
- and not allowed_to_set_run
- ):
- raise AttributeError(f"Cannot set attributes that were not defined in __init__: {name}.")
-
- if isinstance(value, str) and value.startswith("lit://"):
- value = Path(value)
-
- if self._is_state_attribute(name):
- if isinstance(value, (LightningFlow, LightningWork)):
- raise LightningWorkException(
- "A ``LightningWork`` isn't allowed to take any children "
- f"such as ``LightningWork`` or ``LightningFlow``. Found {value}."
- )
-
- if isinstance(value, Path):
- value._attach_work(work=self)
- value._attach_queues(self._request_queue, self._response_queue) # type: ignore[arg-type]
- value._name = name
- # In the init context, the full name of the Flow and Work is not known, i.e., we can't serialize
- # the path without losing the information of origin and consumer. Hence, we delay the serialization
- # of the path object until the app is instantiated.
- if not is_init_context:
- self._paths[name] = value.to_dict()
- self._state.add(name)
-
- elif isinstance(value, Payload):
- if is_init_context:
- raise AttributeError("The Payload object should be set only within the run method of the work.")
- value._attach_work(work=self)
- value._name = name
- self._state.add(name)
-
- elif isinstance(value, Drive):
- value = deepcopy(value)
- value.component_name = self.name
- self._state.add(name)
-
- elif allowed_to_set_run or is_proxy_setattr:
- # enable overriding the run method (dispatcher)
- pass
-
- elif _is_json_serializable(value):
- self._state.add(name)
-
- else:
- raise AttributeError(
- f"Only JSON-serializable attributes are currently supported"
- f" (str, int, float, bool, tuple, list, dict etc.) to be part of {self} state. "
- f"Found the attribute {name} with {value} instead. \n"
- "HINT: Private attributes defined as follows `self._x = y` won't be shared between components "
- "and therefore don't need to be JSON-serializable. If you need to include non-JSON serializable "
- "objects in the state, you can use the `lightning.app.storage.Payload` API."
- )
-
- super().__setattr__(name, value)
-
- def __getattribute__(self, name: str) -> Any:
- try:
- attr = object.__getattribute__(self, name)
- except AttributeError as ex:
- if str(ex).endswith("'_state'"):
- raise AttributeError(f"Did you forget to call super().__init__() in {self}")
- raise ex
-
- if isinstance(attr, ProxyWorkRun):
- return attr
-
- if callable(attr) and getattr(attr, "__name__", "") == "run" and getattr(self, "_cache_calls", False):
- # disable while building the class.
- return self._wrap_run_for_caching(attr)
- return attr
-
- def __getattr__(self, item: str) -> Any:
- if item in self.__dict__.get("_paths", {}) and not _is_init_context(self):
- path = Path.from_dict(self._paths[item])
- path._attach_work(work=self)
- path._attach_queues(self._request_queue, self._response_queue) # type: ignore[arg-type]
- return path
- return self.__getattribute__(item)
-
- def _call_hash(self, fn: Callable, args: Any, kwargs: Any) -> str:
- hash_args = args[1:] if len(args) > 0 and args[0] == self else args
- call_obj = {"args": hash_args, "kwargs": kwargs}
- # Note: Generate a hash as 167fe2e.
- # Seven was selected after checking upon Github default SHA length
- # and to minimize hidden state size.
- return str(DeepHash(call_obj)[call_obj])[:7]
-
- def _wrap_run_for_caching(self, fn: Callable) -> Callable:
- @wraps(fn)
- def new_fn(*args: Any, **kwargs: Any) -> Any:
- call_hash = self._call_hash(fn, args, kwargs)
-
- entered = call_hash in self._calls
- returned = entered and "ret" in self._calls[call_hash]
-
- if returned:
- entry = self._calls[call_hash]
- return entry["ret"]
-
- self._calls[call_hash] = {}
-
- result = fn(*args, **kwargs)
-
- self._calls[call_hash] = {"ret": result}
-
- return result
-
- return new_fn
-
- @property
- def changes(self) -> dict:
- return self._changes.copy()
-
- @property
- def state(self) -> dict:
- """Returns the current state of this LightningWork."""
- return {
- "vars": _sanitize_state({el: getattr(self, el) for el in self._state}),
- # this may have the challenge that ret cannot be pickled, we'll need to handle this
- "calls": self._calls.copy(),
- "changes": {},
- }
-
- @property
- def state_vars(self) -> dict:
- return {"vars": _sanitize_state({el: getattr(self, el) for el in self._state})}
-
- @property
- def state_with_changes(self) -> dict:
- return {
- "vars": _sanitize_state({el: getattr(self, el) for el in self._state}),
- # this may have the challenge that ret cannot be pickled, we'll need to handle this
- "calls": self._calls.copy(),
- "changes": self.changes,
- }
-
- def set_state(self, provided_state: dict) -> None:
- for k, v in provided_state["vars"].items():
- if isinstance(v, Dict):
- v = _maybe_create_drive(self.name, v)
- if isinstance(v, Dict):
- v = _maybe_create_cloud_compute(v)
- setattr(self, k, v)
-
- self._changes = provided_state["changes"]
-
- # Note, this is handled by the flow only.
- if _is_flow_context():
- self._cleanup_calls(provided_state["calls"])
-
- self._calls = provided_state["calls"]
-
- @staticmethod
- def _cleanup_calls(calls: Dict[str, Any]) -> None:
- # 1: Collect all the in_progress call hashes
- in_progress_call_hash = [k for k in list(calls) if k not in (CacheCallsKeys.LATEST_CALL_HASH)]
-
- for call_hash in in_progress_call_hash:
- if "statuses" not in calls[call_hash]:
- continue
-
- # 2: Filter the statuses by timestamp
- statuses = sorted(calls[call_hash]["statuses"], key=lambda x: x["timestamp"])
-
- # If the latest status is succeeded, then drop everything before.
- if statuses[-1]["stage"] == WorkStageStatus.SUCCEEDED:
- status = statuses[-1]
- status["timestamp"] = int(status["timestamp"])
- calls[call_hash]["statuses"] = [status]
- else:
- # TODO: Some status are being duplicated,
- # this seems related to the StateObserver.
- final_statuses = []
- for status in statuses:
- if status not in final_statuses:
- final_statuses.append(status)
- calls[call_hash]["statuses"] = final_statuses
-
- def start(self) -> None:
- """Starts LightingWork component via CloudCompute."""
- if self.status.stage == WorkStageStatus.STOPPED:
- raise Exception("A work can be started only once for now.")
-
- # This enables to start the run method with a phony input and exit.
- self.run(Action(method="start"))
-
- def on_start(self) -> None:
- """Define actions to perform when the work has started."""
-
- def run(self, *args: Any, **kwargs: Any) -> None:
- """Override to add your own logic.
-
- Raises:
- LightningPlatformException: If resource exceeds platform quotas or other constraints.
-
- """
-
- def on_exception(self, exception: BaseException) -> None:
- """Override to customize how to handle exception in the run method."""
- if self._raise_exception:
- raise exception
-
- def _aggregate_status_timeout(self, statuses: List[Dict]) -> WorkStatus:
- """Method used to return the first request and the total count of timeout after the latest succeeded status."""
- succeeded_statuses = [
- status_idx for status_idx, status in enumerate(statuses) if status["stage"] == WorkStageStatus.SUCCEEDED
- ]
- if succeeded_statuses:
- succeed_status_id = succeeded_statuses[-1] + 1
- statuses = statuses[succeed_status_id:]
- timeout_statuses = [status for status in statuses if status.get("reason") == WorkFailureReasons.TIMEOUT]
- assert statuses[0]["stage"] == WorkStageStatus.PENDING
- status = {**timeout_statuses[-1], "timestamp": statuses[0]["timestamp"]}
- return WorkStatus(**status, count=len(timeout_statuses))
-
- def on_exit(self) -> None:
- """Override this hook to add your logic when the work is exiting.
-
- Note: This hook is not guaranteed to be called when running in the cloud.
-
- """
- pass
-
- def stop(self) -> None:
- """Stops LightingWork component and shuts down hardware provisioned via CloudCompute.
-
- This can only be called from a ``LightningFlow``.
-
- """
- if not self._backend:
- raise RuntimeError(f"Only the `LightningFlow` can request this work ({self.name!r}) to stop.")
- if self.status.stage == WorkStageStatus.STOPPED:
- return
- latest_hash = self._calls[CacheCallsKeys.LATEST_CALL_HASH]
- stop_status = make_status(WorkStageStatus.STOPPED, reason=WorkStopReasons.PENDING)
- self._calls[latest_hash]["statuses"].append(stop_status)
- app = _LightningAppRef().get_current()
- self._backend.stop_work(app, self) # type: ignore[arg-type]
-
- def delete(self) -> None:
- """Delete LightingWork component and shuts down hardware provisioned via CloudCompute.
-
- Locally, the work.delete() behaves as work.stop().
-
- """
- if not self._backend:
- raise Exception(
- "Can't delete the work, it looks like it isn't attached to a LightningFlow. "
- "Make sure to assign the Work to a flow instance."
- )
- app = _LightningAppRef().get_current()
- self._backend.delete_work(app, self)
-
- def _check_run_is_implemented(self) -> None:
- if not is_overridden("run", instance=self, parent=LightningWork):
- raise TypeError(
- f"The work `{self.__class__.__name__}` is missing the `run()` method. This is required. Implement it"
- " first and then call it in your Flow."
- )
-
- def _register_cloud_compute(self) -> None:
- internal_id = self.cloud_compute.id
- assert internal_id
- if internal_id not in _CLOUD_COMPUTE_STORE:
- _CLOUD_COMPUTE_STORE[internal_id] = _CloudComputeStore(id=internal_id, component_names=[])
- _CLOUD_COMPUTE_STORE[internal_id].add_component_name(self.name)
-
- def apply_flow_delta(self, delta: Delta) -> None:
- """Override to customize how the flow should update the work state."""
- # TODO: Add support for thread safe locking over JSON Serializable objects.
- if any(k not in ["values_changed", "type_changed"] for k in delta.to_dict()):
- raise Exception(
- "A forbidden operation to update the work from the flow was detected."
- f" Found {delta.to_dict()}, only `values_changed` and `type_changes` are currently allowed."
- )
-
- vars = self.state["vars"] + delta
- for name, value in vars.items():
- property_object = self._get_property_if_exists(name)
- if property_object is not None and property_object.fset is not None:
- property_object.fset(self, value)
- else:
- self._default_setattr(name, value)
-
- def configure_layout(self) -> Union[None, str, "Frontend"]:
- """Configure the UI of this LightningWork.
-
- You can either
-
- 1. Return a single :class:`~lightning.app.frontend.frontend.Frontend` object to serve a user interface
- for this Work.
- 2. Return a string containing a URL to act as the user interface for this Work.
- 3. Return ``None`` to indicate that this Work doesn't currently have a user interface.
-
- **Example:** Serve a static directory (with at least a file index.html inside).
-
- .. code-block:: python
-
- from lightning.app.frontend import StaticWebFrontend
-
-
- class Work(LightningWork):
- def configure_layout(self):
- return StaticWebFrontend("path/to/folder/to/serve")
-
- **Example:** Arrange the UI of my children in tabs (default UI by Lightning).
-
- .. code-block:: python
-
- class Work(LightningWork):
- def configure_layout(self):
- return [
- dict(name="First Tab", content=self.child0),
- dict(name="Second Tab", content=self.child1),
- dict(name="Lightning", content="https://lightning.ai"),
- ]
-
- If you don't implement ``configure_layout``, Lightning will use ``self.url``.
-
- Note:
- This hook gets called at the time of app creation and then again as part of the loop. If desired, a
- returned URL can depend on the state. This is not the case if the work returns a
- :class:`~lightning.app.frontend.frontend.Frontend`. These need to be provided at the time of app creation
- in order for the runtime to start the server.
-
- """
diff --git a/src/lightning/app/frontend/__init__.py b/src/lightning/app/frontend/__init__.py
deleted file mode 100644
index b4f5f5a1ba022..0000000000000
--- a/src/lightning/app/frontend/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from lightning.app.frontend.frontend import Frontend
-from lightning.app.frontend.just_py.just_py import JustPyFrontend
-from lightning.app.frontend.panel import AppStateWatcher, PanelFrontend
-from lightning.app.frontend.stream_lit import StreamlitFrontend
-from lightning.app.frontend.web import StaticWebFrontend
-
-__all__ = ["AppStateWatcher", "Frontend", "JustPyFrontend", "PanelFrontend", "StaticWebFrontend", "StreamlitFrontend"]
diff --git a/src/lightning/app/frontend/frontend.py b/src/lightning/app/frontend/frontend.py
deleted file mode 100644
index fef945f6ff690..0000000000000
--- a/src/lightning/app/frontend/frontend.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from abc import ABC, abstractmethod
-from typing import TYPE_CHECKING, Optional
-
-if TYPE_CHECKING:
- from lightning.app.core.flow import LightningFlow
-
-
-class Frontend(ABC):
- """Base class for any frontend that gets exposed by LightningFlows.
-
- The flow attribute will be set by the app while bootstrapping.
-
- """
-
- def __init__(self) -> None:
- self.flow: Optional["LightningFlow"] = None
-
- @abstractmethod
- def start_server(self, host: str, port: int, root_path: str = "") -> None:
- """Start the process that serves the UI at the given hostname and port number.
-
- Arguments:
- host: The hostname where the UI will be served. This gets determined by the dispatcher (e.g., cloud),
- but defaults to localhost when running locally.
- port: The port number where the UI will be served. This gets determined by the dispatcher, which by default
- chooses any free port when running locally.
- root_path: root_path for the server if app in exposed via a proxy at `/`
-
-
- Example:
-
- An custom implementation could look like this:
-
- .. code-block:: python
-
- def start_server(self, host, port, root_path=""):
- self._process = subprocess.Popen(["flask", "run" "--host", host, "--port", str(port)])
-
- """
-
- @abstractmethod
- def stop_server(self) -> None:
- """Stop the process that was started with :meth:`start_server` so the App can shut down.
-
- This method gets called when the LightningApp terminates.
-
- Example:
-
- .. code-block:: python
-
- def stop_server(self):
- self._process.kill()
-
- """
diff --git a/src/lightning/app/frontend/just_py/__init__.py b/src/lightning/app/frontend/just_py/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/frontend/just_py/just_py.py b/src/lightning/app/frontend/just_py/just_py.py
deleted file mode 100644
index 11a9d55799544..0000000000000
--- a/src/lightning/app/frontend/just_py/just_py.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import inspect
-import os
-import sys
-from subprocess import Popen
-from time import sleep
-from typing import Callable, Optional
-
-import lightning.app
-from lightning.app.frontend.frontend import Frontend
-from lightning.app.utilities.log import get_logfile
-
-
-class JustPyFrontend(Frontend):
- """A frontend for wrapping JustPy code in your LightingFlow.
-
- Return this in your `LightningFlow.configure_layout()` method if you wish to build the UI with ``justpy``.
- To use this frontend, you must first install the `justpy` package (if running locally):
-
- .. code-block:: bash
-
- pip install justpy
-
- Arguments:
- render_fn: A function that contains your justpy code. This function must accept exactly one argument, the
- ``AppState`` object which you can use to access variables in your flow (see example below).
-
- Example:
-
- In your LightningFlow, override the method `configure_layout`:
-
- .. code-block:: python
-
- from typing import Callable
- from lightning import LightningApp, LightningFlow
- from lightning.app.frontend import JustPyFrontend
-
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- print(self.counter)
-
- def configure_layout(self):
- return JustPyFrontend(render_fn=render_fn)
-
-
- def render_fn(get_state: Callable) -> Callable:
- import justpy as jp
-
- def my_click(self, *_):
- state = get_state()
- old_counter = state.counter
- state.counter += 1
- self.text = f"Click Me ! Old Counter: {old_counter} New Counter: {state.counter}"
-
- def webpage():
- wp = jp.WebPage()
- d = jp.Div(text="Hello ! Click Me!")
- d.on("click", my_click)
- wp.add(d)
- return wp
-
- return webpage
-
-
- app = LightningApp(Flow())
-
- """
-
- def __init__(self, render_fn: Callable) -> None:
- super().__init__()
-
- if inspect.ismethod(render_fn):
- raise TypeError(
- "The `JustPyFrontend` doesn't support `render_fn` being a method. Please, use a pure function."
- )
-
- self.render_fn = render_fn
- self._process: Optional[Popen] = None
-
- def start_server(self, host: str, port: int, root_path: str = "") -> None:
- env = os.environ.copy()
- env["LIGHTNING_FLOW_NAME"] = self.flow.name # type: ignore
- env["LIGHTNING_RENDER_FUNCTION"] = self.render_fn.__name__
- env["LIGHTNING_RENDER_MODULE_FILE"] = inspect.getmodule(self.render_fn).__file__ # type: ignore
- env["LIGHTNING_HOST"] = host
- env["LIGHTNING_PORT"] = str(port)
- std_out_out = get_logfile("output.log")
- path = os.path.join(os.path.dirname(lightning.app.frontend.just_py.__file__), "just_py_base.py")
- with open(std_out_out, "wb") as stdout:
- self._process = Popen(f"{sys.executable} {path}", env=env, stdout=stdout, stderr=sys.stderr, shell=True)
-
- sleep(1)
-
- def stop_server(self) -> None:
- assert self._process
- self._process.terminate()
diff --git a/src/lightning/app/frontend/just_py/just_py_base.py b/src/lightning/app/frontend/just_py/just_py_base.py
deleted file mode 100644
index ce6d009ef7905..0000000000000
--- a/src/lightning/app/frontend/just_py/just_py_base.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import pydoc
-from typing import Any, Callable
-
-from lightning.app.frontend.utils import _reduce_to_flow_scope
-from lightning.app.utilities.state import AppState
-
-
-def _get_state() -> AppState:
- app_state = AppState()
- return _reduce_to_flow_scope(app_state, flow=os.environ["LIGHTNING_FLOW_NAME"])
-
-
-def _webpage() -> Any:
- import justpy as jp
-
- wp = jp.WebPage()
- d = jp.Div(text="")
- wp.add(d)
- return wp
-
-
-def _get_render_fn_from_environment() -> Callable:
- render_fn_name = os.environ["LIGHTNING_RENDER_FUNCTION"]
- render_fn_module_file = os.environ["LIGHTNING_RENDER_MODULE_FILE"]
- module = pydoc.importfile(render_fn_module_file)
- return getattr(module, render_fn_name)
-
-
-def _main() -> None:
- """Run the render_fn with the current flow_state."""
- import justpy as jp
-
- # Fetch the information of which flow attaches to this justpy instance
- flow_name = os.environ["LIGHTNING_FLOW_NAME"]
-
- # Call the provided render function.
- # Pass it the state, scoped to the current flow.
- render_fn = _get_render_fn_from_environment()
- host = os.environ["LIGHTNING_HOST"]
- port = int(os.environ["LIGHTNING_PORT"])
- entry_fn = render_fn(_get_state)
- if not isinstance(entry_fn, Callable): # type: ignore
- raise Exception("You need to return a function with JustPy Frontend.")
-
- jp.app.add_jproute(f"/{flow_name}", entry_fn)
-
- jp.justpy(_webpage, host=host, port=port)
-
-
-if __name__ == "__main__":
- _main()
diff --git a/src/lightning/app/frontend/panel/__init__.py b/src/lightning/app/frontend/panel/__init__.py
deleted file mode 100644
index 8d11a3d2815c4..0000000000000
--- a/src/lightning/app/frontend/panel/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""The PanelFrontend and AppStateWatcher make it easy to create Lightning Apps with the Panel data app framework."""
-
-from lightning.app.frontend.panel.app_state_watcher import AppStateWatcher
-from lightning.app.frontend.panel.panel_frontend import PanelFrontend
-
-__all__ = ["AppStateWatcher", "PanelFrontend"]
diff --git a/src/lightning/app/frontend/panel/app_state_comm.py b/src/lightning/app/frontend/panel/app_state_comm.py
deleted file mode 100644
index ae3316167d4a4..0000000000000
--- a/src/lightning/app/frontend/panel/app_state_comm.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""The watch_app_state function enables us to trigger a callback function when ever the app state changes."""
-
-# Todo: Refactor with Streamlit
-# Note: It would be nice one day to just watch changes within the Flow scope instead of whole app
-from __future__ import annotations
-
-import asyncio
-import os
-from threading import Thread
-from typing import Callable
-
-import websockets
-
-from lightning.app.core.constants import APP_SERVER_PORT
-from lightning.app.utilities.app_helpers import Logger
-
-_logger = Logger(__name__)
-
-_CALLBACKS = []
-_THREAD: Thread = None
-
-
-def _get_ws_port():
- if "LIGHTNING_APP_STATE_URL" in os.environ:
- return 8080
- return APP_SERVER_PORT
-
-
-def _get_ws_url():
- port = _get_ws_port()
- return f"ws://localhost:{port}/api/v1/ws"
-
-
-def _run_callbacks():
- for callback in _CALLBACKS:
- callback()
-
-
-def _target_fn():
- async def update_fn():
- ws_url = _get_ws_url()
- _logger.debug("connecting to web socket %s", ws_url)
- async with websockets.connect(ws_url) as websocket: # pylint: disable=no-member
- while True:
- await websocket.recv()
- # Note: I have not seen use cases where the two lines below are needed
- # Changing '< 0.2' to '< 1' makes the App very sluggish to the end user
- # Also the implementation can cause the App state to lag behind because only 1 update
- # is received per 0.2 second (or 1 second).
- # while (time.time() - last_updated) < 0.2:
- # time.sleep(0.05)
-
- # Todo: Add some kind of throttling. If 10 messages are received within 100ms then
- # there is no need to trigger the app state changed, request state and update
- # 10 times.
- _logger.debug("App State Changed. Running callbacks")
- _run_callbacks()
-
- asyncio.run(update_fn())
-
-
-def _start_websocket():
- global _THREAD # pylint: disable=global-statement
- if not _THREAD:
- _logger.debug("Starting the watch_app_state thread.")
- _THREAD = Thread(target=_target_fn)
- _THREAD.setDaemon(True)
- _THREAD.start()
- _logger.debug("thread started")
-
-
-def _watch_app_state(callback: Callable):
- """Start the process that serves the UI at the given hostname and port number.
-
- Arguments:
- callback: A function to run when the App state changes. Must be thread safe.
-
- Example:
-
- .. code-block:: python
-
- def handle_state_change():
- print("The App State changed.")
- watch_app_state(handle_state_change)
-
- """
- _CALLBACKS.append(callback)
- _start_websocket()
diff --git a/src/lightning/app/frontend/panel/app_state_watcher.py b/src/lightning/app/frontend/panel/app_state_watcher.py
deleted file mode 100644
index 8abc9cb52e272..0000000000000
--- a/src/lightning/app/frontend/panel/app_state_watcher.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""The ``AppStateWatcher`` enables a Frontend to:
-
-- subscribe to App state changes
-- to access and change the App state.
-
-This is particularly useful for the ``PanelFrontend`` but can be used by other frontends too.
-
-"""
-
-from __future__ import annotations
-
-import os
-
-from lightning.app.frontend.panel.app_state_comm import _watch_app_state
-from lightning.app.frontend.utils import _get_flow_state
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.imports import _is_param_available, requires
-from lightning.app.utilities.state import AppState
-
-_logger = Logger(__name__)
-
-
-if _is_param_available():
- from param import ClassSelector, Parameterized, edit_constant
-else:
- Parameterized = object
- ClassSelector = dict
-
-
-class AppStateWatcher(Parameterized):
- """The `AppStateWatcher` enables a Frontend to:
-
- - Subscribe to any App state changes.
- - To access and change the App state from the UI.
-
- This is particularly useful for the `PanelFrontend , but can be used by
- other frontends too.
-
- Example
- -------
-
- .. code-block:: python
-
- import param
-
- app = AppStateWatcher()
-
- app.state.counter = 1
-
-
- @param.depends(app.param.state, watch=True)
- def update(state):
- print(f"The counter was updated to {state.counter}")
-
-
- app.state.counter += 1
-
- This would print ``The counter was updated to 2``.
-
- The ``AppStateWatcher`` is built on top of Param, which is a framework like dataclass, attrs and
- Pydantic which additionally provides powerful and unique features for building reactive apps.
-
- Please note the ``AppStateWatcher`` is a singleton, i.e., only one instance is instantiated
-
- """
-
- state: AppState = ClassSelector(
- class_=AppState,
- constant=True,
- doc="The AppState holds the state of the app reduced to the scope of the Flow",
- )
-
- def __new__(cls):
- # This makes the AppStateWatcher a *singleton*.
- # The AppStateWatcher is a singleton to minimize the number of requests etc..
- if not hasattr(cls, "_instance"):
- cls._instance = super().__new__(cls)
- return cls._instance
-
- @requires("param")
- def __init__(self):
- # It is critical to initialize only once
- # See https://github.com/holoviz/param/issues/643
- if not hasattr(self, "_initialized"):
- super().__init__(name="singleton")
- self._start_watching()
- self.param.state.allow_None = False
- self._initialized = True
-
- # The below was observed when using mocks during testing
- if not self.state:
- raise Exception(".state has not been set.")
- if not self.state._state:
- raise Exception(".state._state has not been set.")
-
- def _start_watching(self):
- # Create a thread listening to state changes.
- _watch_app_state(self._update_flow_state)
- self._update_flow_state()
-
- def _get_flow_state(self) -> AppState:
- flow = os.environ["LIGHTNING_FLOW_NAME"]
- return _get_flow_state(flow)
-
- def _update_flow_state(self):
- # Todo: Consider whether to only update if ._state changed
- # This might be much more performant.
- with edit_constant(self):
- self.state = self._get_flow_state()
- _logger.debug("Requested App State.")
diff --git a/src/lightning/app/frontend/panel/panel_frontend.py b/src/lightning/app/frontend/panel/panel_frontend.py
deleted file mode 100644
index 6185ab7ce6429..0000000000000
--- a/src/lightning/app/frontend/panel/panel_frontend.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""The PanelFrontend wraps your Panel code in your LightningFlow."""
-
-from __future__ import annotations
-
-import inspect
-import os
-import pathlib
-import subprocess
-import sys
-from typing import Callable, TextIO
-
-from lightning.app.frontend.frontend import Frontend
-from lightning.app.frontend.utils import _get_frontend_environment
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cloud import is_running_in_cloud
-from lightning.app.utilities.imports import requires
-from lightning.app.utilities.log import get_logfile
-
-_logger = Logger(__name__)
-
-
-def _has_panel_autoreload() -> bool:
- """Returns True if the PANEL_AUTORELOAD environment variable is set to 'yes' or 'true'.
-
- Please note the casing of value does not matter
-
- """
- return os.environ.get("PANEL_AUTORELOAD", "no").lower() in ["yes", "y", "true"]
-
-
-class PanelFrontend(Frontend):
- """The `PanelFrontend` enables you to serve Panel code as a Frontend for your LightningFlow.
-
- Reference: https://lightning.ai/lightning-docs/workflows/add_web_ui/panel/
-
- Args:
- entry_point: The path to a .py or .ipynb file, or a pure function. The file or function must contain your Panel
- code. The function can optionally accept an ``AppStateWatcher`` argument.
-
- Raises:
- TypeError: Raised if the ``entry_point`` provided is a class method
-
- Example:
-
- To use the `PanelFrontend`, you must first install the `panel` package:
-
- .. code-block:: bash
-
- pip install panel
-
- Create the files `panel_app_basic.py` and `app_basic.py` with the content below.
-
- **panel_app_basic.py**
-
- .. code-block:: python
-
- import panel as pn
-
- pn.panel("Hello **Panel ⚡** World").servable()
-
- **app_basic.py**
-
- .. code-block:: python
-
- from lightning.app import LightningFlow, LightningApp
- from lightning.app.frontend.panel import PanelFrontend
-
-
- class LitPanel(LightningFlow):
- def configure_layout(self):
- return PanelFrontend("panel_app_basic.py")
-
-
- class LitApp(LightningFlow):
- def __init__(self):
- super().__init__()
- self.lit_panel = LitPanel()
-
- def configure_layout(self):
- return {"name": "home", "content": self.lit_panel}
-
-
- app = LightningApp(LitApp())
-
- Start the Lightning server with `lightning run app app_basic.py`.
-
- For development you can get Panel autoreload by setting the ``PANEL_AUTORELOAD``
- environment variable to 'yes', i.e. run
- ``PANEL_AUTORELOAD=yes lightning run app app_basic.py``
-
- """
-
- @requires("panel")
- def __init__(self, entry_point: str | Callable):
- super().__init__()
-
- if inspect.ismethod(entry_point):
- raise TypeError(
- "The `PanelFrontend` doesn't support `entry_point` being a method. Please, use a pure function."
- )
-
- self.entry_point = entry_point
- self._process: None | subprocess.Popen = None
- self._log_files: dict[str, TextIO] = {}
- _logger.debug("PanelFrontend Frontend with %s is initialized.", entry_point)
-
- def start_server(self, host: str, port: int, root_path: str = "") -> None:
- _logger.debug("PanelFrontend starting server on %s:%s", host, port)
-
- # 1: Prepare environment variables and arguments.
- env = _get_frontend_environment(
- self.flow.name,
- self.entry_point,
- port,
- host,
- )
- command = self._get_popen_args(host, port)
-
- if is_running_in_cloud():
- self._open_log_files()
-
- self._process = subprocess.Popen(command, env=env, **self._log_files) # pylint: disable=consider-using-with
-
- def stop_server(self) -> None:
- if self._process is None:
- raise RuntimeError("Server is not running. Call `PanelFrontend.start_server()` first.")
- self._process.kill()
- self._close_log_files()
-
- def _close_log_files(self):
- for file_ in self._log_files.values():
- if not file_.closed:
- file_.close()
- self._log_files = {}
-
- def _open_log_files(self) -> None:
- # Don't log to file when developing locally. Makes it harder to debug.
- self._close_log_files()
-
- std_err_out = get_logfile("error.log")
- std_out_out = get_logfile("output.log")
- stderr = std_err_out.open("wb")
- stdout = std_out_out.open("wb")
- self._log_files = {"stdout": stderr, "stderr": stdout}
-
- def _get_popen_args(self, host: str, port: int) -> list:
- if callable(self.entry_point):
- path = str(pathlib.Path(__file__).parent / "panel_serve_render_fn.py")
- else:
- path = pathlib.Path(self.entry_point)
-
- abs_path = str(path)
- # The app is served at http://localhost:{port}/{flow}/{entry_point}
- # Lightning embeds http://localhost:{port}/{flow} but this redirects to the above and
- # seems to work fine.
- command = [
- sys.executable,
- "-m",
- "panel",
- "serve",
- abs_path,
- "--port",
- str(port),
- "--address",
- host,
- "--prefix",
- self.flow.name,
- "--allow-websocket-origin",
- _get_allowed_hosts(),
- ]
- if _has_panel_autoreload():
- command.append("--autoreload")
- _logger.debug("PanelFrontend command %s", command)
- return command
-
-
-def _get_allowed_hosts() -> str:
- """Returns a comma separated list of host[:port] that should be allowed to connect."""
- # TODO: Enable only lightning.ai domain in the cloud
- return "*"
diff --git a/src/lightning/app/frontend/panel/panel_serve_render_fn.py b/src/lightning/app/frontend/panel/panel_serve_render_fn.py
deleted file mode 100644
index 4ba8de45813a0..0000000000000
--- a/src/lightning/app/frontend/panel/panel_serve_render_fn.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""This file gets run by Python to launch a Panel Server with Lightning.
-
-We will call the ``render_fn`` that the user provided to the PanelFrontend.
-
-It requires the following environment variables to be set
-
-
-- LIGHTNING_RENDER_FUNCTION
-- LIGHTNING_RENDER_MODULE_FILE
-
-Example:
-
-.. code-block:: bash
-
- python panel_serve_render_fn
-
-"""
-
-import inspect
-import os
-import pydoc
-from typing import Callable
-
-from lightning.app.frontend.panel.app_state_watcher import AppStateWatcher
-
-
-def _get_render_fn_from_environment(render_fn_name: str, render_fn_module_file: str) -> Callable:
- """Returns the render_fn function to serve in the Frontend."""
- module = pydoc.importfile(render_fn_module_file)
- return getattr(module, render_fn_name)
-
-
-def _get_render_fn():
- render_fn_name = os.environ["LIGHTNING_RENDER_FUNCTION"]
- render_fn_module_file = os.environ["LIGHTNING_RENDER_MODULE_FILE"]
- render_fn = _get_render_fn_from_environment(render_fn_name, render_fn_module_file)
- if inspect.signature(render_fn).parameters:
-
- def _render_fn_wrapper():
- app = AppStateWatcher()
- return render_fn(app)
-
- return _render_fn_wrapper
- return render_fn
-
-
-def _main():
- import panel as pn
-
- # I use caching for efficiency reasons. It shaves off 10ms from having
- # to get_render_fn_from_environment every time
- if "lightning_render_fn" not in pn.state.cache:
- pn.state.cache["lightning_render_fn"] = _get_render_fn()
- pn.state.cache["lightning_render_fn"]()
-
-
-if __name__.startswith("bokeh"):
- _main()
diff --git a/src/lightning/app/frontend/stream_lit.py b/src/lightning/app/frontend/stream_lit.py
deleted file mode 100644
index 0cdc37296d931..0000000000000
--- a/src/lightning/app/frontend/stream_lit.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import inspect
-import os
-import subprocess
-import sys
-from typing import Callable, Optional
-
-import lightning.app
-from lightning.app.frontend.frontend import Frontend
-from lightning.app.utilities.cloud import is_running_in_cloud
-from lightning.app.utilities.imports import requires
-from lightning.app.utilities.log import get_logfile
-
-
-class StreamlitFrontend(Frontend):
- """A frontend for wrapping Streamlit code in your LightingFlow.
-
- Return this in your `LightningFlow.configure_layout()` method if you wish to build the UI with ``streamlit``.
- To use this frontend, you must first install the `streamlit` package (if running locally):
-
- .. code-block:: bash
-
- pip install streamlit
-
- Arguments:
- render_fn: A function that contains your streamlit code. This function must accept exactly one argument, the
- `AppState` object which you can use to access variables in your flow (see example below).
-
- Example:
-
- In your LightningFlow, override the method `configure_layout`:
-
- .. code-block:: python
-
- class MyFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def configure_layout(self):
- return StreamlitFrontend(render_fn=my_streamlit_ui)
-
-
- # define this function anywhere you want
- # this gets called anytime the UI needs to refresh
- def my_streamlit_ui(state):
- import streamlit as st
-
- st.write("Hello from streamlit!")
- st.write(state.counter)
-
- """
-
- @requires("streamlit")
- def __init__(self, render_fn: Callable) -> None:
- super().__init__()
-
- if inspect.ismethod(render_fn):
- raise TypeError(
- "The `StreamlitFrontend` doesn't support `render_fn` being a method. Please, use a pure function."
- )
-
- self.render_fn = render_fn
- self._process: Optional[subprocess.Popen] = None
-
- def start_server(self, host: str, port: int) -> None:
- env = os.environ.copy()
- env["LIGHTNING_FLOW_NAME"] = self.flow.name
- env["LIGHTNING_RENDER_FUNCTION"] = self.render_fn.__name__
- env["LIGHTNING_RENDER_MODULE_FILE"] = inspect.getmodule(self.render_fn).__file__
- std_err_out = get_logfile("error.log")
- std_out_out = get_logfile("output.log")
- with open(std_err_out, "wb") as stderr, open(std_out_out, "wb") as stdout:
- self._process = subprocess.Popen(
- [
- sys.executable,
- "-m",
- "streamlit",
- "run",
- os.path.join(os.path.dirname(lightning.app.frontend.__file__), "streamlit_base.py"),
- "--server.address",
- str(host),
- "--server.port",
- str(port),
- "--server.baseUrlPath",
- self.flow.name,
- "--server.headless",
- "true", # do not open the browser window when running locally
- "--server.enableXsrfProtection",
- "true" if is_running_in_cloud() else "false",
- ],
- env=env,
- stdout=stdout,
- stderr=stderr,
- )
-
- def stop_server(self) -> None:
- if self._process is None:
- raise RuntimeError("Server is not running. Call `StreamlitFrontend.start_server()` first.")
- self._process.kill()
diff --git a/src/lightning/app/frontend/streamlit_base.py b/src/lightning/app/frontend/streamlit_base.py
deleted file mode 100644
index a0ebc4b7cf66d..0000000000000
--- a/src/lightning/app/frontend/streamlit_base.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""This file gets run by streamlit, which we launch within Lightning.
-
-From here, we will call the render function that the user provided in ``configure_layout``.
-
-"""
-
-import os
-import pydoc
-from typing import Callable
-
-from lightning.app.frontend.utils import _reduce_to_flow_scope
-from lightning.app.utilities.app_helpers import StreamLitStatePlugin
-from lightning.app.utilities.state import AppState
-
-
-def _get_render_fn_from_environment() -> Callable:
- render_fn_name = os.environ["LIGHTNING_RENDER_FUNCTION"]
- render_fn_module_file = os.environ["LIGHTNING_RENDER_MODULE_FILE"]
- module = pydoc.importfile(render_fn_module_file)
- return getattr(module, render_fn_name)
-
-
-def _main():
- """Run the render_fn with the current flow_state."""
- app_state = AppState(plugin=StreamLitStatePlugin())
-
- # Fetch the information of which flow attaches to this streamlit instance
- flow_state = _reduce_to_flow_scope(app_state, flow=os.environ["LIGHTNING_FLOW_NAME"])
-
- # Call the provided render function.
- # Pass it the state, scoped to the current flow.
- render_fn = _get_render_fn_from_environment()
- render_fn(flow_state)
-
-
-if __name__ == "__main__":
- _main()
diff --git a/src/lightning/app/frontend/utils.py b/src/lightning/app/frontend/utils.py
deleted file mode 100644
index c05f7e143fe11..0000000000000
--- a/src/lightning/app/frontend/utils.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Utility functions for lightning Frontends."""
-
-from __future__ import annotations
-
-import inspect
-import os
-from typing import Callable
-
-from lightning.app.core.flow import LightningFlow
-from lightning.app.utilities.state import AppState
-
-
-def _reduce_to_flow_scope(state: AppState, flow: str | LightningFlow) -> AppState:
- """Returns a new AppState with the scope reduced to the given flow."""
- flow_name = flow.name if isinstance(flow, LightningFlow) else flow
- flow_name_parts = flow_name.split(".")[1:] # exclude root
- flow_state = state
- for part in flow_name_parts:
- flow_state = getattr(flow_state, part)
- return flow_state
-
-
-def _get_flow_state(flow: str) -> AppState:
- """Returns an AppState scoped to the current Flow.
-
- Returns:
- AppState: An AppState scoped to the current Flow.
-
- """
- app_state = AppState()
- app_state._request_state() # pylint: disable=protected-access
- return _reduce_to_flow_scope(app_state, flow)
-
-
-def _get_frontend_environment(flow: str, render_fn_or_file: Callable | str, port: int, host: str) -> os._Environ:
- """Returns an _Environ with the environment variables for serving a Frontend app set.
-
- Args:
- flow: The name of the flow, for example root.lit_frontend
- render_fn_or_file: A function to render
- port: The port number, for example 54321
- host: The host, for example 'localhost'
-
- Returns:
- os._Environ: An environment
-
- """
- env = os.environ.copy()
- env["LIGHTNING_FLOW_NAME"] = flow
- env["LIGHTNING_RENDER_PORT"] = str(port)
- env["LIGHTNING_RENDER_ADDRESS"] = str(host)
-
- if isinstance(render_fn_or_file, str):
- env["LIGHTNING_RENDER_FILE"] = render_fn_or_file
- else:
- env["LIGHTNING_RENDER_FUNCTION"] = render_fn_or_file.__name__
- env["LIGHTNING_RENDER_MODULE_FILE"] = inspect.getmodule(render_fn_or_file).__file__
-
- return env
diff --git a/src/lightning/app/frontend/web.py b/src/lightning/app/frontend/web.py
deleted file mode 100644
index 2e7d9f3f2f8e3..0000000000000
--- a/src/lightning/app/frontend/web.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import multiprocessing as mp
-from argparse import ArgumentParser
-from typing import Optional
-from urllib.parse import urljoin
-
-import uvicorn
-from fastapi import FastAPI
-from fastapi.middleware.cors import CORSMiddleware
-from starlette.staticfiles import StaticFiles
-
-from lightning.app.frontend.frontend import Frontend
-from lightning.app.utilities.log import get_logfile
-from lightning.app.utilities.network import find_free_network_port
-
-
-class StaticWebFrontend(Frontend):
- """A frontend that serves static files from a directory using FastAPI.
-
- Return this in your `LightningFlow.configure_layout()` method if you wish to serve a HTML page.
-
- Arguments:
- serve_dir: A local directory to serve files from. This directory should at least contain a file `index.html`.
- root_path: A path prefix when routing traffic from behind a proxy at `/`
-
- Example:
-
- In your LightningFlow, override the method `configure_layout`:
-
- .. code-block:: python
-
- def configure_layout(self):
- return StaticWebFrontend("path/to/folder/to/serve")
-
- """
-
- def __init__(self, serve_dir: str) -> None:
- super().__init__()
- self.serve_dir = serve_dir
- self._process: Optional[mp.Process] = None
-
- def start_server(self, host: str, port: int, root_path: str = "") -> None:
- log_file = str(get_logfile())
- self._process = mp.Process(
- target=_start_server,
- kwargs={
- "host": host,
- "port": port,
- "serve_dir": self.serve_dir,
- "path": f"/{self.flow.name}",
- "log_file": log_file,
- "root_path": root_path,
- },
- )
- self._process.start()
-
- def stop_server(self) -> None:
- if self._process is None:
- raise RuntimeError("Server is not running. Call `StaticWebFrontend.start_server()` first.")
- self._process.kill()
-
-
-def _healthz():
- """Health check endpoint used in the cloud FastAPI servers to check the status periodically."""
- return {"status": "ok"}
-
-
-def _start_server(
- serve_dir: str, host: str = "localhost", port: int = -1, path: str = "/", log_file: str = "", root_path: str = ""
-) -> None:
- if port == -1:
- port = find_free_network_port()
- fastapi_service = FastAPI()
-
- fastapi_service.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- # trailing / is required for urljoin to properly join the path. In case of
- # multiple trailing /, urljoin removes them
- fastapi_service.get(urljoin(f"{path}/", "healthz"), status_code=200)(_healthz)
- fastapi_service.mount(urljoin(path, root_path), StaticFiles(directory=serve_dir, html=True), name="static")
-
- log_config = _get_log_config(log_file) if log_file else uvicorn.config.LOGGING_CONFIG
-
- uvicorn.run(app=fastapi_service, host=host, port=port, log_config=log_config, root_path=root_path)
-
-
-def _get_log_config(log_file: str) -> dict:
- """Returns a logger configuration in the format expected by uvicorn that sends all logs to the given logfile."""
- # Modified from the default config found in uvicorn.config.LOGGING_CONFIG
- return {
- "version": 1,
- "disable_existing_loggers": False,
- "formatters": {
- "default": {
- "()": "uvicorn.logging.DefaultFormatter",
- "fmt": "%(levelprefix)s %(message)s",
- "use_colors": False,
- },
- },
- "handlers": {
- "default": {
- "formatter": "default",
- "class": "logging.FileHandler",
- "filename": log_file,
- },
- },
- "loggers": {
- "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
- "uvicorn.error": {"handlers": ["default"], "level": "INFO", "propagate": False},
- "uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False},
- },
- }
-
-
-if __name__ == "__main__": # pragma: no-cover
- parser = ArgumentParser()
- parser.add_argument("serve_dir", type=str)
- parser.add_argument("root_path", type=str, default="")
- parser.add_argument("--host", type=str, default="localhost")
- parser.add_argument("--port", type=int, default=-1)
- args = parser.parse_args()
- _start_server(serve_dir=args.serve_dir, host=args.host, port=args.port, root_path=args.root_path)
diff --git a/src/lightning/app/launcher/__init__.py b/src/lightning/app/launcher/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/launcher/launcher.py b/src/lightning/app/launcher/launcher.py
deleted file mode 100644
index d9e24ad1d3974..0000000000000
--- a/src/lightning/app/launcher/launcher.py
+++ /dev/null
@@ -1,492 +0,0 @@
-import inspect
-import logging
-import os
-import signal
-import sys
-import time
-import traceback
-from functools import partial
-from multiprocessing import Process
-from typing import Callable, Dict, List, Optional, Tuple, TypedDict
-
-from lightning.app import LightningFlow
-from lightning.app.core import constants
-from lightning.app.core.api import start_server
-from lightning.app.core.constants import (
- CHECK_ERROR_QUEUE_INTERVAL,
- ENABLE_ORCHESTRATOR,
- IS_DISTRIBUTED_PLUGIN,
-)
-from lightning.app.core.queues import MultiProcessQueue, QueuingSystem
-from lightning.app.storage.orchestrator import StorageOrchestrator
-from lightning.app.utilities.cloud import _sigterm_flow_handler
-from lightning.app.utilities.component import _set_flow_context, _set_frontend_context
-from lightning.app.utilities.enum import AppStage
-from lightning.app.utilities.exceptions import ExitAppException
-from lightning.app.utilities.load_app import extract_metadata_from_app, load_app_from_file
-from lightning.app.utilities.proxies import WorkRunner
-from lightning.app.utilities.redis import check_if_redis_running
-
-try:
- from lightning.app.utilities.app_commands import run_app_commands
-
- ABLE_TO_RUN_APP_COMMANDS = True
-except (ImportError, ModuleNotFoundError):
- ABLE_TO_RUN_APP_COMMANDS = False
-
-from lightning.app.launcher.lightning_backend import CloudBackend
-from lightning.app.launcher.utils import LIGHTNING_VERSION, convert_print_to_logger_info, enable_debugging, exit_app
-
-if hasattr(constants, "get_cloud_queue_type"):
- CLOUD_QUEUE_TYPE = constants.get_cloud_queue_type() or "redis"
-else:
- CLOUD_QUEUE_TYPE = "redis"
-
-logger = logging.getLogger(__name__)
-
-
-class FlowRestAPIQueues(TypedDict):
- api_publish_state_queue: MultiProcessQueue
- api_response_queue: MultiProcessQueue
-
-
-def check_error_queue(self) -> None:
- if not getattr(self, "_last_check_error_queue", None):
- self._last_check_error_queue = 0.0
-
- if (time.time() - self._last_check_error_queue) > CHECK_ERROR_QUEUE_INTERVAL:
- exception: Exception = self.get_state_changed_from_queue(self.error_queue) # type: ignore[assignment,arg-type]
- if isinstance(exception, Exception):
- self.exception = exception
- self.stage = AppStage.FAILED
- self._last_check_error_queue = time.time()
-
-
-def patch_app(app):
- app.check_error_queue = partial(check_error_queue, self=app)
-
-
-@convert_print_to_logger_info
-@enable_debugging
-def start_application_server(
- entrypoint_file: str, host: str, port: int, queue_id: str, queues: Optional[FlowRestAPIQueues] = None
-):
- logger.debug(f"Run Lightning Work {entrypoint_file} {host} {port} {queue_id}")
- queue_system = QueuingSystem(CLOUD_QUEUE_TYPE)
-
- wait_for_queues(queue_system)
-
- kwargs = {
- "api_delta_queue": queue_system.get_api_delta_queue(queue_id=queue_id),
- }
-
- # Note: Override the queues if provided
- if isinstance(queues, Dict):
- kwargs.update(queues)
- else:
- kwargs.update({
- "api_publish_state_queue": queue_system.get_api_state_publish_queue(queue_id=queue_id),
- "api_response_queue": queue_system.get_api_response_queue(queue_id=queue_id),
- })
-
- app = load_app_from_file(entrypoint_file)
- patch_app(app)
-
- from lightning.app.api.http_methods import _add_tags_to_api, _validate_api
- from lightning.app.utilities.app_helpers import is_overridden
- from lightning.app.utilities.commands.base import _commands_to_api, _prepare_commands
-
- apis = []
- if is_overridden("configure_api", app.root):
- apis = app.root.configure_api()
- _validate_api(apis)
- _add_tags_to_api(apis, ["app_api"])
-
- if is_overridden("configure_commands", app.root):
- commands = _prepare_commands(app)
- apis += _commands_to_api(commands)
-
- start_server(
- host=host,
- port=port,
- apis=apis,
- **kwargs,
- spec=extract_metadata_from_app(app),
- )
-
-
-@convert_print_to_logger_info
-@enable_debugging
-def run_lightning_work(
- file: str,
- work_name: str,
- queue_id: str,
-):
- """This staticmethod runs the specified work in the current process.
-
- It is organized under cloud runtime to indicate that it will be used by the cloud runner but otherwise, no cloud
- specific logic is being implemented here
-
- """
- logger.debug(f"Run Lightning Work {file} {work_name} {queue_id}")
-
- queues = QueuingSystem(CLOUD_QUEUE_TYPE)
- wait_for_queues(queues)
-
- caller_queue = queues.get_caller_queue(work_name=work_name, queue_id=queue_id)
- readiness_queue = queues.get_readiness_queue(queue_id=queue_id)
- delta_queue = queues.get_delta_queue(queue_id=queue_id)
- error_queue = queues.get_error_queue(queue_id=queue_id)
-
- request_queues = queues.get_orchestrator_request_queue(work_name=work_name, queue_id=queue_id)
- response_queues = queues.get_orchestrator_response_queue(work_name=work_name, queue_id=queue_id)
- copy_request_queues = queues.get_orchestrator_copy_request_queue(work_name=work_name, queue_id=queue_id)
- copy_response_queues = queues.get_orchestrator_copy_response_queue(work_name=work_name, queue_id=queue_id)
-
- if ABLE_TO_RUN_APP_COMMANDS:
- run_app_commands(file)
-
- load_app_from_file(file)
-
- if IS_DISTRIBUTED_PLUGIN:
- import json
-
- from multi_node.launcher import ScriptLauncher
-
- from lightning.app import CloudCompute
-
- script_command = os.environ["COMMAND"]
- distributed_arguments = os.environ["DISTRIBUTED_ARGUMENTS"]
- distributed_arguments = json.loads(distributed_arguments)
- cloud_compute = distributed_arguments["cloud_compute"]
- disk_size = int(distributed_arguments.get("disk_size", 400))
-
- work = ScriptLauncher(
- cloud_compute=CloudCompute(cloud_compute, disk_size=disk_size),
- parallel=True,
- command=script_command,
- )
- work_name = os.getenv("LIGHTNING_CLOUD_WORK_NAME", "")
- work._name = work_name
- else:
- queue = queues.get_work_queue(work_name=work_name, queue_id=queue_id)
- work = queue.get()
-
- extras = {}
-
- if hasattr(work, "_run_executor_cls"):
- extras["run_executor_cls"] = work._run_executor_cls
-
- WorkRunner(
- work=work,
- work_name=work_name,
- caller_queue=caller_queue,
- delta_queue=delta_queue,
- readiness_queue=readiness_queue,
- error_queue=error_queue,
- request_queue=request_queues,
- response_queue=response_queues,
- copy_request_queue=copy_request_queues,
- copy_response_queue=copy_response_queues,
- **extras,
- )()
-
-
-@convert_print_to_logger_info
-@enable_debugging
-def run_lightning_flow(entrypoint_file: str, queue_id: str, base_url: str, queues: Optional[FlowRestAPIQueues] = None):
- _set_flow_context()
-
- logger.debug(f"Run Lightning Flow {entrypoint_file} {queue_id} {base_url}")
-
- app = load_app_from_file(entrypoint_file)
- app.backend = CloudBackend(entrypoint_file, queue_id=queue_id)
-
- queue_system = app.backend.queues
- app.backend.update_lightning_app_frontend(app)
- wait_for_queues(queue_system)
-
- app.backend.resolve_url(app, base_url)
- if app.root_path != "":
- app._update_index_file()
- app.backend._prepare_queues(app)
-
- # Note: Override the queues if provided
- if queues:
- app.api_publish_state_queue = queues["api_publish_state_queue"]
- app.api_response_queue = queues["api_response_queue"]
-
- LightningFlow._attach_backend(app.root, app.backend)
-
- app.should_publish_changes_to_api = True
-
- # reduces the number of requests to the CP
- if ENABLE_ORCHESTRATOR:
- storage_orchestrator = StorageOrchestrator(
- app,
- app.request_queues,
- app.response_queues,
- app.copy_request_queues,
- app.copy_response_queues,
- )
- storage_orchestrator.setDaemon(True)
- storage_orchestrator.start()
-
- # refresh the layout with the populated urls.
- app._update_layout()
-
- # register a signal handler to clean all works.
- if sys.platform != "win32":
- signal.signal(signal.SIGTERM, partial(_sigterm_flow_handler, app=app))
-
- if "apis" in inspect.signature(start_server).parameters:
- from lightning.app.utilities.commands.base import _prepare_commands
-
- _prepare_commands(app)
-
- # Once the bootstrapping is done, running the rank 0
- # app with all the components inactive
- try:
- app._run()
- except ExitAppException:
- pass
- except Exception:
- app.stage = AppStage.FAILED
- print(traceback.format_exc())
-
- if ENABLE_ORCHESTRATOR:
- storage_orchestrator.join(0)
-
- app.backend.stop_all_works(app.works)
-
- exit_code = 1 if app.stage == AppStage.FAILED else 0
- print(f"Finishing the App with exit_code: {str(exit_code)}...")
-
- if not exit_code:
- exit_app(app)
-
- sys.exit(exit_code)
-
-
-@convert_print_to_logger_info
-@enable_debugging
-def serve_frontend(file: str, flow_name: str, host: str, port: int):
- """This staticmethod runs the specified frontend for a given flow in a new process.
-
- It is organized under cloud runtime to indicate that it will be used by the cloud runner but otherwise, no cloud
- specific logic is being implemented here.
-
- """
- _set_frontend_context()
- logger.debug(f"Run Serve Frontend {file} {flow_name} {host} {port}")
- app = load_app_from_file(file)
- if flow_name not in app.frontends:
- raise ValueError(f"Could not find frontend for flow with name {flow_name}.")
- frontend = app.frontends[flow_name]
- assert frontend.flow.name == flow_name
-
- frontend.start_server(host, port)
-
-
-def start_server_in_process(target: Callable, args: Tuple = (), kwargs: Dict = {}) -> Process:
- p = Process(target=target, args=args, kwargs=kwargs)
- p.start()
- return p
-
-
-def format_row(elements, col_widths, padding=1):
- elements = [el.ljust(w - padding * 2) for el, w in zip(elements, col_widths)]
- pad = " " * padding
- elements = [f"{pad}{el}{pad}" for el in elements]
- return f'|{"|".join(elements)}|'
-
-
-def tabulate(data, headers):
- data = [[str(el) for el in row] for row in data]
- col_widths = [len(el) for el in headers]
- for row in data:
- col_widths = [max(len(el), curr) for el, curr in zip(row, col_widths)]
- col_widths = [w + 2 for w in col_widths]
- seps = ["-" * w for w in col_widths]
- lines = [format_row(headers, col_widths), format_row(seps, col_widths, padding=0)] + [
- format_row(row, col_widths) for row in data
- ]
- return "\n".join(lines)
-
-
-def manage_server_processes(processes: List[Tuple[str, Process]]) -> None:
- if not processes:
- return
-
- sigterm_called = [False]
-
- def _sigterm_handler(*_):
- sigterm_called[0] = True
-
- if sys.platform != "win32":
- signal.signal(signal.SIGTERM, _sigterm_handler)
-
- # Since frontends run user code, any of them could fail. In that case,
- # we want to fail all of them, as well as the application server, and
- # exit the command with an error status code.
-
- exitcode = 0
-
- while True:
- # We loop until
- # 1. Get a sigterm
- # 2. All the children died but all with exit code 0
- # 3. At-least one of the child died with non-zero exit code
-
- # sleeping quickly at the starting of every loop
- # moving this to the end of the loop might result in some flaky tests
- time.sleep(1)
-
- if sigterm_called[0]:
- print("Got SIGTERM. Exiting execution!!!")
- break
- if all(not p.is_alive() and p.exitcode == 0 for _, p in processes):
- print("All the components are inactive with exitcode 0. Exiting execution!!!")
- break
- if any((not p.is_alive() and p.exitcode != 0) for _, p in processes):
- print("Found dead components with non-zero exit codes, exiting execution!!! Components: ")
- print(
- tabulate(
- [(name, p.exitcode) for name, p in processes if not p.is_alive() and p.exitcode != 0],
- headers=["Name", "Exit Code"],
- )
- )
- exitcode = 1
- break
-
- # sleeping for the last set of logs to reach stdout
- time.sleep(2)
-
- # Cleanup
- for _, p in processes:
- if p.is_alive():
- os.kill(p.pid, signal.SIGTERM)
-
- # Give processes time to terminate
- for _, p in processes:
- p.join(5)
-
- # clean the remaining ones.
- if any(p.is_alive() for _, p in processes):
- for _, p in processes:
- if p.is_alive():
- os.kill(p.pid, signal.SIGKILL)
-
- # this sleep is just a precaution - signals might take a while to get raised.
- time.sleep(1)
- sys.exit(1)
-
- sys.exit(exitcode)
-
-
-def _get_frontends_from_app(entrypoint_file):
- """This function is used to get the frontends from the app. It will be used to start the frontends in a separate
- process if the backend cannot provide flow_names_and_ports. This is useful if the app cannot be loaded locally to
- set the frontend before dispatching to the cloud. The backend exposes by default 10 ports from 8081 if the
- app.spec.frontends is not set.
-
- NOTE: frontend_name are sorted to ensure that they get consistent ports.
-
- :param entrypoint_file: The entrypoint file for the app
- :return: A list of tuples of the form (frontend_name, port_number)
-
- """
- app = load_app_from_file(entrypoint_file)
-
- frontends = []
- # This value of the port should be synced with the port value in the backend.
- # If you change this value, you should also change the value in the backend.
- flow_frontends_starting_port = 8081
- for frontend in sorted(app.frontends.keys()):
- frontends.append((frontend, flow_frontends_starting_port))
- flow_frontends_starting_port += 1
-
- return frontends
-
-
-@convert_print_to_logger_info
-@enable_debugging
-def start_flow_and_servers(
- entrypoint_file: str,
- base_url: str,
- queue_id: str,
- host: str,
- port: int,
- flow_names_and_ports: Tuple[Tuple[str, int]],
-):
- processes: List[Tuple[str, Process]] = []
-
- # Queues between Flow and its Rest API are using multiprocessing to:
- # - reduce redis load
- # - increase UI responsiveness and RPS
- queue_system = QueuingSystem.MULTIPROCESS
- queues = {
- "api_publish_state_queue": queue_system.get_api_state_publish_queue(queue_id=queue_id),
- "api_response_queue": queue_system.get_api_response_queue(queue_id=queue_id),
- }
-
- if ABLE_TO_RUN_APP_COMMANDS:
- # In order to avoid running this function 3 seperate times while executing the
- # `run_lightning_flow`, `start_application_server`, & `serve_frontend` functions
- # in a subprocess we extract this to the top level. If we intend to make changes
- # to be able to start these components in seperate containers, the implementation
- # will have to move a call to this function within the initialization process.
- run_app_commands(entrypoint_file)
-
- flow_process = start_server_in_process(
- run_lightning_flow,
- args=(
- entrypoint_file,
- queue_id,
- base_url,
- ),
- kwargs={"queues": queues},
- )
- processes.append(("Flow", flow_process))
-
- server_process = start_server_in_process(
- target=start_application_server,
- args=(
- entrypoint_file,
- host,
- port,
- queue_id,
- ),
- kwargs={"queues": queues},
- )
- processes.append(("Server", server_process))
-
- if not flow_names_and_ports:
- flow_names_and_ports = _get_frontends_from_app(entrypoint_file)
-
- for name, fe_port in flow_names_and_ports:
- frontend_process = start_server_in_process(target=serve_frontend, args=(entrypoint_file, name, host, fe_port))
- processes.append((name, frontend_process))
-
- manage_server_processes(processes)
-
-
-def wait_for_queues(queue_system: QueuingSystem) -> None:
- queue_check_start_time = int(time.time())
-
- if hasattr(queue_system, "get_queue"):
- while not queue_system.get_queue("healthz").is_running:
- if (int(time.time()) - queue_check_start_time) % 10 == 0:
- logger.warning("Waiting for http queues to start...")
- time.sleep(1)
- else:
- if CLOUD_QUEUE_TYPE != "redis":
- raise ValueError(
- f"Queue system {queue_system} is not correctly configured. You seem to have requested HTTP queues,"
- f"but using an old version of lightning framework ({LIGHTNING_VERSION}) that doesn't support "
- f"HTTP queues. Try upgrading lightning framework to the latest version."
- )
- while not check_if_redis_running():
- if (int(time.time()) - queue_check_start_time) % 10 == 0:
- logger.warning("Waiting for redis queues to start...")
- time.sleep(1)
diff --git a/src/lightning/app/launcher/lightning_backend.py b/src/lightning/app/launcher/lightning_backend.py
deleted file mode 100644
index ee037c8470402..0000000000000
--- a/src/lightning/app/launcher/lightning_backend.py
+++ /dev/null
@@ -1,570 +0,0 @@
-import contextlib
-import inspect
-import json
-import logging
-import os
-import random
-import string
-from time import monotonic, sleep, time
-from typing import List, Optional
-
-from lightning.app import LightningApp, LightningWork
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.runners.backends.backend import Backend
-from lightning.app.storage import Drive
-from lightning.app.utilities.enum import WorkStageStatus, WorkStopReasons, make_status
-from lightning.app.utilities.network import LightningClient
-
-with contextlib.suppress(ImportError, ModuleNotFoundError):
- # TODO: remove try block and just import after lighting_app > 0.6.3 is released
- from lightning.app.storage import Mount
-
-try:
- from lightning.app.utilities.exceptions import LightningPlatformException
-except ImportError:
- LightningPlatformException = Exception
-
-from lightning_cloud.openapi import (
- AppIdWorksBody,
- AppinstancesIdBody,
- Externalv1LightningappInstance,
- Externalv1Lightningwork,
- V1BuildSpec,
- V1Drive,
- V1DriveSpec,
- V1DriveStatus,
- V1DriveType,
- V1Flowserver,
- V1LightningappInstanceState,
- V1LightningappRestartPolicy,
- V1LightningworkClusterDriver,
- V1LightningworkDrives,
- V1LightningworkSpec,
- V1LightningworkState,
- V1ListLightningworkResponse,
- V1Metadata,
- V1NetworkConfig,
- V1PackageManager,
- V1PythonDependencyInfo,
- V1SourceType,
- V1UserRequestedComputeConfig,
-)
-from lightning_cloud.openapi.rest import ApiException
-
-from lightning.app.launcher.utils import LIGHTNING_VERSION, cloud_work_stage_to_work_status_stage
-
-logger = logging.getLogger(__name__)
-
-# TODO: For future travelers: This backward incompatible change is being introduced when lightning app is at 0.6.0
-# Once we are safe to remove the support for 0.6.0, remove this ugly import
-try:
- from lightning_cloud.openapi import SpecLightningappInstanceIdWorksBody, WorksIdBody
-except ImportError:
- logger.warning(
- f"You are using an old version of lightning ({LIGHTNING_VERSION}). " f"Please upgrade to the latest version."
- )
- from lightning_cloud.openapi import Body5 as SpecLightningappInstanceIdWorksBody
- from lightning_cloud.openapi import Body6 as WorksIdBody
-except Exception as e:
- logger.warning(
- f"You are using an old version of lightning ({LIGHTNING_VERSION}). "
- f"Please upgrade to the latest version. {e}"
- )
- from lightning_cloud.openapi import Body5 as SpecLightningappInstanceIdWorksBody
- from lightning_cloud.openapi import Body6 as WorksIdBody
-
-LIGHTNING_STOP_TIMEOUT = int(os.getenv("LIGHTNING_STOP_TIMEOUT", 2 * 60))
-
-
-class CloudBackend(Backend):
- def __init__(
- self,
- entrypoint_file,
- queue_id: Optional[str] = None,
- status_update_interval: int = 5,
- ) -> None:
- # TODO: Properly handle queue_id in the cloud.
- super().__init__(entrypoint_file, queues=QueuingSystem("http"), queue_id=queue_id)
- self._status_update_interval = status_update_interval
- self._last_time_updated = None
- self.client = LightningClient(retry=True)
- self.base_url: Optional[str] = None
-
- @staticmethod
- def _work_to_spec(work: LightningWork) -> V1LightningworkSpec:
- work_requirements = "\n".join(work.cloud_build_config.requirements)
-
- build_spec = V1BuildSpec(
- commands=work.cloud_build_config.build_commands(),
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages=work_requirements
- ),
- image=work.cloud_build_config.image,
- )
-
- drive_specs: List[V1LightningworkDrives] = []
- for drive_attr_name, drive in [
- (k, getattr(work, k)) for k in work._state if isinstance(getattr(work, k), Drive)
- ]:
- if drive.protocol == "lit://":
- drive_type = V1DriveType.NO_MOUNT_S3
- source_type = V1SourceType.S3
- else:
- drive_type = V1DriveType.UNSPECIFIED
- source_type = V1SourceType.UNSPECIFIED
-
- drive_specs.append(
- V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(name=f"{work.name}.{drive_attr_name}"),
- spec=V1DriveSpec(
- drive_type=drive_type,
- source_type=source_type,
- source=f"{drive.protocol}{drive.id}",
- ),
- status=V1DriveStatus(),
- ),
- mount_location=str(drive.root_folder),
- ),
- )
-
- # TODO: remove after we move lighting_app past v0.6.3
- if hasattr(work.cloud_compute, "mounts") and work.cloud_compute.mounts is not None:
- # this should really be part of the work.cloud_compute struct, but to save
- # time we are not going to modify the backend in this set of PRs & instead
- # use the same s3 drives API which we used before.
- if isinstance(work.cloud_compute.mounts, Mount):
- drive_specs.append(
- _create_mount_drive_spec(
- work_name=work.name,
- mount=work.cloud_compute.mounts,
- )
- )
- else:
- for mount in work.cloud_compute.mounts:
- drive_specs.append(
- _create_mount_drive_spec(
- work_name=work.name,
- mount=mount,
- )
- )
-
- if hasattr(work.cloud_compute, "interruptible"):
- spot = work.cloud_compute.interruptible
- else:
- spot = work.cloud_compute.preemptible
-
- colocation_group_id = None
- if hasattr(work.cloud_compute, "colocation_group_id"):
- colocation_group_id = work.cloud_compute.colocation_group_id
-
- user_compute_config = V1UserRequestedComputeConfig(
- name=work.cloud_compute.name,
- count=1,
- disk_size=work.cloud_compute.disk_size,
- spot=spot,
- shm_size=work.cloud_compute.shm_size,
- affinity_identifier=colocation_group_id,
- )
-
- random_name = "".join(random.choice(string.ascii_lowercase) for _ in range(5)) # noqa: S311
-
- return V1LightningworkSpec(
- build_spec=build_spec,
- drives=drive_specs,
- user_requested_compute_config=user_compute_config,
- network_config=[V1NetworkConfig(name=random_name, port=work.port)],
- desired_state=V1LightningworkState.RUNNING,
- restart_policy=V1LightningappRestartPolicy.NEVER,
- cluster_driver=V1LightningworkClusterDriver.DIRECT,
- )
-
- def create_work(self, app: LightningApp, work: LightningWork) -> None:
- app_id = self._get_app_id()
- project_id = self._get_project_id()
- list_response: V1ListLightningworkResponse = self.client.lightningwork_service_list_lightningwork(
- project_id=project_id, app_id=app_id
- )
- external_specs: List[Externalv1Lightningwork] = list_response.lightningworks
-
- # Find THIS work in the list of all registered works
- external_spec = None
- for es in external_specs:
- if es.name == work.name:
- external_spec = es
- break
-
- if external_spec is None:
- spec = self._work_to_spec(work)
- try:
- fn = SpecLightningappInstanceIdWorksBody.__init__
- params = list(inspect.signature(fn).parameters)
- extras = {}
- if "display_name" in params:
- extras["display_name"] = getattr(work, "display_name", "")
-
- external_spec = self.client.lightningwork_service_create_lightningwork(
- project_id=project_id,
- spec_lightningapp_instance_id=app_id,
- body=SpecLightningappInstanceIdWorksBody(
- name=work.name,
- spec=spec,
- **extras,
- ),
- )
- # overwriting spec with return value
- spec = external_spec.spec
- except ApiException as e:
- # We might get exceed quotas, or be out of credits.
- message = json.loads(e.body).get("message")
- raise LightningPlatformException(message) from None
- elif external_spec.spec.desired_state == V1LightningworkState.RUNNING:
- spec = external_spec.spec
- work._port = spec.network_config[0].port
- else:
- # Signal the LightningWorkState to go into state RUNNING
- spec = external_spec.spec
-
- # getting the updated spec but ignoring everything other than port & drives
- new_spec = self._work_to_spec(work)
-
- spec.desired_state = V1LightningworkState.RUNNING
- spec.network_config[0].port = new_spec.network_config[0].port
- spec.drives = new_spec.drives
- spec.user_requested_compute_config = new_spec.user_requested_compute_config
- spec.build_spec = new_spec.build_spec
- spec.env = new_spec.env
- try:
- self.client.lightningwork_service_update_lightningwork(
- project_id=project_id,
- id=external_spec.id,
- spec_lightningapp_instance_id=app_id,
- body=WorksIdBody(spec),
- )
- except ApiException as e:
- # We might get exceed quotas, or be out of credits.
- message = json.loads(e.body).get("message")
- raise LightningPlatformException(message) from None
-
- # Replace the undefined url and host by the known one.
- work._host = "0.0.0.0" # noqa: S104
- work._future_url = f"{self._get_proxy_scheme()}://{spec.network_config[0].host}"
-
- # removing the backend to avoid the threadlock error
- _backend = work._backend
- work._backend = None
- app.work_queues[work.name].put(work)
- work._backend = _backend
-
- logger.info(f"Starting work {work.name}")
- logger.debug(f"With the following external spec: {external_spec}")
-
- def update_work_statuses(self, works: List[LightningWork]) -> None:
- """Pulls the status of each Work instance in the cloud.
-
- Normally, the Lightning frameworks communicates statuses through the queues, but while the Work instance is
- being provisionied, the queues don't exist yet and hence we need to make API calls directly to the Grid backend
- to fetch the status and update it in the states.
-
- """
- if not works:
- return
-
- # TODO: should this run in a timer thread instead?
- if self._last_time_updated is not None and monotonic() - self._last_time_updated < self._status_update_interval:
- return
-
- cloud_work_specs = self._get_cloud_work_specs(self.client)
- local_works = works
- for cloud_work_spec in cloud_work_specs:
- for local_work in local_works:
- # TODO (tchaton) Better resolve pending status after succeeded
-
- # 1. Skip if the work isn't the current one.
- if local_work.name != cloud_work_spec.name:
- continue
-
- # 2. Logic for idle timeout
- self._handle_idle_timeout(
- local_work.cloud_compute.idle_timeout,
- local_work,
- cloud_work_spec,
- )
-
- # 3. Map the cloud phase to the local one
- cloud_stage = cloud_work_stage_to_work_status_stage(
- cloud_work_spec.status.phase,
- )
-
- # 4. Detect if the work failed during pending phase
- if local_work.status.stage == WorkStageStatus.PENDING and cloud_stage in WorkStageStatus.FAILED:
- if local_work._raise_exception:
- raise Exception(f"The work {local_work.name} failed during pending phase.")
- logger.error(f"The work {local_work.name} failed during pending phase.")
-
- # 5. Skip the pending and running as this is already handled by Lightning.
- if cloud_stage in (WorkStageStatus.PENDING, WorkStageStatus.RUNNING):
- continue
-
- # TODO: Add the logic for wait_timeout
- if local_work.status.stage != cloud_stage:
- latest_hash = local_work._calls["latest_call_hash"]
- if latest_hash is None:
- continue
- local_work._calls[latest_hash]["statuses"].append(make_status(cloud_stage))
-
- self._last_time_updated = monotonic()
-
- def stop_all_works(self, works: List[LightningWork]) -> None:
- """Stop resources for all LightningWorks in this app.
-
- The Works are stopped rather than deleted so that they can be inspected for debugging.
-
- """
- self.stop_works(works)
-
- def all_works_stopped(works: List[Externalv1Lightningwork]) -> bool:
- for work in works:
- # deleted work won't be in the request hence only checking for stopped & failed
- if work.status.phase not in (
- V1LightningworkState.STOPPED,
- V1LightningworkState.FAILED,
- ):
- return False
- return True
-
- t0 = time()
- while not all_works_stopped(self._get_cloud_work_specs(self.client)):
- # Wait a little..
- print("Waiting for works to stop...")
- sleep(3)
-
- # Break if we reached timeout.
- if time() - t0 > LIGHTNING_STOP_TIMEOUT:
- break
-
- def stop_works(self, works) -> None:
- # Used to stop all the works in a batch
- cloud_works = self._get_cloud_work_specs(self.client)
-
- cloud_works_to_stop = []
- for cloud_work in cloud_works:
- # Skip the works already stopped
- spec: V1LightningworkSpec = cloud_work.spec
- if spec.desired_state == V1LightningworkState.DELETED:
- # work is set to be deleted. Do nothing
- continue
- if spec.desired_state == V1LightningworkState.STOPPED:
- # work is set to be stopped already. Do nothing
- continue
- if cloud_work.status.phase == V1LightningworkState.FAILED:
- # work is already failed. Do nothing
- continue
-
- for w in works:
- if not w.has_failed and w.name == cloud_work.name:
- cloud_works_to_stop.append(cloud_work)
- break
-
- if cloud_works_to_stop:
- self.client.lightningwork_service_batch_update_lightningworks(
- project_id=CloudBackend._get_project_id(),
- app_id=CloudBackend._get_app_id(),
- body=AppIdWorksBody(
- desired_state=V1LightningworkState.STOPPED,
- work_ids=[w.id for w in cloud_works_to_stop],
- ),
- )
- print(f"Stopping {','.join([w.name for w in cloud_works_to_stop])} ...")
-
- def resolve_url(self, app, base_url: Optional[str] = None) -> None:
- pass
- # if not self.base_url:
- # self.base_url = base_url
-
- # for flow in app.flows:
- # if self.base_url:
- # # Replacing the path with complete URL
- # if not (self.base_url.startswith("http://") or self.base_url.startswith("https://")):
- # raise ValueError(
- # "Base URL doesn't have a valid scheme, expected it to start with 'http://' or 'https://' "
- # )
- # if isinstance(flow._layout, dict) and "target" not in flow._layout:
- # # FIXME: Why _check_service_url_is_ready doesn't work ?
- # frontend_url = urllib.parse.urljoin(self.base_url, flow.name + "/")
- # flow._layout["target"] = frontend_url
-
- # for work in app.works:
- # if (
- # work._url == ""
- # and work.status.stage
- # in (
- # WorkStageStatus.RUNNING,
- # WorkStageStatus.SUCCEEDED,
- # )
- # and work._internal_ip != ""
- # and _check_service_url_is_ready(f"http://{work._internal_ip}:{work._port}")
- # ):
- # work._url = work._future_url
-
- @staticmethod
- def _get_proxy_scheme() -> str:
- return os.environ.get("LIGHTNING_PROXY_SCHEME", "https")
-
- @staticmethod
- def _get_app_id() -> str:
- return os.environ["LIGHTNING_CLOUD_APP_ID"]
-
- @staticmethod
- def _get_project_id() -> str:
- return os.environ["LIGHTNING_CLOUD_PROJECT_ID"]
-
- @staticmethod
- def _get_cloud_work_specs(client: LightningClient) -> List[Externalv1Lightningwork]:
- list_response: V1ListLightningworkResponse = client.lightningwork_service_list_lightningwork(
- project_id=CloudBackend._get_project_id(),
- app_id=CloudBackend._get_app_id(),
- )
- return list_response.lightningworks
-
- def _handle_idle_timeout(self, idle_timeout: float, work: LightningWork, resp: Externalv1Lightningwork) -> None:
- if idle_timeout is None:
- return
-
- if work.status.stage != WorkStageStatus.SUCCEEDED:
- return
-
- if time() > (idle_timeout + work.status.timestamp):
- logger.info(f"Idle Timeout {idle_timeout} has triggered. Stopping gracefully the {work.name}.")
- latest_hash = work._calls["latest_call_hash"]
- status = make_status(WorkStageStatus.STOPPED, reason=WorkStopReasons.PENDING)
- work._calls[latest_hash]["statuses"].append(status)
- self._stop_work(resp)
- logger.debug(f"Stopping work: {resp.id}")
-
- def _register_queues(self, app, work):
- super()._register_queues(app, work)
- kw = dict(queue_id=self.queue_id, work_name=work.name) # noqa: C408
- app.work_queues.update({work.name: self.queues.get_work_queue(**kw)})
-
- def stop_work(self, app: LightningApp, work: LightningWork) -> None:
- cloud_works = self._get_cloud_work_specs(self.client)
- for cloud_work in cloud_works:
- if work.name == cloud_work.name:
- self._stop_work(cloud_work)
-
- def _stop_work(self, work_resp: Externalv1Lightningwork) -> None:
- spec: V1LightningworkSpec = work_resp.spec
- if spec.desired_state == V1LightningworkState.DELETED:
- # work is set to be deleted. Do nothing
- return
- if spec.desired_state == V1LightningworkState.STOPPED:
- # work is set to be stopped already. Do nothing
- return
- if work_resp.status.phase == V1LightningworkState.FAILED:
- # work is already failed. Do nothing
- return
- spec.desired_state = V1LightningworkState.STOPPED
- self.client.lightningwork_service_update_lightningwork(
- project_id=CloudBackend._get_project_id(),
- id=work_resp.id,
- spec_lightningapp_instance_id=CloudBackend._get_app_id(),
- body=WorksIdBody(spec),
- )
- print(f"Stopping {work_resp.name} ...")
-
- def delete_work(self, app: LightningApp, work: LightningWork) -> None:
- cloud_works = self._get_cloud_work_specs(self.client)
- for cloud_work in cloud_works:
- if work.name == cloud_work.name:
- self._delete_work(cloud_work)
-
- def _delete_work(self, work_resp: Externalv1Lightningwork) -> None:
- spec: V1LightningworkSpec = work_resp.spec
- if spec.desired_state == V1LightningworkState.DELETED:
- # work is set to be deleted. Do nothing
- return
- spec.desired_state = V1LightningworkState.DELETED
- self.client.lightningwork_service_update_lightningwork(
- project_id=CloudBackend._get_project_id(),
- id=work_resp.id,
- spec_lightningapp_instance_id=CloudBackend._get_app_id(),
- body=WorksIdBody(spec),
- )
- print(f"Deleting {work_resp.name} ...")
-
- def update_lightning_app_frontend(self, app):
- """Used to create frontend's if the app couldn't be loaded locally."""
- if not len(app.frontends.keys()):
- return
-
- external_app_spec: "Externalv1LightningappInstance" = (
- self.client.lightningapp_instance_service_get_lightningapp_instance(
- project_id=CloudBackend._get_project_id(),
- id=CloudBackend._get_app_id(),
- )
- )
-
- frontend_specs = external_app_spec.spec.flow_servers
- spec = external_app_spec.spec
- if len(frontend_specs) != len(app.frontends.keys()):
- frontend_specs: List[V1Flowserver] = []
- for flow_name in sorted(app.frontends.keys()):
- frontend_spec = V1Flowserver(name=flow_name)
- frontend_specs.append(frontend_spec)
-
- spec.flow_servers = frontend_specs
- spec.enable_app_server = True
-
- logger.info("Found new frontends. Updating the app spec.")
-
- self.client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=CloudBackend._get_project_id(),
- id=CloudBackend._get_app_id(),
- body=AppinstancesIdBody(spec=spec),
- )
-
- def stop_app(self, app):
- """Used to mark the App has stopped if everything has fine."""
-
- external_app_spec: "Externalv1LightningappInstance" = (
- self.client.lightningapp_instance_service_get_lightningapp_instance(
- project_id=CloudBackend._get_project_id(),
- id=CloudBackend._get_app_id(),
- )
- )
-
- spec = external_app_spec.spec
- spec.desired_state = V1LightningappInstanceState.STOPPED
-
- self.client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=CloudBackend._get_project_id(),
- id=CloudBackend._get_app_id(),
- body=AppinstancesIdBody(spec=spec),
- )
-
-
-def _create_mount_drive_spec(work_name: str, mount: "Mount") -> V1LightningworkDrives:
- if mount.protocol == "s3://":
- drive_type = V1DriveType.INDEXED_S3
- source_type = V1SourceType.S3
- else:
- raise RuntimeError(
- f"unknown mounts protocol `{mount.protocol}`. Please verify this "
- f"drive type has been configured for use in the cloud dispatcher."
- )
-
- return V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name=work_name,
- ),
- spec=V1DriveSpec(
- drive_type=drive_type,
- source_type=source_type,
- source=mount.source,
- ),
- status=V1DriveStatus(),
- ),
- mount_location=str(mount.mount_path),
- )
diff --git a/src/lightning/app/launcher/lightning_hybrid_backend.py b/src/lightning/app/launcher/lightning_hybrid_backend.py
deleted file mode 100644
index 27e3d02256751..0000000000000
--- a/src/lightning/app/launcher/lightning_hybrid_backend.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import os
-from typing import Optional
-
-from lightning_cloud.openapi import AppinstancesIdBody, Externalv1LightningappInstance
-
-from lightning.app.core import constants
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.launcher.lightning_backend import CloudBackend
-from lightning.app.runners.backends.backend import Backend
-from lightning.app.runners.backends.mp_process import MultiProcessingBackend
-from lightning.app.utilities.network import LightningClient
-
-if hasattr(constants, "get_cloud_queue_type"):
- CLOUD_QUEUE_TYPE = constants.get_cloud_queue_type() or "redis"
-else:
- CLOUD_QUEUE_TYPE = "redis"
-
-
-class CloudHybridBackend(Backend):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, queues=QueuingSystem(CLOUD_QUEUE_TYPE), **kwargs)
- cloud_backend = CloudBackend(*args, **kwargs)
- kwargs.pop("queue_id")
- multiprocess_backend = MultiProcessingBackend(*args, **kwargs)
-
- self.backends = {"cloud": cloud_backend, "multiprocess": multiprocess_backend}
- self.work_to_network_configs = {}
-
- def create_work(self, app, work) -> None:
- backend = self._get_backend(work)
- if isinstance(backend, MultiProcessingBackend):
- self._prepare_work_creation(app, work)
- backend.create_work(app, work)
-
- def _prepare_work_creation(self, app, work) -> None:
- app_id = self._get_app_id()
- project_id = self._get_project_id()
- assert project_id
-
- client = LightningClient()
- list_apps_resp = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id)
- lightning_app: Optional[Externalv1LightningappInstance] = None
-
- for lightningapp in list_apps_resp.lightningapps:
- if lightningapp.id == app_id:
- lightning_app = lightningapp
-
- assert lightning_app
-
- network_configs = lightning_app.spec.network_config
-
- index = len(self.work_to_network_configs)
-
- if work.name not in self.work_to_network_configs:
- self.work_to_network_configs[work.name] = network_configs[index]
-
- # Enable Ingress and update the specs.
- lightning_app.spec.network_config[index].enable = True
-
- client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=lightning_app.id,
- body=AppinstancesIdBody(name=lightning_app.name, spec=lightning_app.spec),
- )
-
- work_network_config = self.work_to_network_configs[work.name]
-
- work._host = "0.0.0.0" # noqa: S104
- work._port = work_network_config.port
- work._future_url = f"{self._get_proxy_scheme()}://{work_network_config.host}"
-
- def update_work_statuses(self, works) -> None:
- if works:
- backend = self._get_backend(works[0])
- backend.update_work_statuses(works)
-
- def stop_all_works(self, works) -> None:
- if works:
- backend = self._get_backend(works[0])
- backend.stop_all_works(works)
-
- def stop_works(self, works) -> None:
- if works:
- backend = self._get_backend(works[0])
- backend.stop_works(works)
-
- def resolve_url(self, app, base_url: Optional[str] = None) -> None:
- works = app.works
- if works:
- backend = self._get_backend(works[0])
- backend.resolve_url(app, base_url)
-
- def update_lightning_app_frontend(self, app):
- self.backends["cloud"].update_lightning_app_frontend(app)
-
- def stop_work(self, app, work) -> None:
- backend = self._get_backend(work)
- if isinstance(backend, MultiProcessingBackend):
- self._prepare_work_stop(app, work)
- backend.stop_work(app, work)
-
- def delete_work(self, app, work) -> None:
- backend = self._get_backend(work)
- if isinstance(backend, MultiProcessingBackend):
- self._prepare_work_stop(app, work)
- backend.delete_work(app, work)
-
- def _prepare_work_stop(self, app, work):
- app_id = self._get_app_id()
- project_id = self._get_project_id()
- assert project_id
-
- client = LightningClient()
- list_apps_resp = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id)
- lightning_app: Optional[Externalv1LightningappInstance] = None
-
- for lightningapp in list_apps_resp.lightningapps:
- if lightningapp.id == app_id:
- lightning_app = lightningapp
-
- assert lightning_app
-
- network_config = self.work_to_network_configs[work.name]
-
- for nc in lightning_app.spec.network_config:
- if nc.host == network_config.host:
- nc.enable = False
-
- client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=lightning_app.id,
- body=AppinstancesIdBody(name=lightning_app.name, spec=lightning_app.spec),
- )
-
- del self.work_to_network_configs[work.name]
-
- def _register_queues(self, app, work):
- backend = self._get_backend(work)
- backend._register_queues(app, work)
-
- def _get_backend(self, work):
- if work.cloud_compute.id == "default":
- return self.backends["multiprocess"]
- return self.backends["cloud"]
-
- @staticmethod
- def _get_proxy_scheme() -> str:
- return os.environ.get("LIGHTNING_PROXY_SCHEME", "https")
-
- @staticmethod
- def _get_app_id() -> str:
- return os.environ["LIGHTNING_CLOUD_APP_ID"]
-
- @staticmethod
- def _get_project_id() -> str:
- return os.environ["LIGHTNING_CLOUD_PROJECT_ID"]
-
- def stop_app(self, app):
- """Used to mark the App has stopped if everything has fine."""
- self.backends["cloud"].stop_app(app)
diff --git a/src/lightning/app/launcher/utils.py b/src/lightning/app/launcher/utils.py
deleted file mode 100644
index b6a3859830a49..0000000000000
--- a/src/lightning/app/launcher/utils.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import functools
-import logging
-import os
-import signal
-from typing import Any, Callable
-
-import psutil
-from lightning_cloud.openapi import V1LightningworkState
-
-from lightning.app import LightningApp, _logger, _root_logger
-from lightning.app import __version__ as LIGHTNING_VERSION
-from lightning.app.utilities.enum import WorkStageStatus
-
-
-def cloud_work_stage_to_work_status_stage(stage: V1LightningworkState) -> str:
- """Maps the Work stage names from the Grid cloud backend to the status names in the Lightning framework."""
- mapping = {
- V1LightningworkState.STOPPED: WorkStageStatus.STOPPED,
- V1LightningworkState.PENDING: WorkStageStatus.PENDING,
- V1LightningworkState.NOT_STARTED: WorkStageStatus.PENDING,
- V1LightningworkState.IMAGE_BUILDING: WorkStageStatus.PENDING,
- V1LightningworkState.RUNNING: WorkStageStatus.RUNNING,
- V1LightningworkState.FAILED: WorkStageStatus.FAILED,
- }
- if stage not in mapping:
- raise ValueError(f"Cannot map the lightning-cloud work state {stage} to the lightning status stage.")
- return mapping[stage]
-
-
-def _print_to_logger_info(*args: Any, **kwargs: Any) -> None:
- # TODO Find a better way to re-direct print to loggers.
- _logger.info(" ".join([str(v) for v in args]))
-
-
-def convert_print_to_logger_info(func: Callable) -> Callable:
- """This function is used to transform any print into logger.info calls, so it gets tracked in the cloud."""
-
- @functools.wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> Any:
- original_print = __builtins__["print"]
- __builtins__["print"] = _print_to_logger_info
- res = func(*args, **kwargs)
- __builtins__["print"] = original_print
- return res
-
- return wrapper
-
-
-def _enable_debugging() -> None:
- tar_file = os.path.join(os.getcwd(), f"lightning-{LIGHTNING_VERSION}.tar.gz")
-
- if not os.path.exists(tar_file):
- return
-
- _root_logger.propagate = True
- _logger.propagate = True
- _root_logger.setLevel(logging.DEBUG)
- _root_logger.debug("Setting debugging mode.")
-
-
-def enable_debugging(func: Callable) -> Callable:
- """This function is used set the logging level to DEBUG and set it back to INFO once the function is done."""
-
- @functools.wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> Any:
- _enable_debugging()
- res = func(*args, **kwargs)
- _logger.setLevel(logging.INFO)
- return res
-
- return wrapper
-
-
-def exit_app(app: LightningApp) -> None:
- """This function checks if dumb-init is running on process 0 and exits the containter with exit code 0.
-
- Otherwise we fall back to stopping the app via backend API call
-
- """
- try:
- # Get process information for PID 1, where dumb-init is running
- process = psutil.Process(1)
- process_name = process.name()
-
- # This kills the dumb-init process running on pid 1
- # There's issues propagating the exit code through regular python
- # program exit, so we directly kill the dumb-init process
- # which causes the flow container to exit with status code 0
- if "dumb-init" in process_name.lower():
- print("Killing dumb-init and exiting the container..")
- os.kill(1, signal.SIGTERM)
- else:
- print("Process 1 not running dumb-init. Stopping the app..")
- app.backend.stop_app(app)
- except psutil.NoSuchProcess:
- print("Process with PID 1 not found. Stopping the app..")
- app.backend.stop_app(app)
diff --git a/src/lightning/app/pdb/__init__.py b/src/lightning/app/pdb/__init__.py
deleted file mode 100644
index 260b2505c005d..0000000000000
--- a/src/lightning/app/pdb/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from lightning.app.pdb.pdb import MPPdb, set_trace
-
-# Enable breakpoint within forked processes.
-__builtins__["breakpoint"] = set_trace
-
-__all__ = ["set_trace", "MPPdb"]
diff --git a/src/lightning/app/pdb/pdb.py b/src/lightning/app/pdb/pdb.py
deleted file mode 100644
index a9249437cc698..0000000000000
--- a/src/lightning/app/pdb/pdb.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import multiprocessing
-import os
-import pdb
-import sys
-
-_stdin = [None]
-_stdin_lock = multiprocessing.Lock()
-try:
- _stdin_fd = sys.stdin.fileno()
-except Exception:
- _stdin_fd = None
-
-
-# Taken from https://github.com/facebookresearch/metaseq/blob/main/metaseq/pdb.py
-class MPPdb(pdb.Pdb):
- """A Pdb wrapper that works in a multiprocessing environment."""
-
- def __init__(self) -> None:
- pdb.Pdb.__init__(self, nosigint=True)
-
- def _cmdloop(self) -> None:
- stdin_back = sys.stdin
- with _stdin_lock:
- try:
- if _stdin_fd is not None:
- if not _stdin[0]:
- _stdin[0] = os.fdopen(_stdin_fd)
- sys.stdin = _stdin[0]
- self.cmdloop()
- finally:
- sys.stdin = stdin_back
-
-
-def set_trace() -> None:
- pdb = MPPdb()
- pdb.set_trace(sys._getframe().f_back)
diff --git a/src/lightning/app/plugin/__init__.py b/src/lightning/app/plugin/__init__.py
deleted file mode 100644
index 8d08c38eb0d5c..0000000000000
--- a/src/lightning/app/plugin/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from lightning.app.plugin.plugin import LightningPlugin
-
-__all__ = ["LightningPlugin"]
diff --git a/src/lightning/app/plugin/plugin.py b/src/lightning/app/plugin/plugin.py
deleted file mode 100644
index 78165ba065d2d..0000000000000
--- a/src/lightning/app/plugin/plugin.py
+++ /dev/null
@@ -1,237 +0,0 @@
-# Copyright The Lightning team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import os
-import shutil
-import tarfile
-import tempfile
-from pathlib import Path
-from typing import Any, Dict
-from urllib.parse import urlparse
-
-import requests
-import uvicorn
-from fastapi import FastAPI, HTTPException, status
-from fastapi.middleware.cors import CORSMiddleware
-from lightning_cloud.openapi import Externalv1LightningappInstance
-from pydantic import BaseModel
-
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.component import _set_flow_context
-from lightning.app.utilities.enum import AppStage
-from lightning.app.utilities.load_app import _load_plugin_from_file
-
-logger = Logger(__name__)
-
-_PLUGIN_MAX_CLIENT_TRIES: int = 3
-_PLUGIN_INTERNAL_DIR_PATH: str = f"{os.environ.get('HOME', '')}/internal"
-
-
-class LightningPlugin:
- """A ``LightningPlugin`` is a single-file Python class that can be executed within a cloudspace to perform
- actions."""
-
- def __init__(self) -> None:
- self.project_id = ""
- self.cloudspace_id = ""
- self.cluster_id = ""
- self.source_app = ""
- self.keep_machines_after_stop = False
-
- def run(self, *args: str, **kwargs: str) -> Externalv1LightningappInstance:
- """Override with the logic to execute on the cloudspace."""
- raise NotImplementedError
-
- def run_job(self, name: str, app_entrypoint: str, env_vars: Dict[str, str] = {}) -> Externalv1LightningappInstance:
- """Run a job in the cloudspace associated with this plugin.
-
- Args:
- name: The name of the job.
- app_entrypoint: The path of the file containing the app to run.
- env_vars: Additional env vars to set when running the app.
-
- Returns:
- The spec of the created LightningappInstance.
-
- """
- from lightning.app.runners.backends.cloud import CloudBackend
- from lightning.app.runners.cloud import CloudRuntime
-
- logger.info(f"Processing job run request. name: {name}, app_entrypoint: {app_entrypoint}, env_vars: {env_vars}")
-
- # Dispatch the job
- _set_flow_context()
-
- entrypoint_file = Path(app_entrypoint)
-
- app = CloudRuntime.load_app_from_file(str(entrypoint_file.resolve().absolute()), env_vars=env_vars)
-
- app.stage = AppStage.BLOCKING
-
- runtime = CloudRuntime(
- app=app,
- entrypoint=entrypoint_file,
- start_server=True,
- env_vars=env_vars,
- secrets={},
- run_app_comment_commands=True,
- backend=CloudBackend(entrypoint_file, client_max_tries=_PLUGIN_MAX_CLIENT_TRIES),
- )
- # Used to indicate Lightning has been dispatched
- os.environ["LIGHTNING_DISPATCHED"] = "1"
-
- return runtime.cloudspace_dispatch(
- project_id=self.project_id,
- cloudspace_id=self.cloudspace_id,
- name=name,
- cluster_id=self.cluster_id,
- source_app=self.source_app,
- keep_machines_after_stop=self.keep_machines_after_stop,
- )
-
- def _setup(
- self,
- project_id: str,
- cloudspace_id: str,
- cluster_id: str,
- source_app: str,
- keep_machines_after_stop: bool,
- ) -> None:
- self.source_app = source_app
- self.project_id = project_id
- self.cloudspace_id = cloudspace_id
- self.cluster_id = cluster_id
- self.keep_machines_after_stop = keep_machines_after_stop
-
-
-class _Run(BaseModel):
- plugin_entrypoint: str
- source_code_url: str
- project_id: str
- cloudspace_id: str
- cluster_id: str
- plugin_arguments: Dict[str, str]
- source_app: str
- keep_machines_after_stop: bool
-
-
-def _run_plugin(run: _Run) -> Dict[str, Any]:
- from lightning.app.runners.cloud import _to_clean_dict
-
- """Create a run with the given name and entrypoint under the cloudspace with the given ID."""
- with tempfile.TemporaryDirectory() as tmpdir:
- download_path = os.path.join(tmpdir, "source.tar.gz")
- source_path = os.path.join(tmpdir, "source")
- os.makedirs(source_path)
-
- # Download the tarball
- try:
- logger.info(f"Downloading plugin source: {run.source_code_url}")
-
- # Sometimes the URL gets encoded, so we parse it here
- source_code_url = urlparse(run.source_code_url).geturl()
-
- response = requests.get(source_code_url)
-
- # TODO: Backoff retry a few times in case the URL is flaky
- response.raise_for_status()
-
- with open(download_path, "wb") as f:
- f.write(response.content)
- except Exception as ex:
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
- detail=f"Error downloading plugin source: {str(ex)}.",
- )
-
- # Extract
- try:
- logger.info("Extracting plugin source.")
-
- with tarfile.open(download_path, "r:gz") as tf:
- tf.extractall(source_path) # noqa: S202
- except Exception as ex:
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
- detail=f"Error extracting plugin source: {str(ex)}.",
- )
-
- # Import the plugin
- try:
- logger.info(f"Importing plugin: {run.plugin_entrypoint}")
-
- plugin = _load_plugin_from_file(os.path.join(source_path, run.plugin_entrypoint))
- except Exception as ex:
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error loading plugin: {str(ex)}."
- )
-
- # Allow devs to add files to the app source
- if os.path.isdir(_PLUGIN_INTERNAL_DIR_PATH):
- shutil.copytree(_PLUGIN_INTERNAL_DIR_PATH, source_path, dirs_exist_ok=True)
-
- # Ensure that apps are dispatched from the temp directory
- cwd = os.getcwd()
- os.chdir(source_path)
-
- # Setup and run the plugin
- try:
- logger.info(
- "Running plugin. "
- f"project_id: {run.project_id}, cloudspace_id: {run.cloudspace_id}, cluster_id: {run.cluster_id}."
- )
-
- plugin._setup(
- project_id=run.project_id,
- cloudspace_id=run.cloudspace_id,
- cluster_id=run.cluster_id,
- source_app=run.source_app,
- keep_machines_after_stop=run.keep_machines_after_stop,
- )
- app_instance = plugin.run(**run.plugin_arguments)
- return _to_clean_dict(app_instance, True)
- except Exception as ex:
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error running plugin: {str(ex)}."
- )
- finally:
- os.chdir(cwd)
-
-
-async def _healthz() -> Dict[str, str]:
- """Health check endpoint."""
- return {"status": "ok"}
-
-
-def _start_plugin_server(port: int) -> None:
- """Start the plugin server which can be used to dispatch apps or run plugins."""
-
- fastapi_service = FastAPI()
-
- fastapi_service.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
-
- fastapi_service.post("/v1/runs")(_run_plugin)
- fastapi_service.get("/healthz", status_code=200)(_healthz)
-
- uvicorn.run(
- app=fastapi_service,
- host="127.0.0.1",
- port=port,
- log_level="error",
- )
diff --git a/src/lightning/app/runners/__init__.py b/src/lightning/app/runners/__init__.py
deleted file mode 100644
index 985203033da09..0000000000000
--- a/src/lightning/app/runners/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from lightning.app.runners.cloud import CloudRuntime
-from lightning.app.runners.multiprocess import MultiProcessRuntime
-from lightning.app.runners.runtime import Runtime, dispatch
-from lightning.app.utilities.app_commands import run_app_commands
-from lightning.app.utilities.load_app import load_app_from_file
-
-__all__ = [
- "dispatch",
- "load_app_from_file",
- "run_app_commands",
- "Runtime",
- "MultiProcessRuntime",
- "CloudRuntime",
-]
diff --git a/src/lightning/app/runners/backends/__init__.py b/src/lightning/app/runners/backends/__init__.py
deleted file mode 100644
index be7b3a7457976..0000000000000
--- a/src/lightning/app/runners/backends/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from enum import Enum
-
-from lightning.app.core.constants import APP_SERVER_IN_CLOUD
-from lightning.app.runners.backends.backend import Backend
-from lightning.app.runners.backends.cloud import CloudBackend
-from lightning.app.runners.backends.docker import DockerBackend
-from lightning.app.runners.backends.mp_process import CloudMultiProcessingBackend, MultiProcessingBackend
-
-
-class BackendType(Enum):
- MULTIPROCESSING = "multiprocessing"
- DOCKER = "docker"
- CLOUD = "cloud"
-
- def get_backend(self, entrypoint_file: str) -> "Backend":
- if self == BackendType.MULTIPROCESSING:
- if APP_SERVER_IN_CLOUD:
- return CloudMultiProcessingBackend(entrypoint_file)
- return MultiProcessingBackend(entrypoint_file)
- if self == BackendType.DOCKER:
- return DockerBackend(entrypoint_file)
- if self == BackendType.CLOUD:
- return CloudBackend(entrypoint_file)
- raise ValueError("Unknown client type")
diff --git a/src/lightning/app/runners/backends/backend.py b/src/lightning/app/runners/backends/backend.py
deleted file mode 100644
index abd9bbe24c1a8..0000000000000
--- a/src/lightning/app/runners/backends/backend.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from abc import ABC, abstractmethod
-from functools import partial
-from typing import TYPE_CHECKING, Any, Callable, List, Optional
-
-from lightning.app.core.constants import PLUGIN_CHECKER
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.utilities.proxies import ProxyWorkRun, unwrap
-
-if TYPE_CHECKING:
- import lightning.app
-
-
-class Backend(ABC):
- """The Backend provides and interface for the framework to communicate with resources in the cloud."""
-
- def __init__(self, entrypoint_file: str, queues: QueuingSystem, queue_id: str) -> None:
- self.queues: QueuingSystem = queues
- self.queue_id = queue_id
- self.entrypoint_file = entrypoint_file
-
- @abstractmethod
- def create_work(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork") -> None:
- pass
-
- @abstractmethod
- def update_work_statuses(self, works: List["lightning.app.LightningWork"]) -> None:
- pass
-
- @abstractmethod
- def stop_all_works(self, works: List["lightning.app.LightningWork"]) -> None:
- pass
-
- @abstractmethod
- def resolve_url(self, app, base_url: Optional[str] = None) -> None:
- pass
-
- @abstractmethod
- def stop_work(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork") -> None:
- pass
-
- @abstractmethod
- def stop_works(self, works: "List[lightning.app.LightningWork]") -> None:
- pass
-
- def _dynamic_run_wrapper(
- self,
- *args: Any,
- app: "lightning.app.LightningApp",
- work: "lightning.app.LightningWork",
- work_run: Callable,
- **kwargs: Any,
- ) -> None:
- if not work.name:
- # the name is empty, which means this work was never assigned to a parent flow
- raise AttributeError(
- f"Failed to create process for {work.__class__.__name__}."
- f" Make sure to set this work as an attribute of a `LightningFlow` before calling the run method."
- )
-
- # 1. Create and register the queues associated the work
- self._register_queues(app, work)
-
- work.run = work_run
-
- # Note: This is an optimization as the MMT is created directly within the launcher.
- if PLUGIN_CHECKER.should_create_work(work):
- # 2. Create the work
- self.create_work(app, work)
-
- # 3. Attach backend
- work._backend = self
-
- # 4. Create the work proxy to manipulate the work
- work.run = ProxyWorkRun(
- work_run=work_run,
- work_name=work.name,
- work=work,
- caller_queue=app.caller_queues[work.name],
- )
-
- # 5. Run the work proxy
- return work.run(*args, **kwargs)
-
- def _wrap_run_method(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork"):
- if work.run.__name__ == "_dynamic_run_wrapper":
- return
-
- work.run = partial(self._dynamic_run_wrapper, app=app, work=work, work_run=unwrap(work.run))
-
- def _prepare_queues(self, app: "lightning.app.LightningApp"):
- kw = {"queue_id": self.queue_id}
- app.delta_queue = self.queues.get_delta_queue(**kw)
- app.readiness_queue = self.queues.get_readiness_queue(**kw)
- app.api_response_queue = self.queues.get_api_response_queue(**kw)
- app.error_queue = self.queues.get_error_queue(**kw)
- app.api_publish_state_queue = self.queues.get_api_state_publish_queue(**kw)
- app.api_delta_queue = app.delta_queue
- app.request_queues = {}
- app.response_queues = {}
- app.copy_request_queues = {}
- app.copy_response_queues = {}
- app.caller_queues = {}
- app.work_queues = {}
- app.flow_to_work_delta_queues = {}
-
- def _register_queues(self, app, work):
- kw = {"queue_id": self.queue_id, "work_name": work.name}
- app.request_queues.update({work.name: self.queues.get_orchestrator_request_queue(**kw)})
- app.response_queues.update({work.name: self.queues.get_orchestrator_response_queue(**kw)})
- app.copy_request_queues.update({work.name: self.queues.get_orchestrator_copy_request_queue(**kw)})
- app.copy_response_queues.update({work.name: self.queues.get_orchestrator_copy_response_queue(**kw)})
- app.caller_queues.update({work.name: self.queues.get_caller_queue(**kw)})
- app.flow_to_work_delta_queues.update({work.name: self.queues.get_flow_to_work_delta_queue(**kw)})
-
-
-class WorkManager(ABC):
- """The work manager is an interface for the backend, runtime to control the LightningWork."""
-
- def __init__(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork"):
- pass
-
- @abstractmethod
- def start(self) -> None:
- pass
-
- @abstractmethod
- def kill(self) -> None:
- pass
-
- @abstractmethod
- def restart(self) -> None:
- pass
-
- @abstractmethod
- def is_alive(self) -> bool:
- pass
diff --git a/src/lightning/app/runners/backends/cloud.py b/src/lightning/app/runners/backends/cloud.py
deleted file mode 100644
index 0d3eeefef8cbe..0000000000000
--- a/src/lightning/app/runners/backends/cloud.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import TYPE_CHECKING, List, Optional
-
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.runners.backends import Backend
-from lightning.app.utilities.network import LightningClient
-
-if TYPE_CHECKING:
- import lightning.app
-
-
-class CloudBackend(Backend):
- def __init__(
- self,
- entrypoint_file,
- queue_id: Optional[str] = None,
- status_update_interval: Optional[int] = None,
- client_max_tries: Optional[int] = None,
- ):
- super().__init__(entrypoint_file, queues=QueuingSystem.MULTIPROCESS, queue_id=queue_id)
- self.client = LightningClient(max_tries=client_max_tries)
-
- def create_work(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork") -> None:
- raise NotImplementedError
-
- def update_work_statuses(self, works: List["lightning.app.LightningWork"]) -> None:
- raise NotImplementedError
-
- def stop_all_works(self, works: List["lightning.app.LightningWork"]) -> None:
- raise NotImplementedError
-
- def resolve_url(self, app, base_url: Optional[str] = None) -> None:
- raise NotImplementedError
-
- def stop_work(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork") -> None:
- raise NotImplementedError
-
- def stop_works(self, works: "List[lightning.app.LightningWork]") -> None:
- raise NotImplementedError
diff --git a/src/lightning/app/runners/backends/docker.py b/src/lightning/app/runners/backends/docker.py
deleted file mode 100644
index cd3f14a9e2166..0000000000000
--- a/src/lightning/app/runners/backends/docker.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from time import time
-from typing import List, Optional
-
-import lightning.app
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.runners.backends.backend import Backend
-
-
-class DockerBackend(Backend):
- def resolve_url(self, app, base_url: Optional[str] = None) -> None:
- pass
-
- def stop_work(self, app: "lightning.app.LightningApp", work: "lightning.app.LightningWork") -> None:
- pass
-
- def __init__(self, entrypoint_file: str):
- super().__init__(entrypoint_file=entrypoint_file, queues=QueuingSystem.REDIS, queue_id=str(int(time())))
-
- def create_work(self, app, work):
- pass
-
- def update_work_statuses(self, works) -> None:
- pass
-
- def stop_all_works(self, works: List["lightning.app.LightningWork"]) -> None:
- pass
-
- def stop_works(self, works: "List[lightning.app.LightningWork]") -> None:
- pass
diff --git a/src/lightning/app/runners/backends/mp_process.py b/src/lightning/app/runners/backends/mp_process.py
deleted file mode 100644
index ddd0ed6eb5272..0000000000000
--- a/src/lightning/app/runners/backends/mp_process.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import multiprocessing
-from typing import Any, List, Optional
-
-import lightning.app
-from lightning.app.core import constants
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.runners.backends.backend import Backend, WorkManager
-from lightning.app.utilities.enum import WorkStageStatus
-from lightning.app.utilities.network import _check_service_url_is_ready, find_free_network_port
-from lightning.app.utilities.port import disable_port, enable_port
-from lightning.app.utilities.proxies import ProxyWorkRun, WorkRunner
-
-
-class MultiProcessWorkManager(WorkManager):
- def __init__(self, app, work):
- self.app = app
- self.work = work
- self._process = None
-
- def start(self):
- self._work_runner = WorkRunner(
- work=self.work,
- work_name=self.work.name,
- caller_queue=self.app.caller_queues[self.work.name],
- delta_queue=self.app.delta_queue,
- readiness_queue=self.app.readiness_queue,
- error_queue=self.app.error_queue,
- request_queue=self.app.request_queues[self.work.name],
- response_queue=self.app.response_queues[self.work.name],
- copy_request_queue=self.app.copy_request_queues[self.work.name],
- copy_response_queue=self.app.copy_response_queues[self.work.name],
- flow_to_work_delta_queue=self.app.flow_to_work_delta_queues[self.work.name],
- run_executor_cls=self.work._run_executor_cls,
- )
-
- start_method = self.work._start_method
- context = multiprocessing.get_context(start_method)
- self._process = context.Process(target=self._work_runner)
- self._process.start()
-
- def kill(self):
- self._process.terminate()
-
- def restart(self):
- assert not self.is_alive()
- work = self._work_runner.work
- # un-wrap ProxyRun.
- is_proxy = isinstance(work.run, ProxyWorkRun)
- if is_proxy:
- work_run = work.run
- work.run = work_run.work_run
- work._restarting = True
- self.start()
- if is_proxy:
- work.run = work_run
-
- def is_alive(self) -> bool:
- return self._process.is_alive()
-
-
-class MultiProcessingBackend(Backend):
- def __init__(self, entrypoint_file: str):
- super().__init__(entrypoint_file=entrypoint_file, queues=QueuingSystem.MULTIPROCESS, queue_id="0")
-
- def create_work(self, app, work) -> None:
- if constants.LIGHTNING_CLOUDSPACE_HOST is not None:
- # Override the port if set by the user
- work._port = find_free_network_port()
- work._host = "0.0.0.0" # noqa: S104
- work._future_url = f"https://{work.port}-{constants.LIGHTNING_CLOUDSPACE_HOST}"
-
- app.processes[work.name] = MultiProcessWorkManager(app, work)
- app.processes[work.name].start()
- self.resolve_url(app)
- app._update_layout()
-
- def update_work_statuses(self, works) -> None:
- pass
-
- def stop_works(self, works: "List[lightning.app.LightningWork]") -> None:
- for w in works:
- w.stop()
-
- def stop_all_works(self, works: List["lightning.app.LightningWork"]) -> None:
- pass
-
- def resolve_url(self, app, base_url: Optional[str] = None) -> None:
- for work in app.works:
- if (
- work.status.stage in (WorkStageStatus.RUNNING, WorkStageStatus.SUCCEEDED)
- and work._url == ""
- and work._port
- ):
- url = work._future_url if work._future_url else f"http://{work._host}:{work._port}"
- if _check_service_url_is_ready(url, metadata=f"Checking {work.name}"):
- work._url = url
-
- def stop_work(self, app, work: "lightning.app.LightningWork") -> None:
- work_manager: MultiProcessWorkManager = app.processes[work.name]
- work_manager.kill()
-
- def delete_work(self, app, work: "lightning.app.LightningWork") -> None:
- self.stop_work(app, work)
-
-
-class CloudMultiProcessingBackend(MultiProcessingBackend):
- def __init__(self, *args: Any, **kwargs: Any):
- super().__init__(*args, **kwargs)
-
- # Note: Track the open ports to close them on termination.
- self.ports = []
-
- def create_work(self, app, work) -> None:
- work._host = "0.0.0.0" # noqa: S104
- nc = enable_port()
- self.ports.append(nc.port)
- work._port = nc.port
- work._future_url = f"https://{nc.host}"
- return super().create_work(app, work)
-
- def stop_work(self, app, work: "lightning.app.LightningWork") -> None:
- disable_port(work._port)
- self.ports = [port for port in self.ports if port != work._port]
- return super().stop_work(app, work)
-
- def delete_work(self, app, work: "lightning.app.LightningWork") -> None:
- self.stop_work(app, work)
diff --git a/src/lightning/app/runners/cloud.py b/src/lightning/app/runners/cloud.py
deleted file mode 100644
index 7928874a775d1..0000000000000
--- a/src/lightning/app/runners/cloud.py
+++ /dev/null
@@ -1,1109 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import fnmatch
-import json
-import os
-import random
-import re
-import string
-import sys
-import time
-from dataclasses import dataclass
-from functools import partial
-from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple, Union
-from urllib.parse import quote
-
-import click
-import rich
-from lightning_cloud.openapi import (
- Body3,
- Body4,
- CloudspaceIdRunsBody,
- Externalv1LightningappInstance,
- Gridv1ImageSpec,
- IdGetBody,
- ProjectIdCloudspacesBody,
- V1BuildSpec,
- V1CloudSpace,
- V1DataConnectionMount,
- V1DependencyFileInfo,
- V1Drive,
- V1DriveSpec,
- V1DriveStatus,
- V1DriveType,
- V1EnvVar,
- V1Flowserver,
- V1LightningappInstanceSpec,
- V1LightningappInstanceState,
- V1LightningAuth,
- V1LightningBasicAuth,
- V1LightningRun,
- V1LightningworkDrives,
- V1LightningworkSpec,
- V1Membership,
- V1Metadata,
- V1NetworkConfig,
- V1PackageManager,
- V1PythonDependencyInfo,
- V1QueueServerType,
- V1SourceType,
- V1UserRequestedComputeConfig,
- V1UserRequestedFlowComputeConfig,
- V1Work,
-)
-from lightning_cloud.openapi.rest import ApiException
-
-from lightning.app.core.app import LightningApp
-from lightning.app.core.constants import (
- CLOUD_UPLOAD_WARNING,
- DEFAULT_NUMBER_OF_EXPOSED_PORTS,
- DISABLE_DEPENDENCY_CACHE,
- ENABLE_APP_COMMENT_COMMAND_EXECUTION,
- ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER,
- ENABLE_PULLING_STATE_ENDPOINT,
- ENABLE_PUSHING_STATE_ENDPOINT,
- LIGHTNING_CLOUD_PRINT_SPECS,
- SYS_CUSTOMIZATIONS_SYNC_ROOT,
- enable_interruptible_works,
- enable_multiple_works_in_default_container,
- get_cloud_queue_type,
- get_lightning_cloud_url,
-)
-from lightning.app.core.work import LightningWork
-from lightning.app.runners.backends.cloud import CloudBackend
-from lightning.app.runners.runtime import Runtime
-from lightning.app.source_code import LocalSourceCodeDir
-from lightning.app.source_code.copytree import _IGNORE_FUNCTION, _filter_ignored, _parse_lightningignore
-from lightning.app.storage import Drive, Mount
-from lightning.app.utilities.app_helpers import Logger, _is_headless
-from lightning.app.utilities.auth import _credential_string_to_basic_auth_params
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.clusters import _ensure_cluster_project_binding, _get_default_cluster
-from lightning.app.utilities.dependency_caching import get_hash
-from lightning.app.utilities.load_app import load_app_from_file
-from lightning.app.utilities.packaging.app_config import AppConfig, _get_config_file
-from lightning.app.utilities.packaging.lightning_utils import _prepare_lightning_wheels_and_requirements
-from lightning.app.utilities.secrets import _names_to_ids
-
-logger = Logger(__name__)
-
-
-def _to_clean_dict(swagger_object, map_attributes):
- """Returns the swagger object properties as a dict with correct object names."""
- if hasattr(swagger_object, "to_dict"):
- attribute_map = swagger_object.attribute_map
- result = {}
- for key in attribute_map:
- value = getattr(swagger_object, key)
- value = _to_clean_dict(value, map_attributes)
- if value is not None and value != {}:
- key = attribute_map[key] if map_attributes else key
- result[key] = value
- return result
- if isinstance(swagger_object, list):
- return [_to_clean_dict(x, map_attributes) for x in swagger_object]
- if isinstance(swagger_object, dict):
- return {key: _to_clean_dict(value, map_attributes) for key, value in swagger_object.items()}
- return swagger_object
-
-
-@dataclass
-class CloudRuntime(Runtime):
- backend: Union[str, CloudBackend] = "cloud"
-
- def open(self, name: str, cluster_id: Optional[str] = None):
- """Method to open a CloudSpace with the root folder uploaded."""
- try:
- # Check for feature support
- user = self.backend.client.auth_service_get_user()
- if not user.features.code_tab:
- rich.print(
- "[red]The `lightning_app open` command has not been enabled for your account. "
- "To request access, please contact support@lightning.ai[/red]"
- )
- sys.exit(1)
-
- # Dispatch in four phases: resolution, validation, spec creation, API transactions
- # Resolution
- cloudspace_config = self._resolve_config(name, load=False)
- root = self._resolve_root()
- ignore_functions = self._resolve_open_ignore_functions()
- repo = self._resolve_repo(root, ignore_functions)
- project = self._resolve_project()
- existing_cloudspaces = self._resolve_existing_cloudspaces(project.project_id, cloudspace_config.name)
- cluster_id = self._resolve_cluster_id(cluster_id, project.project_id, existing_cloudspaces)
- existing_cloudspace, existing_run_instance = self._resolve_existing_run_instance(
- cluster_id, project.project_id, existing_cloudspaces
- )
- cloudspace_name = self._resolve_cloudspace_name(
- cloudspace_config.name,
- existing_cloudspace,
- existing_cloudspaces,
- )
- needs_credits = self._resolve_needs_credits(project)
-
- # Validation
- # Note: We do not validate the repo here since open only uploads a directory if asked explicitly
- self._validate_cluster_id(cluster_id, project.project_id)
-
- # Spec creation
- run_body = self._get_run_body(cluster_id, [], None, [], True, root, self.start_server)
-
- if existing_run_instance is not None:
- print(
- f"Re-opening the CloudSpace {cloudspace_config.name}. "
- "This operation will create a new run but will not overwrite the files in your CloudSpace."
- )
- else:
- print(f"The name of the CloudSpace is: {cloudspace_config.name}")
-
- # API transactions
- cloudspace_id = self._api_create_cloudspace_if_not_exists(
- project.project_id,
- cloudspace_name,
- existing_cloudspace,
- )
- self._api_stop_existing_run_instance(project.project_id, existing_run_instance)
- run = self._api_create_run(project.project_id, cloudspace_id, run_body)
- self._api_package_and_upload_repo(repo, run)
-
- if getattr(run, "cluster_id", None):
- print(f"Running on {run.cluster_id}")
-
- if "PYTEST_CURRENT_TEST" not in os.environ:
- click.launch(self._get_cloudspace_url(project, cloudspace_name, "code", needs_credits))
-
- except ApiException as ex:
- logger.error(ex.body)
- sys.exit(1)
-
- def cloudspace_dispatch(
- self,
- project_id: str,
- cloudspace_id: str,
- name: str,
- cluster_id: str,
- source_app: Optional[str] = None,
- keep_machines_after_stop: Optional[bool] = None,
- ) -> Externalv1LightningappInstance:
- """Slim dispatch for creating runs from a cloudspace. This dispatch avoids resolution of some properties such
- as the project and cluster IDs that are instead passed directly.
-
- Args:
- project_id: The ID of the project.
- cloudspace_id: The ID of the cloudspace.
- name: The name for the run.
- cluster_id: The ID of the cluster to run on.
- source_app: Name of the source app that triggered the run.
- keep_machines_after_stop: If true, machines will be left running after the run is finished and reused after
-
- Raises:
- ApiException: If there was an issue in the backend.
- RuntimeError: If there are validation errors.
- ValueError: If there are validation errors.
-
- Returns:
- The spec the created app instance.
-
- """
- # Dispatch in four phases: resolution, validation, spec creation, API transactions
- # Resolution
- root = self._resolve_root()
- # If the root will already be there, we don't need to upload and preserve the absolute entrypoint
- top_folder = os.getenv("FILESYSTEM_TOP_FOLDER_NAME", "project")
- absolute_entrypoint = str(root).startswith(f"/{top_folder}")
- # If system customization files found, it will set their location path
- sys_customizations_root = self._resolve_env_root()
- repo = self._resolve_repo(
- root,
- default_ignore=False,
- package_source=not absolute_entrypoint,
- sys_customizations_root=sys_customizations_root,
- )
- existing_instances = self._resolve_run_instances_by_name(project_id, name)
- name = self._resolve_run_name(name, existing_instances)
- cloudspace = self._resolve_cloudspace(project_id, cloudspace_id)
- queue_server_type = self._resolve_queue_server_type()
-
- self.app._update_index_file()
-
- # Validation
- # TODO: Validate repo and surface to the user
- # self._validate_repo(root, repo)
- self._validate_work_build_specs_and_compute()
- self._validate_drives()
- self._validate_mounts()
-
- # Spec creation
- flow_servers = self._get_flow_servers()
- network_configs = self._get_network_configs(flow_servers)
- works = self._get_works(cloudspace=cloudspace)
- run_body = self._get_run_body(
- cluster_id,
- flow_servers,
- network_configs,
- works,
- False,
- root,
- True,
- True,
- absolute_entrypoint,
- )
- env_vars = self._get_env_vars(self.env_vars, self.secrets, self.run_app_comment_commands)
-
- # API transactions
- logger.info(f"Creating cloudspace run. run_body: {run_body}")
- run = self._api_create_run(project_id, cloudspace_id, run_body)
-
- self._api_package_and_upload_repo(repo, run)
-
- logger.info(f"Creating cloudspace run instance. name: {name}")
- return self._api_create_run_instance(
- cluster_id,
- project_id,
- name,
- cloudspace_id,
- run.id,
- V1LightningappInstanceState.RUNNING,
- queue_server_type,
- env_vars,
- source_app=source_app,
- keep_machines_after_stop=keep_machines_after_stop,
- )
-
- def dispatch(
- self,
- name: str = "",
- cluster_id: Optional[str] = None,
- open_ui: bool = True,
- no_cache: bool = False,
- **kwargs: Any,
- ) -> None:
- """Method to dispatch and run the :class:`~lightning.app.core.app.LightningApp` in the cloud."""
- # not user facing error ideally - this should never happen in normal user workflow
- if not self.entrypoint:
- raise ValueError(
- "Entrypoint file not provided. Did you forget to "
- "initialize the Runtime object with `entrypoint` argument?"
- )
-
- cleanup_handle = None
-
- try:
- # Dispatch in four phases: resolution, validation, spec creation, API transactions
- # Resolution
- cloudspace_config = self._resolve_config(name)
- root = self._resolve_root()
- repo = self._resolve_repo(root)
- project = self._resolve_project()
- existing_cloudspaces = self._resolve_existing_cloudspaces(project.project_id, cloudspace_config.name)
- cluster_id = self._resolve_cluster_id(cluster_id, project.project_id, existing_cloudspaces)
- existing_cloudspace, existing_run_instance = self._resolve_existing_run_instance(
- cluster_id, project.project_id, existing_cloudspaces
- )
- cloudspace_name = self._resolve_cloudspace_name(
- cloudspace_config.name,
- existing_cloudspace,
- existing_cloudspaces,
- )
- queue_server_type = self._resolve_queue_server_type()
- needs_credits = self._resolve_needs_credits(project)
-
- # TODO: Move these
- cleanup_handle = _prepare_lightning_wheels_and_requirements(root)
- self.app._update_index_file()
-
- # Validation
- self._validate_repo(root, repo)
- self._validate_cluster_id(cluster_id, project.project_id)
- self._validate_work_build_specs_and_compute()
- self._validate_drives()
- self._validate_mounts()
-
- # Spec creation
- flow_servers = self._get_flow_servers()
- network_configs = self._get_network_configs(flow_servers)
- works = self._get_works()
- run_body = self._get_run_body(
- cluster_id, flow_servers, network_configs, works, no_cache, root, self.start_server
- )
- auth = self._get_auth(self.enable_basic_auth)
- env_vars = self._get_env_vars(self.env_vars, self.secrets, self.run_app_comment_commands)
-
- if LIGHTNING_CLOUD_PRINT_SPECS is not None:
- self._print_specs(run_body, LIGHTNING_CLOUD_PRINT_SPECS)
- sys.exit(0)
-
- print(f"The name of the app is: {cloudspace_name}")
-
- # API transactions
- cloudspace_id = self._api_create_cloudspace_if_not_exists(
- project.project_id,
- cloudspace_name,
- existing_cloudspace,
- )
- self._api_stop_existing_run_instance(project.project_id, existing_run_instance)
- run = self._api_create_run(project.project_id, cloudspace_id, run_body)
- self._api_package_and_upload_repo(repo, run)
-
- if getattr(run, "cluster_id", None):
- print(f"Running app on {run.cluster_id}")
-
- # Save the config for re-runs
- cloudspace_config.save_to_dir(root)
-
- desired_state = (
- V1LightningappInstanceState.STOPPED if needs_credits else V1LightningappInstanceState.RUNNING
- )
-
- if existing_run_instance is not None:
- run_instance = self._api_transfer_run_instance(
- project.project_id,
- run.id,
- existing_run_instance.id,
- desired_state,
- queue_server_type,
- env_vars,
- auth,
- )
- else:
- run_instance = self._api_create_run_instance(
- cluster_id,
- project.project_id,
- cloudspace_name,
- cloudspace_id,
- run.id,
- desired_state,
- queue_server_type,
- env_vars,
- auth,
- )
-
- if run_instance.status.phase == V1LightningappInstanceState.FAILED:
- raise RuntimeError("Failed to create the application. Cannot upload the source code.")
-
- # TODO: Remove testing dependency, but this would open a tab for each test...
- if open_ui and "PYTEST_CURRENT_TEST" not in os.environ:
- click.launch(
- self._get_app_url(project, run_instance, "logs" if run.is_headless else "web-ui", needs_credits)
- )
-
- if bool(int(os.getenv("LIGHTING_TESTING", "0"))):
- print(f"APP_LOGS_URL: {self._get_app_url(project, run_instance, 'logs')}")
-
- except ApiException as ex:
- logger.error(ex.body)
- sys.exit(1)
- finally:
- if cleanup_handle:
- cleanup_handle()
-
- @classmethod
- def load_app_from_file(cls, filepath: str, env_vars: Dict[str, str] = {}) -> "LightningApp":
- """Load a LightningApp from a file, mocking the imports."""
- # Pretend we are running in the cloud when loading the app locally
- os.environ["LAI_RUNNING_IN_CLOUD"] = "1"
-
- try:
- app = load_app_from_file(filepath, raise_exception=True, mock_imports=True, env_vars=env_vars)
- except FileNotFoundError as ex:
- raise ex
- except Exception:
- from lightning.app.testing.helpers import EmptyFlow
-
- # Create a generic app.
- logger.info("Could not load the app locally. Starting the app directly on the cloud.")
- app = LightningApp(EmptyFlow())
- finally:
- del os.environ["LAI_RUNNING_IN_CLOUD"]
- return app
-
- def _resolve_config(self, name: Optional[str], load: bool = True) -> AppConfig:
- """Find and load the config file if it exists (otherwise create an empty config).
-
- Override the name if provided.
-
- """
- config_file = _get_config_file(self.entrypoint)
- cloudspace_config = AppConfig.load_from_file(config_file) if config_file.exists() and load else AppConfig()
- if name:
- # Override the name if provided
- cloudspace_config.name = name
- return cloudspace_config
-
- def _resolve_root(self) -> Path:
- """Determine the root of the project."""
- root = Path(self.entrypoint).absolute()
- if root.is_file():
- root = root.parent
- return root
-
- def _resolve_env_root(self) -> Optional[Path]:
- """Determine whether the root of environment sync files exists."""
- root = Path(SYS_CUSTOMIZATIONS_SYNC_ROOT)
- if root.exists():
- return root
- return None
-
- def _resolve_open_ignore_functions(self) -> List[_IGNORE_FUNCTION]:
- """Used by the ``open`` method.
-
- If the entrypoint is a file, return an ignore function that will ignore everything except that file so only the
- file gets uploaded.
-
- """
- entrypoint = self.entrypoint.absolute()
- if entrypoint.is_file():
- return [lambda src, paths: [path for path in paths if path.absolute() == entrypoint]]
- return []
-
- def _resolve_repo(
- self,
- root: Path,
- ignore_functions: Optional[List[_IGNORE_FUNCTION]] = None,
- default_ignore: bool = True,
- package_source: bool = True,
- sys_customizations_root: Optional[Path] = None,
- ) -> LocalSourceCodeDir:
- """Gather and merge all lightningignores from the app children and create the ``LocalSourceCodeDir`` object."""
- if ignore_functions is None:
- ignore_functions = []
-
- if self.app is not None:
- flow_lightningignores = [flow.lightningignore for flow in self.app.flows]
- work_lightningignores = [work.lightningignore for work in self.app.works]
- lightningignores = flow_lightningignores + work_lightningignores
- if lightningignores:
- merged = sum(lightningignores, ())
- logger.debug(f"Found the following lightningignores: {merged}")
- patterns = _parse_lightningignore(merged)
- ignore_functions = [*ignore_functions, partial(_filter_ignored, root, patterns)]
-
- return LocalSourceCodeDir(
- path=root,
- ignore_functions=ignore_functions,
- default_ignore=default_ignore,
- package_source=package_source,
- sys_customizations_root=sys_customizations_root,
- )
-
- def _resolve_project(self, project_id: Optional[str] = None) -> V1Membership:
- """Determine the project to run on, choosing a default if multiple projects are found."""
- return _get_project(self.backend.client, project_id=project_id)
-
- def _resolve_existing_cloudspaces(self, project_id: str, cloudspace_name: str) -> List[V1CloudSpace]:
- """Lists all the cloudspaces with a name matching the provided cloudspace name."""
- # TODO: Add pagination, otherwise this could break if users have a lot of cloudspaces.
- existing_cloudspaces = self.backend.client.cloud_space_service_list_cloud_spaces(
- project_id=project_id
- ).cloudspaces
-
- # Search for cloudspaces with the given name (possibly with some random characters appended)
- pattern = re.escape(f"{cloudspace_name}-") + ".{4}"
- return [
- cloudspace
- for cloudspace in existing_cloudspaces
- if cloudspace.name == cloudspace_name or (re.fullmatch(pattern, cloudspace.name) is not None)
- ]
-
- def _resolve_cluster_id(
- self, cluster_id: Optional[str], project_id: str, existing_cloudspaces: List[V1CloudSpace]
- ) -> Optional[str]:
- """If cloudspaces exist and cluster is None, mimic cluster selection logic to choose a default."""
- # 1. Use the environement variables
- if cluster_id is None:
- cluster_id = os.getenv("LIGHTNING_CLUSTER_ID", None)
-
- # 2. Use the project bindings
- # TODO: Use the user prefered cluster.
- if cluster_id is None and len(existing_cloudspaces) > 0:
- # Determine the cluster ID
- cluster_id = _get_default_cluster(self.backend.client, project_id)
- return cluster_id
-
- def _resolve_existing_run_instance(
- self, cluster_id: Optional[str], project_id: str, existing_cloudspaces: List[V1CloudSpace]
- ) -> Tuple[Optional[V1CloudSpace], Optional[Externalv1LightningappInstance]]:
- """Look for an existing run and instance from one of the provided cloudspaces on the provided cluster."""
- existing_cloudspace = None
- existing_run_instance = None
-
- if cluster_id is not None:
- for cloudspace in existing_cloudspaces:
- run_instances = self.backend.client.lightningapp_instance_service_list_lightningapp_instances(
- project_id=project_id,
- app_id=cloudspace.id,
- ).lightningapps
- if run_instances and run_instances[0].spec.cluster_id == cluster_id:
- existing_cloudspace = cloudspace
- existing_run_instance = run_instances[0]
- break
- return existing_cloudspace, existing_run_instance
-
- def _resolve_run_instances_by_name(self, project_id: str, name: str) -> List[Externalv1LightningappInstance]:
- """Get all existing instances in the given project with the given name."""
- run_instances = self.backend.client.lightningapp_instance_service_list_lightningapp_instances(
- project_id=project_id,
- ).lightningapps
-
- return [run_instance for run_instance in run_instances if run_instance.display_name == name]
-
- def _resolve_cloudspace_name(
- self,
- cloudspace_name: str,
- existing_cloudspace: Optional[V1CloudSpace],
- existing_cloudspaces: List[V1CloudSpace],
- ) -> str:
- """If there are existing cloudspaces but not on the cluster - choose a randomised name."""
- if len(existing_cloudspaces) > 0 and existing_cloudspace is None:
- name_exists = True
- while name_exists:
- random_name = cloudspace_name + "-" + "".join(random.sample(string.ascii_letters, 4))
- name_exists = any(app.name == random_name for app in existing_cloudspaces)
-
- cloudspace_name = random_name
- return cloudspace_name
-
- def _resolve_run_name(
- self,
- name: str,
- existing_instances: List[Externalv1LightningappInstance],
- ) -> str:
- """If there are existing instances with the same name - choose a randomised name."""
- if len(existing_instances) > 0:
- name_exists = True
- while name_exists:
- random_name = name + "-" + "".join(random.sample(string.ascii_letters, 4))
- name_exists = any(app.name == random_name for app in existing_instances)
-
- name = random_name
- return name
-
- def _resolve_cloudspace(self, project_id: str, cloudspace_id: str) -> Optional[V1CloudSpace]:
- """Returns a cloudspace by project_id and cloudspace_id, if exists."""
- return self.backend.client.cloud_space_service_get_cloud_space(
- project_id=project_id,
- id=cloudspace_id,
- )
-
- def _resolve_queue_server_type(self) -> V1QueueServerType:
- """Resolve the cloud queue type from the environment."""
- queue_server_type = V1QueueServerType.UNSPECIFIED
- # Note: Enable app to select their own queue type.
- queue_type = get_cloud_queue_type()
- if queue_type == "http":
- queue_server_type = V1QueueServerType.HTTP
- elif queue_type == "redis":
- queue_server_type = V1QueueServerType.REDIS
- return queue_server_type
-
- @staticmethod
- def _resolve_needs_credits(project: V1Membership):
- """Check if the user likely needs credits to run the app with its hardware.
-
- Returns False if user has 1 or more credits.
-
- """
- balance = project.balance
- if balance is None:
- balance = 0 # value is missing in some tests
-
- needs_credits = balance < 1
- if needs_credits:
- logger.warn("You may need Lightning credits to run your apps on the cloud.")
- return needs_credits
-
- @staticmethod
- def _validate_repo(root: Path, repo: LocalSourceCodeDir) -> None:
- """This method is used to inform the users if their folder files are large and how to filter them."""
- excludes = set(fnmatch.filter(repo.files, "*lightning-*.tar.gz"))
- excludes.update(fnmatch.filter(repo.files, ".lightningignore"))
- files = [Path(f) for f in repo.files if f not in excludes]
- file_sizes = {f: f.stat().st_size for f in files}
- mb = 1000_000
- app_folder_size_in_mb = sum(file_sizes.values()) / mb
- if app_folder_size_in_mb > CLOUD_UPLOAD_WARNING:
- # filter out files under 0.01mb
- relevant_files = {f: sz for f, sz in file_sizes.items() if sz > 0.01 * mb}
- if relevant_files:
- by_largest = dict(sorted(relevant_files.items(), key=lambda x: x[1], reverse=True))
- by_largest = dict(list(by_largest.items())[:25]) # trim
- largest_paths_msg = "\n".join(
- f"{round(sz / mb, 5)} MB: {p.relative_to(root)}" for p, sz in by_largest.items()
- )
- largest_paths_msg = f"Here are the largest files:\n{largest_paths_msg}\n"
- else:
- largest_paths_msg = ""
- warning_msg = (
- f"Your application folder '{root.absolute()}' is more than {CLOUD_UPLOAD_WARNING} MB. "
- f"The total size is {round(app_folder_size_in_mb, 2)} MB. {len(files)} files were uploaded.\n"
- + largest_paths_msg
- + "Perhaps you should try running the app in an empty directory.\n"
- + "You can ignore some files or folders by adding them to `.lightningignore`.\n"
- + " You can also set the `self.lightningingore` attribute in a Flow or Work."
- )
-
- logger.warn(warning_msg)
-
- def _validate_cluster_id(self, cluster_id: Optional[str], project_id: str):
- """Check that the provided cluster exists and ensure that it is bound to the given project."""
- if cluster_id is not None:
- # Verify that the cluster exists
- list_clusters_resp = self.backend.client.cluster_service_list_clusters()
- cluster_ids = [cluster.id for cluster in list_clusters_resp.clusters]
- if cluster_id not in cluster_ids:
- raise ValueError(
- f"You requested to run on cluster {cluster_id}, but that cluster doesn't exist."
- f" Found {list_clusters_resp} with project_id: {project_id}"
- )
-
- _ensure_cluster_project_binding(self.backend.client, project_id, cluster_id)
-
- def _validate_work_build_specs_and_compute(self) -> None:
- """Check that the cloud compute and build configs are valid for all works in the app."""
- for work in self.app.works:
- if work.cloud_build_config.image is not None and work.cloud_compute.name == "default":
- raise ValueError(
- f"You requested a custom base image for the Work with name '{work.name}', but custom images are "
- "currently not supported on the default cloud compute instance. Please choose a different "
- "configuration, for example `CloudCompute('cpu-medium')`."
- )
-
- def _validate_drives(self) -> None:
- """Check that all drives in the app have a valid protocol."""
- for work in self.app.works:
- for drive_attr_name, drive in [
- (k, getattr(work, k)) for k in work._state if isinstance(getattr(work, k), Drive)
- ]:
- if drive.protocol != "lit://":
- raise RuntimeError(
- f"Unknown drive protocol `{drive.protocol}` for drive `{work.name}.{drive_attr_name}`."
- )
-
- def _validate_mounts(self) -> None:
- """Check that all mounts in the app have a valid protocol."""
- for work in self.app.works:
- if work.cloud_compute.mounts is not None:
- mounts = work.cloud_compute.mounts
- for mount in [mounts] if isinstance(mounts, Mount) else mounts:
- if mount.protocol != "s3://":
- raise RuntimeError(f"Unknown mount protocol `{mount.protocol}` for work `{work.name}`.")
-
- def _get_flow_servers(self) -> List[V1Flowserver]:
- """Collect a spec for each flow that contains a frontend so that the backend knows for which flows it needs to
- start servers."""
- flow_servers: List[V1Flowserver] = []
- for flow_name in self.app.frontends:
- flow_server = V1Flowserver(name=flow_name)
- flow_servers.append(flow_server)
- return flow_servers
-
- @staticmethod
- def _get_network_configs(flow_servers: List[V1Flowserver]) -> Optional[List[V1NetworkConfig]]:
- """Get the list of network configs for the run if multiple works in default container is enabled."""
- network_configs = None
- if enable_multiple_works_in_default_container():
- network_configs = []
- initial_port = 8080 + 1 + len(flow_servers)
- for _ in range(DEFAULT_NUMBER_OF_EXPOSED_PORTS):
- network_configs.append(
- V1NetworkConfig(
- name="w" + str(initial_port),
- port=initial_port,
- )
- )
- initial_port += 1
- return network_configs
-
- @staticmethod
- def _get_drives(work: LightningWork) -> List[V1LightningworkDrives]:
- """Get the list of drive specifications for the provided work."""
- drives: List[V1LightningworkDrives] = []
- for drive_attr_name, drive in [
- (k, getattr(work, k)) for k in work._state if isinstance(getattr(work, k), Drive)
- ]:
- drives.append(
- V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name=f"{work.name}.{drive_attr_name}",
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.NO_MOUNT_S3,
- source_type=V1SourceType.S3,
- source=f"{drive.protocol}{drive.id}",
- ),
- status=V1DriveStatus(),
- ),
- ),
- )
-
- return drives
-
- @staticmethod
- def _get_mounts(work: LightningWork) -> List[V1LightningworkDrives]:
- """Get the list of mount specifications for the provided work."""
- mounts = []
- if work.cloud_compute.mounts is not None:
- mount_objects = work.cloud_compute.mounts
- for mount in [mount_objects] if isinstance(mount_objects, Mount) else mount_objects:
- mounts.append(
- V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name=work.name,
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.INDEXED_S3,
- source_type=V1SourceType.S3,
- source=mount.source,
- ),
- status=V1DriveStatus(),
- ),
- mount_location=str(mount.mount_path),
- )
- )
- return mounts
-
- def _get_works(self, cloudspace: Optional[V1CloudSpace] = None) -> List[V1Work]:
- """Get the list of work specs from the app."""
- works: List[V1Work] = []
- for work in self.app.works:
- if not work._start_with_flow:
- continue
-
- work_requirements = "\n".join(work.cloud_build_config.requirements)
- build_spec = V1BuildSpec(
- commands=work.cloud_build_config.build_commands(),
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages=work_requirements
- ),
- image=work.cloud_build_config.image,
- )
- user_compute_config = V1UserRequestedComputeConfig(
- name=work.cloud_compute.name,
- count=1,
- disk_size=work.cloud_compute.disk_size,
- spot=work.cloud_compute.interruptible,
- shm_size=work.cloud_compute.shm_size,
- affinity_identifier=work.cloud_compute.colocation_group_id,
- )
-
- drives = self._get_drives(work)
- mounts = self._get_mounts(work)
-
- data_connection_mounts: list[V1DataConnectionMount] = []
- if cloudspace is not None and cloudspace.code_config is not None:
- data_connection_mounts = cloudspace.code_config.data_connection_mounts
-
- random_name = "".join(random.choice(string.ascii_lowercase) for _ in range(5)) # noqa: S311
- work_spec = V1LightningworkSpec(
- build_spec=build_spec,
- drives=drives + mounts,
- user_requested_compute_config=user_compute_config,
- network_config=[V1NetworkConfig(name=random_name, port=work.port)],
- data_connection_mounts=data_connection_mounts,
- )
- works.append(V1Work(name=work.name, display_name=work.display_name, spec=work_spec))
-
- return works
-
- def _get_run_body(
- self,
- cluster_id: str,
- flow_servers: List[V1Flowserver],
- network_configs: Optional[List[V1NetworkConfig]],
- works: List[V1Work],
- no_cache: bool,
- root: Path,
- start_server: bool,
- should_mount_cloudspace_content: bool = False,
- absolute_entrypoint: bool = False,
- ) -> CloudspaceIdRunsBody:
- """Get the specification of the run creation request."""
- if absolute_entrypoint:
- # If the entrypoint will already exist in the cloud then we can choose to keep it as an absolute path.
- app_entrypoint_file = Path(self.entrypoint).absolute()
- else:
- # The entry point file needs to be relative to the root of the uploaded source file directory,
- # because the backend will invoke the lightning commands relative said source directory
- # TODO: we shouldn't set this if the entrypoint isn't a file but the backend gives an error if we don't
- app_entrypoint_file = Path(self.entrypoint).absolute().relative_to(root)
-
- run_body = CloudspaceIdRunsBody(
- cluster_id=cluster_id,
- app_entrypoint_file=str(app_entrypoint_file),
- enable_app_server=start_server,
- flow_servers=flow_servers,
- network_config=network_configs,
- works=works,
- local_source=True,
- should_mount_cloudspace_content=should_mount_cloudspace_content,
- )
-
- if self.app is not None:
- run_body.user_requested_flow_compute_config = V1UserRequestedFlowComputeConfig(
- name=self.app.flow_cloud_compute.name,
- shm_size=self.app.flow_cloud_compute.shm_size,
- spot=False,
- )
-
- run_body.is_headless = _is_headless(self.app)
-
- # if requirements file at the root of the repository is present,
- # we pass just the file name to the backend, so backend can find it in the relative path
- requirements_file = root / "requirements.txt"
- if requirements_file.is_file() and requirements_file.exists():
- requirements_path = requirements_file if absolute_entrypoint else "requirements.txt"
- run_body.image_spec = Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(package_manager=V1PackageManager.PIP, path=requirements_path)
- )
- if not DISABLE_DEPENDENCY_CACHE and not no_cache:
- # hash used for caching the dependencies
- run_body.dependency_cache_key = get_hash(requirements_file)
-
- return run_body
-
- @staticmethod
- def _get_auth(credentials: str) -> Optional[V1LightningAuth]:
- """If credentials are provided, parse them and return the auth spec."""
- auth = None
- if credentials != "":
- parsed_credentials = _credential_string_to_basic_auth_params(credentials)
- auth = V1LightningAuth(
- basic=V1LightningBasicAuth(
- username=parsed_credentials["username"], password=parsed_credentials["password"]
- )
- )
- return auth
-
- @staticmethod
- def _get_env_vars(
- env_vars: Dict[str, str], secrets: Dict[str, str], run_app_comment_commands: bool
- ) -> List[V1EnvVar]:
- """Generate the list of environment variable specs for the app, including variables set by the framework."""
- v1_env_vars = [V1EnvVar(name=k, value=v) for k, v in env_vars.items()]
-
- if len(secrets.values()) > 0:
- secret_names_to_ids = _names_to_ids(secrets.values())
- env_vars_from_secrets = [V1EnvVar(name=k, from_secret=secret_names_to_ids[v]) for k, v in secrets.items()]
- v1_env_vars.extend(env_vars_from_secrets)
-
- if run_app_comment_commands or ENABLE_APP_COMMENT_COMMAND_EXECUTION:
- v1_env_vars.append(V1EnvVar(name="ENABLE_APP_COMMENT_COMMAND_EXECUTION", value="1"))
-
- if enable_multiple_works_in_default_container():
- v1_env_vars.append(V1EnvVar(name="ENABLE_MULTIPLE_WORKS_IN_DEFAULT_CONTAINER", value="1"))
-
- if ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER:
- v1_env_vars.append(V1EnvVar(name="ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER", value="1"))
-
- if not ENABLE_PULLING_STATE_ENDPOINT:
- v1_env_vars.append(V1EnvVar(name="ENABLE_PULLING_STATE_ENDPOINT", value="0"))
-
- if not ENABLE_PUSHING_STATE_ENDPOINT:
- v1_env_vars.append(V1EnvVar(name="ENABLE_PUSHING_STATE_ENDPOINT", value="0"))
-
- if enable_interruptible_works():
- v1_env_vars.append(
- V1EnvVar(
- name="LIGHTNING_INTERRUPTIBLE_WORKS",
- value=os.getenv("LIGHTNING_INTERRUPTIBLE_WORKS", "0"),
- )
- )
-
- return v1_env_vars
-
- def _api_create_cloudspace_if_not_exists(
- self, project_id: str, name: str, existing_cloudspace: Optional[V1CloudSpace]
- ) -> str:
- """Create the cloudspace if it doesn't exist.
-
- Return the cloudspace ID.
-
- """
- if existing_cloudspace is None:
- cloudspace_body = ProjectIdCloudspacesBody(name=name, can_download_source_code=True)
- cloudspace = self.backend.client.cloud_space_service_create_cloud_space(
- project_id=project_id, body=cloudspace_body
- )
- return cloudspace.id
- return existing_cloudspace.id
-
- def _api_stop_existing_run_instance(
- self, project_id: str, existing_run_instance: Optional[Externalv1LightningappInstance]
- ) -> None:
- """If an existing instance is provided and it isn't stopped, stop it."""
- if existing_run_instance and existing_run_instance.status.phase != V1LightningappInstanceState.STOPPED:
- # TODO(yurij): Implement release switching in the UI and remove this
- # We can only switch release of the stopped instance
- existing_run_instance = self.backend.client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=existing_run_instance.id,
- body=Body3(spec=V1LightningappInstanceSpec(desired_state=V1LightningappInstanceState.STOPPED)),
- )
- # wait for the instance to stop for up to 150 seconds
- for _ in range(150):
- existing_run_instance = self.backend.client.lightningapp_instance_service_get_lightningapp_instance(
- project_id=project_id, id=existing_run_instance.id
- )
- if existing_run_instance.status.phase == V1LightningappInstanceState.STOPPED:
- break
- time.sleep(1)
- if existing_run_instance.status.phase != V1LightningappInstanceState.STOPPED:
- raise RuntimeError("Failed to stop the existing instance.")
-
- def _api_create_run(self, project_id: str, cloudspace_id: str, run_body: CloudspaceIdRunsBody) -> V1LightningRun:
- """Create and return the run."""
- return self.backend.client.cloud_space_service_create_lightning_run(
- project_id=project_id, cloudspace_id=cloudspace_id, body=run_body
- )
-
- def _api_transfer_run_instance(
- self,
- project_id: str,
- run_id: str,
- instance_id: str,
- desired_state: V1LightningappInstanceState,
- queue_server_type: Optional[V1QueueServerType] = None,
- env_vars: Optional[List[V1EnvVar]] = None,
- auth: Optional[V1LightningAuth] = None,
- ) -> Externalv1LightningappInstance:
- """Transfer an existing instance to the given run ID and update its specification.
-
- Return the instance.
-
- """
- run_instance = self.backend.client.lightningapp_instance_service_update_lightningapp_instance_release(
- project_id=project_id,
- id=instance_id,
- body=Body4(release_id=run_id),
- )
-
- self.backend.client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=instance_id,
- body=Body3(
- spec=V1LightningappInstanceSpec(
- desired_state=desired_state,
- queue_server_type=queue_server_type,
- env=env_vars,
- auth=auth,
- )
- ),
- )
-
- return run_instance
-
- def _api_create_run_instance(
- self,
- cluster_id: str,
- project_id: str,
- run_name: str,
- cloudspace_id: str,
- run_id: str,
- desired_state: V1LightningappInstanceState,
- queue_server_type: Optional[V1QueueServerType] = None,
- env_vars: Optional[List[V1EnvVar]] = None,
- auth: Optional[V1LightningAuth] = None,
- source_app: Optional[str] = None,
- keep_machines_after_stop: Optional[bool] = None,
- ) -> Externalv1LightningappInstance:
- """Create a new instance of the given run with the given specification."""
- return self.backend.client.cloud_space_service_create_lightning_run_instance(
- project_id=project_id,
- cloudspace_id=cloudspace_id,
- id=run_id,
- body=IdGetBody(
- cluster_id=cluster_id,
- name=run_name,
- desired_state=desired_state,
- queue_server_type=queue_server_type,
- env=env_vars,
- auth=auth,
- source_app=source_app,
- keep_machines_after_stop=keep_machines_after_stop,
- ),
- )
-
- @staticmethod
- def _api_package_and_upload_repo(
- repo: LocalSourceCodeDir,
- run: V1LightningRun,
- ) -> None:
- """Package and upload the provided local source code directory to the provided run."""
- if run.source_upload_url == "":
- raise RuntimeError("The source upload url is empty.")
- repo.package()
- repo.upload(url=run.source_upload_url)
-
- @staticmethod
- def _print_specs(run_body: CloudspaceIdRunsBody, print_format: str) -> None:
- """Print the given run body in either `web` or `gallery` format."""
- if print_format not in ("web", "gallery"):
- raise ValueError(
- f"`LIGHTNING_CLOUD_PRINT_SPECS` should be either `web` or `gallery`. You provided: {print_format}"
- )
-
- flow_servers_json = [{"Name": flow_server.name} for flow_server in run_body.flow_servers]
- logger.info(f"flow_servers: {flow_servers_json}")
- works_json = json.dumps(_to_clean_dict(run_body.works, print_format == "web"), separators=(",", ":"))
- logger.info(f"works: {works_json}")
- logger.info(f"entrypoint_file: {run_body.app_entrypoint_file}")
- requirements_path = getattr(getattr(run_body.image_spec, "dependency_file_info", ""), "path", "")
- logger.info(f"requirements_path: {requirements_path}")
-
- def _get_cloudspace_url(
- self, project: V1Membership, cloudspace_name: str, tab: str, need_credits: bool = False
- ) -> str:
- user = self.backend.client.auth_service_get_user()
- action = "?action=add_credits" if need_credits else ""
- paths = [
- user.username,
- project.name,
- "apps",
- cloudspace_name,
- tab,
- ]
- path = "/".join([quote(path, safe="") for path in paths])
- return f"{get_lightning_cloud_url()}/{path}{action}"
-
- def _get_app_url(
- self,
- project: V1Membership,
- run_instance: Externalv1LightningappInstance,
- tab: str,
- need_credits: bool = False,
- ) -> str:
- user = self.backend.client.auth_service_get_user()
- action = "?action=add_credits" if need_credits else ""
- if user.features.project_selector:
- paths = [
- user.username,
- project.name,
- "jobs",
- run_instance.name,
- tab,
- ]
- else:
- paths = [
- user.username,
- "apps",
- run_instance.id,
- tab,
- ]
- path = "/".join([quote(path, safe="") for path in paths])
- return f"{get_lightning_cloud_url()}/{path}{action}"
diff --git a/src/lightning/app/runners/multiprocess.py b/src/lightning/app/runners/multiprocess.py
deleted file mode 100644
index 94d627e95fc7b..0000000000000
--- a/src/lightning/app/runners/multiprocess.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import multiprocessing
-import os
-from dataclasses import dataclass
-from typing import Any, Union
-
-import click
-
-from lightning.app.api.http_methods import _add_tags_to_api, _validate_api
-from lightning.app.core import constants
-from lightning.app.core.api import start_server
-from lightning.app.runners.backends import Backend
-from lightning.app.runners.runtime import Runtime
-from lightning.app.storage.orchestrator import StorageOrchestrator
-from lightning.app.utilities.app_helpers import _is_headless, is_overridden
-from lightning.app.utilities.commands.base import _commands_to_api, _prepare_commands
-from lightning.app.utilities.component import _set_flow_context, _set_frontend_context
-from lightning.app.utilities.load_app import extract_metadata_from_app
-from lightning.app.utilities.network import find_free_network_port
-from lightning.app.utilities.port import disable_port
-
-
-@dataclass
-class MultiProcessRuntime(Runtime):
- """Runtime to launch the LightningApp into multiple processes.
-
- The MultiProcessRuntime will generate 1 process for each :class:`~lightning.app.core.work.LightningWork` and attach
- queues to enable communication between the different processes.
-
- """
-
- backend: Union[str, Backend] = "multiprocessing"
- _has_triggered_termination: bool = False
-
- def dispatch(self, *args: Any, open_ui: bool = True, **kwargs: Any):
- """Method to dispatch and run the LightningApp."""
- try:
- _set_flow_context()
-
- # Note: In case the runtime is used in the cloud.
- in_cloudspace = constants.LIGHTNING_CLOUDSPACE_HOST is not None
- self.host = "0.0.0.0" if constants.APP_SERVER_IN_CLOUD or in_cloudspace else self.host # noqa: S104
-
- self.app.backend = self.backend
- self.backend._prepare_queues(self.app)
- self.backend.resolve_url(self.app, "http://127.0.0.1")
- self.app._update_index_file()
-
- # set env variables
- os.environ.update(self.env_vars)
-
- # refresh the layout with the populated urls.
- self.app._update_layout()
-
- _set_frontend_context()
- for frontend in self.app.frontends.values():
- port = find_free_network_port()
-
- server_host = "0.0.0.0" if in_cloudspace else "localhost" # noqa: S104
- server_target = (
- f"https://{port}-{constants.LIGHTNING_CLOUDSPACE_HOST}"
- if in_cloudspace
- else f"http://localhost:{port}"
- )
-
- frontend.start_server(host=server_host, port=port)
- frontend.flow._layout["target"] = f"{server_target}/{frontend.flow.name}"
-
- _set_flow_context()
-
- if constants.ENABLE_ORCHESTRATOR:
- storage_orchestrator = StorageOrchestrator(
- self.app,
- self.app.request_queues,
- self.app.response_queues,
- self.app.copy_request_queues,
- self.app.copy_response_queues,
- )
- self.threads.append(storage_orchestrator)
- storage_orchestrator.setDaemon(True)
- storage_orchestrator.start()
-
- if self.start_server:
- self.app.should_publish_changes_to_api = True
- has_started_queue = self.backend.queues.get_has_server_started_queue()
-
- apis = []
- if is_overridden("configure_api", self.app.root):
- apis = self.app.root.configure_api()
- _validate_api(apis)
- _add_tags_to_api(apis, ["app_api"])
-
- if is_overridden("configure_commands", self.app.root):
- commands = _prepare_commands(self.app)
- apis += _commands_to_api(commands, info=self.app.info)
-
- kwargs = {
- "apis": apis,
- "host": self.host,
- "port": self.port,
- "api_response_queue": self.app.api_response_queue,
- "api_publish_state_queue": self.app.api_publish_state_queue,
- "api_delta_queue": self.app.api_delta_queue,
- "has_started_queue": has_started_queue,
- "spec": extract_metadata_from_app(self.app),
- "root_path": self.app.root_path,
- }
- server_proc = multiprocessing.Process(target=start_server, kwargs=kwargs)
- self.processes["server"] = server_proc
- server_proc.start()
- # requires to wait for the UI to be clicked on.
-
- # wait for server to be ready
- has_started_queue.get()
-
- if all([
- open_ui,
- "PYTEST_CURRENT_TEST" not in os.environ,
- not _is_headless(self.app),
- constants.LIGHTNING_CLOUDSPACE_HOST is None,
- ]):
- click.launch(self._get_app_url())
-
- # Connect the runtime to the application.
- self.app.connect(self)
-
- # Once the bootstrapping is done, running the rank 0
- # app with all the components inactive
- self.app._run()
- except KeyboardInterrupt:
- self.terminate()
- self._has_triggered_termination = True
- raise
- finally:
- if not self._has_triggered_termination:
- self.terminate()
-
- def terminate(self):
- if constants.APP_SERVER_IN_CLOUD:
- # Close all the ports open for the App within the App.
- ports = [self.port] + getattr(self.backend, "ports", [])
- for port in ports:
- disable_port(port)
- super().terminate()
-
- @staticmethod
- def _get_app_url() -> str:
- return os.getenv("APP_SERVER_HOST", "http://127.0.0.1:7501/view")
diff --git a/src/lightning/app/runners/runtime.py b/src/lightning/app/runners/runtime.py
deleted file mode 100644
index be938436ff76a..0000000000000
--- a/src/lightning/app/runners/runtime.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import multiprocessing
-import os
-import sys
-from dataclasses import dataclass, field
-from pathlib import Path
-from threading import Thread
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
-
-from lightning.app.core import LightningApp, LightningFlow
-from lightning.app.core.constants import APP_SERVER_HOST, APP_SERVER_PORT
-from lightning.app.runners.backends import Backend, BackendType
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.enum import AppStage, CacheCallsKeys, WorkStageStatus, make_status
-from lightning.app.utilities.load_app import load_app_from_file
-from lightning.app.utilities.proxies import WorkRunner
-
-logger = Logger(__name__)
-
-if TYPE_CHECKING:
- import lightning.app
-
-
-def dispatch(
- entrypoint_file: Path,
- runtime_type: "lightning.app.runners.runtime_type.RuntimeType",
- start_server: bool = True,
- no_cache: bool = False,
- host: str = APP_SERVER_HOST,
- port: int = APP_SERVER_PORT,
- blocking: bool = True,
- open_ui: bool = True,
- name: str = "",
- env_vars: Optional[Dict[str, str]] = None,
- secrets: Optional[Dict[str, str]] = None,
- run_app_comment_commands: bool = False,
- enable_basic_auth: str = "",
-) -> Optional[Any]:
- """Bootstrap and dispatch the application to the target.
-
- Arguments:
- entrypoint_file: Filepath to the current script
- runtime_type: The runtime to be used for launching the app.
- start_server: Whether to run the app REST API.
- no_cache: Whether to use the dependency cache for the app.
- host: Server host address
- port: Server port
- blocking: Whether for the wait for the UI to start running.
- open_ui: Whether to open the UI in the browser.
- name: Name of app execution
- env_vars: Dict of env variables to be set on the app
- secrets: Dict of secrets to be passed as environment variables to the app
- run_app_comment_commands: whether to parse commands from the entrypoint file and execute them before app startup
- enable_basic_auth: whether to enable basic authentication for the app
- (use credentials in the format username:password as an argument)
-
- """
- from lightning.app.runners.runtime_type import RuntimeType
- from lightning.app.utilities.component import _set_flow_context
-
- _set_flow_context()
-
- runtime_type = RuntimeType(runtime_type)
- runtime_cls: Type[Runtime] = runtime_type.get_runtime()
- app = runtime_cls.load_app_from_file(str(entrypoint_file))
-
- env_vars = {} if env_vars is None else env_vars
- secrets = {} if secrets is None else secrets
-
- if blocking:
- app.stage = AppStage.BLOCKING
-
- runtime = runtime_cls(
- app=app,
- entrypoint=entrypoint_file,
- start_server=start_server,
- host=host,
- port=port,
- env_vars=env_vars,
- secrets=secrets,
- run_app_comment_commands=run_app_comment_commands,
- enable_basic_auth=enable_basic_auth,
- )
- # Used to indicate Lightning has been dispatched
- os.environ["LIGHTNING_DISPATCHED"] = "1"
- # a cloud dispatcher will return the result while local
- # dispatchers will be running the app in the main process
- return runtime.dispatch(open_ui=open_ui, name=name, no_cache=no_cache)
-
-
-@dataclass
-class Runtime:
- app: Optional[LightningApp] = None
- entrypoint: Optional[Path] = None
- start_server: bool = True
- host: str = APP_SERVER_HOST
- port: int = APP_SERVER_PORT
- processes: Dict[str, multiprocessing.Process] = field(default_factory=dict)
- threads: List[Thread] = field(default_factory=list)
- work_runners: Dict[str, WorkRunner] = field(default_factory=dict)
- done: bool = False
- backend: Optional[Union[str, Backend]] = "multiprocessing"
- env_vars: Dict[str, str] = field(default_factory=dict)
- secrets: Dict[str, str] = field(default_factory=dict)
- run_app_comment_commands: bool = False
- enable_basic_auth: str = ""
-
- def __post_init__(self):
- if isinstance(self.backend, str):
- self.backend = BackendType(self.backend).get_backend(self.entrypoint)
-
- if self.app is not None:
- LightningFlow._attach_backend(self.app.root, self.backend)
-
- def terminate(self) -> None:
- """This method is used to terminate all the objects (threads, processes, etc..) created by the app."""
- logger.info("Your Lightning App is being stopped. This won't take long.")
- self.done = False
- has_messaged = False
- while not self.done:
- try:
- if self.app.backend is not None:
- self.app.backend.stop_all_works(self.app.works)
-
- if self.app.api_publish_state_queue:
- for work in self.app.works:
- self._add_stopped_status_to_work(work)
-
- # Publish the updated state and wait for the frontend to update.
- self.app.api_publish_state_queue.put((self.app.state, self.app.status))
-
- for thread in self.threads + self.app.threads:
- thread.join(timeout=0)
-
- for frontend in self.app.frontends.values():
- frontend.stop_server()
-
- for proc in list(self.processes.values()) + list(self.app.processes.values()):
- if proc.is_alive():
- proc.kill()
-
- self.done = True
-
- except KeyboardInterrupt:
- if not has_messaged:
- logger.info("Your Lightning App is being stopped. This won't take long.")
- has_messaged = True
-
- if self.done:
- logger.info("Your Lightning App has been stopped successfully!")
-
- # Inform the application failed.
- if self.app.stage == AppStage.FAILED:
- sys.exit(1)
-
- def dispatch(self, *args: Any, **kwargs: Any):
- raise NotImplementedError
-
- def _add_stopped_status_to_work(self, work: "lightning.app.LightningWork") -> None:
- if work.status.stage == WorkStageStatus.STOPPED:
- return
-
- latest_call_hash = work._calls[CacheCallsKeys.LATEST_CALL_HASH]
- if latest_call_hash in work._calls:
- work._calls[latest_call_hash]["statuses"].append(make_status(WorkStageStatus.STOPPED))
-
- @classmethod
- def load_app_from_file(cls, filepath: str) -> "LightningApp":
- return load_app_from_file(filepath)
diff --git a/src/lightning/app/runners/runtime_type.py b/src/lightning/app/runners/runtime_type.py
deleted file mode 100644
index 26212385d82bd..0000000000000
--- a/src/lightning/app/runners/runtime_type.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from enum import Enum
-from typing import TYPE_CHECKING, Type
-
-from lightning.app.runners import CloudRuntime, MultiProcessRuntime
-
-if TYPE_CHECKING:
- from lightning.app.runners.runtime import Runtime
-
-
-class RuntimeType(Enum):
- MULTIPROCESS = "multiprocess"
- CLOUD = "cloud"
-
- def get_runtime(self) -> Type["Runtime"]:
- if self == RuntimeType.MULTIPROCESS:
- return MultiProcessRuntime
- if self == RuntimeType.CLOUD:
- return CloudRuntime
- raise ValueError("Unknown runtime type")
diff --git a/src/lightning/app/source_code/__init__.py b/src/lightning/app/source_code/__init__.py
deleted file mode 100644
index 8b4fffd27762e..0000000000000
--- a/src/lightning/app/source_code/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from lightning.app.source_code.local import LocalSourceCodeDir
-from lightning.app.source_code.uploader import FileUploader
-
-__all__ = [
- "LocalSourceCodeDir",
- "FileUploader",
-]
diff --git a/src/lightning/app/source_code/copytree.py b/src/lightning/app/source_code/copytree.py
deleted file mode 100644
index fa2716e94c5c7..0000000000000
--- a/src/lightning/app/source_code/copytree.py
+++ /dev/null
@@ -1,183 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import fnmatch
-import os
-from functools import partial
-from pathlib import Path
-from shutil import Error, copy2, copystat
-from typing import Callable, List, Optional, Set, Tuple, Union
-
-from lightning.app.core.constants import DOT_IGNORE_FILENAME
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-_IGNORE_FUNCTION = Callable[[Path, List[Path]], List[Path]]
-
-
-def _copytree(
- src: Union[Path, str],
- dst: Union[Path, str],
- ignore_functions: Optional[List[_IGNORE_FUNCTION]] = None,
- dirs_exist_ok=False,
- dry_run=False,
-) -> List[str]:
- """Vendor in from `shutil.copytree` to support ignoring files recursively based on `.lightningignore`, like `git`
- does with `.gitignore`. Also removed a few checks from the original copytree related to symlink checks. Differences
- between original and this function are.
-
- 1. It supports a list of ignore function instead of a single one in the
- original. We can use this for filtering out files based on nested
- .lightningignore files
- 2. It supports a dry run. When enabled, this function will not copy anything but just recursively
- find the source files which are not-ignored and return them. It is useful while calculating
- the hash or checking the size of files
- 3. This function returns a list of copied files unlike the original which was returning the
- destination directory
-
- Recursively copy a directory tree and return the destination directory.
-
- Parameters
- ----------
- src:
- Source directory path to copy from
- dst:
- Destination directory path to copy to
- ignore_functions:
- List of functions that will be used to filter out files
- and directories. This isn't required to be passed when calling from outside but will be
- autopopulated by the recursive calls in this function itself (Original copytree doesn't have this argument)
- dirs_exist_ok:
- If true, the destination directory will be created if it doesn't exist.
- dry_run:
- If true, this function will not copy anything (this is not present in the original copytree)
-
-
- If exception(s) occur, an Error is raised with a list of reasons.
-
- """
- files_copied = []
-
- if ignore_functions is None:
- ignore_functions = []
-
- _ignore_filename_spell_check(src)
- src = Path(src)
- dst = Path(dst)
- ignore_filepath = src / DOT_IGNORE_FILENAME
- if ignore_filepath.is_file():
- patterns = _read_lightningignore(ignore_filepath)
- ignore_fn = partial(_filter_ignored, src, patterns)
- # creating new list so we won't modify the original
- ignore_functions = [*ignore_functions, ignore_fn]
-
- if not dry_run:
- os.makedirs(dst, exist_ok=dirs_exist_ok)
-
- errors = []
-
- entries = list(src.iterdir())
- for fn in ignore_functions:
- # ignore function return only the entries that are not ignored
- entries = fn(src, entries)
-
- for srcentry in entries:
- dstpath = dst / srcentry.name
- try:
- if srcentry.is_dir():
- _files = _copytree(
- src=srcentry,
- dst=dstpath,
- ignore_functions=ignore_functions,
- dirs_exist_ok=dirs_exist_ok,
- dry_run=dry_run,
- )
- files_copied.extend(_files)
- else:
- files_copied.append(str(srcentry))
- if not dry_run:
- # Will raise a SpecialFileError for unsupported file types
- copy2(srcentry, dstpath)
- # catch the Error from the recursive copytree so that we can
- # continue with other files
- except Error as err:
- errors.extend(err.args[0])
- except OSError as why:
- errors.append((srcentry, dstpath, str(why)))
- try:
- if not dry_run:
- copystat(src, dst)
- except OSError as why:
- # Copying file access times may fail on Windows
- if getattr(why, "winerror", None) is None:
- errors.append((src, dst, str(why)))
- if errors:
- raise Error(errors)
- return files_copied
-
-
-def _filter_ignored(src: Path, patterns: Set[str], current_dir: Path, entries: List[Path]) -> List[Path]:
- relative_dir = current_dir.relative_to(src)
- names = [str(relative_dir / entry.name) for entry in entries]
- ignored_names = set()
- for pattern in patterns:
- ignored_names.update(fnmatch.filter(names, pattern))
- return [entry for entry in entries if str(relative_dir / entry.name) not in ignored_names]
-
-
-def _parse_lightningignore(lines: Tuple[str]) -> Set[str]:
- """Creates a set that removes empty lines and comments."""
- lines = [ln.strip() for ln in lines]
- # removes first `/` character for posix and `\\` for windows
- lines = [ln.lstrip("/").lstrip("\\") for ln in lines if ln != "" and not ln.startswith("#")]
- # convert to path and converting back to string to sanitize the pattern
- return {str(Path(ln)) for ln in lines}
-
-
-def _read_lightningignore(path: Path) -> Set[str]:
- """Reads ignore file and filter and empty lines. This will also remove patterns that start with a `/`. That's done
- to allow `glob` to simulate the behavior done by `git` where it interprets that as a root path.
-
- Parameters
- ----------
- path: Path
- Path to .lightningignore file or equivalent.
-
- Returns
- -------
- Set[str]
- Set of unique lines.
-
- """
- raw_lines = path.open().readlines()
- return _parse_lightningignore(raw_lines)
-
-
-def _ignore_filename_spell_check(src: Path):
- possible_spelling_mistakes = [
- ".gridignore",
- ".lightingignore",
- ".lightinginore",
- ".lightninginore",
- ".lightninignore",
- ".lightinignore",
- ]
- possible_spelling_mistakes.extend([p.lstrip(".") for p in possible_spelling_mistakes])
- for path in src.iterdir():
- if path.is_file() and path.name in possible_spelling_mistakes:
- logger.warn(
- f"Lightning uses `{DOT_IGNORE_FILENAME}` as the ignore file but found {path.name} at "
- f"{path.parent} instead. If this was a mistake, please rename the file."
- )
diff --git a/src/lightning/app/source_code/hashing.py b/src/lightning/app/source_code/hashing.py
deleted file mode 100644
index 8ca8f0cf72f51..0000000000000
--- a/src/lightning/app/source_code/hashing.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import hashlib
-from typing import List
-
-
-def _get_hash(files: List[str], algorithm: str = "blake2", chunk_num_blocks: int = 128) -> str:
- """Hashes the contents of a list of files.
-
- Parameters
- ----------
- files: List[Path]
- List of files.
- algorithm: str, default "blake2"
- Algorithm to hash contents. "blake2" is set by default because it
- is faster than "md5". [1]
- chunk_num_blocks: int, default 128
- Block size to user when iterating over file chunks.
-
- References
- ----------
- [1] https://crypto.stackexchange.com/questions/70101/blake2-vs-md5-for-checksum-file-integrity
- [2] https://stackoverflow.com/questions/1131220/get-md5-hash-of-big-files-in-python
-
- """
- # validate input
- if algorithm == "blake2":
- h = hashlib.blake2b(digest_size=20)
- elif algorithm == "md5":
- h = hashlib.md5()
- else:
- raise ValueError(f"Algorithm {algorithm} not supported")
-
- # calculate hash for all files
- for file in files:
- with open(file, "rb") as f:
- for chunk in iter(lambda: f.read(chunk_num_blocks * h.block_size), b""):
- h.update(chunk)
- return h.hexdigest()
diff --git a/src/lightning/app/source_code/local.py b/src/lightning/app/source_code/local.py
deleted file mode 100644
index 4062b5cab54d7..0000000000000
--- a/src/lightning/app/source_code/local.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import uuid
-from contextlib import contextmanager
-from pathlib import Path
-from shutil import copytree, rmtree
-from typing import List, Optional
-
-from lightning.app.core.constants import DOT_IGNORE_FILENAME, SYS_CUSTOMIZATIONS_SYNC_PATH
-from lightning.app.source_code.copytree import _IGNORE_FUNCTION, _copytree
-from lightning.app.source_code.tar import _tar_path
-from lightning.app.source_code.uploader import FileUploader
-
-
-class LocalSourceCodeDir:
- """Represents the source code directory and provide the utilities to manage it."""
-
- def __init__(
- self,
- path: Path,
- ignore_functions: Optional[List[_IGNORE_FUNCTION]] = None,
- default_ignore: bool = True,
- package_source: bool = True,
- sys_customizations_root: Optional[Path] = None,
- ) -> None:
- if "LIGHTNING_VSCODE_WORKSPACE" in os.environ:
- # Don't use home to store the tar ball. This won't play nice with symlinks
- self.cache_location: Path = Path("/tmp", ".lightning", "cache", "repositories")
- else:
- self.cache_location: Path = Path.home() / ".lightning" / "cache" / "repositories"
-
- self.path = path
- self.ignore_functions = ignore_functions
- self.package_source = package_source
- self.sys_customizations_root = sys_customizations_root
-
- # cache version
- self._version: Optional[str] = None
- self._non_ignored_files: Optional[List[str]] = None
-
- # create global cache location if it doesn't exist
- if not self.cache_location.exists():
- self.cache_location.mkdir(parents=True, exist_ok=True)
-
- # Create a default dotignore if requested and it doesn't exist
- if default_ignore and not (path / DOT_IGNORE_FILENAME).is_file():
- with open(path / DOT_IGNORE_FILENAME, "w") as f:
- f.write("venv/\n")
- if (path / "bin" / "activate").is_file() or (path / "pyvenv.cfg").is_file():
- # the user is developing inside venv
- f.write("bin/\ninclude/\nlib/\npyvenv.cfg\n")
-
- # clean old cache entries
- self._prune_cache()
-
- @property
- def files(self) -> List[str]:
- """Returns a set of files that are not ignored by .lightningignore."""
- if self._non_ignored_files is None:
- if self.package_source:
- self._non_ignored_files = _copytree(self.path, "", ignore_functions=self.ignore_functions, dry_run=True)
- else:
- self._non_ignored_files = []
- return self._non_ignored_files
-
- @property
- def version(self):
- """Calculates the checksum of a local path."""
- # cache value to prevent doing this over again
- if self._version is not None:
- return self._version
-
- # create a random version ID and store it
- self._version = uuid.uuid4().hex
- return self._version
-
- @property
- def package_path(self):
- """Location to tarball in local cache."""
- filename = f"{self.version}.tar.gz"
- return self.cache_location / filename
-
- @contextmanager
- def packaging_session(self) -> Path:
- """Creates a local directory with source code that is used for creating a local source-code package."""
- session_path = self.cache_location / "packaging_sessions" / self.version
- try:
- rmtree(session_path, ignore_errors=True)
- if self.package_source:
- _copytree(self.path, session_path, ignore_functions=self.ignore_functions)
- if self.sys_customizations_root is not None:
- path_to_sync = Path(session_path, SYS_CUSTOMIZATIONS_SYNC_PATH)
- copytree(self.sys_customizations_root, path_to_sync, dirs_exist_ok=True)
- yield session_path
- finally:
- rmtree(session_path, ignore_errors=True)
-
- def _prune_cache(self) -> None:
- """Prunes cache; only keeps the 10 most recent items."""
- packages = sorted(self.cache_location.iterdir(), key=os.path.getmtime)
- for package in packages[10:]:
- if package.is_file():
- package.unlink()
-
- def package(self) -> Path:
- """Packages local path using tar."""
- if self.package_path.exists():
- return self.package_path
- # create a packaging session if not available
- with self.packaging_session() as session_path:
- _tar_path(source_path=session_path, target_file=str(self.package_path), compression=True)
- return self.package_path
-
- def upload(self, url: str) -> None:
- """Uploads package to URL, usually pre-signed UR.
-
- Notes
- -----
- Since we do not use multipart uploads here, we cannot upload any
- packaged repository files which have a size > 2GB.
-
- This limitation should be removed during the datastore upload redesign
-
- """
- if self.package_path.stat().st_size > 2e9:
- raise OSError(
- "cannot upload directory code whose total fize size is greater than 2GB (2e9 bytes)"
- ) from None
-
- uploader = FileUploader(
- presigned_url=url,
- source_file=str(self.package_path),
- name=self.package_path.name,
- total_size=self.package_path.stat().st_size,
- )
- uploader.upload()
diff --git a/src/lightning/app/source_code/tar.py b/src/lightning/app/source_code/tar.py
deleted file mode 100644
index 5b92201df2485..0000000000000
--- a/src/lightning/app/source_code/tar.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import math
-import os
-import subprocess
-import tarfile
-from dataclasses import dataclass
-from typing import Optional, Tuple
-
-import click
-
-MAX_SPLIT_COUNT = 999
-
-
-def _get_dir_size_and_count(source_dir: str, prefix: Optional[str] = None) -> Tuple[int, int]:
- """Get size and file count of a directory.
-
- Parameters
- ----------
- source_dir: str
- Directory path
-
- Returns
- -------
- Tuple[int, int]
- Size in megabytes and file count
-
- """
- size = 0
- count = 0
- for root, _, files in os.walk(source_dir, topdown=True):
- for f in files:
- if prefix and not f.startswith(prefix):
- continue
-
- full_path = os.path.join(root, f)
- size += os.path.getsize(full_path)
- count += 1
-
- return (size, count)
-
-
-@dataclass
-class _TarResults:
- """This class holds the results of running tar_path.
-
- Attributes
- ----------
- before_size: int
- The total size of the original directory files in bytes
- after_size: int
- The total size of the compressed and tarred split files in bytes
-
- """
-
- before_size: int
- after_size: int
-
-
-def _get_split_size(
- total_size: int, minimum_split_size: int = 1024 * 1000 * 20, max_split_count: int = MAX_SPLIT_COUNT
-) -> int:
- """Calculate the split size we should use to split the multipart upload of an object to a bucket. We are limited
- to 1000 max parts as the way we are using ListMultipartUploads. More info https://github.com/gridai/grid/pull/5267
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html#mpu-process
- https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html
- https://github.com/psf/requests/issues/2717#issuecomment-724725392 Python or requests has a limit of 2**31 bytes
- for a single file upload.
-
- Parameters
- ----------
- minimum_split_size: int
- The minimum split size to use
- max_split_count: int
- The maximum split count
- total_size: int
- Total size of the file to split
-
- Returns
- -------
- int
- Split size
-
- """
- max_size = max_split_count * (1 << 31) # max size per part limited by Requests or urllib as shown in ref above
- if total_size > max_size:
- raise click.ClickException(
- f"The size of the datastore to be uploaded is bigger than our {max_size / (1 << 40):.2f} TBytes limit"
- )
-
- split_size = minimum_split_size
- split_count = math.ceil(total_size / split_size)
- if split_count > max_split_count:
- # Adjust the split size based on max split count
- split_size = math.ceil(total_size / max_split_count)
-
- return split_size
-
-
-def _tar_path(source_path: str, target_file: str, compression: bool = False) -> _TarResults:
- """Create tar from directory using `tar`
-
- Parameters
- ----------
- source_path: str
- Source directory or file
- target_file
- Target tar file
- compression: bool, default False
- Enable compression, which is disabled by default.
-
- Returns
- -------
- TarResults
- Results that holds file counts and sizes
-
- """
- if os.path.isdir(source_path):
- before_size, _ = _get_dir_size_and_count(source_path)
- else:
- before_size = os.path.getsize(source_path)
-
- try:
- _tar_path_subprocess(source_path, target_file, compression)
- except subprocess.CalledProcessError:
- _tar_path_python(source_path, target_file, compression)
-
- after_size = os.stat(target_file).st_size
- return _TarResults(before_size=before_size, after_size=after_size)
-
-
-def _tar_path_python(source_path: str, target_file: str, compression: bool = False) -> None:
- """Create tar from directory using `python`
-
- Parameters
- ----------
- source_path: str
- Source directory or file
- target_file
- Target tar file
- compression: bool, default False
- Enable compression, which is disabled by default.
-
- """
- file_mode = "w:gz" if compression else "w:"
-
- with tarfile.open(target_file, file_mode) as tar:
- if os.path.isdir(source_path):
- tar.add(str(source_path), arcname=".")
- elif os.path.isfile(source_path):
- file_info = tarfile.TarInfo(os.path.basename(str(source_path)))
- with open(source_path) as fo:
- tar.addfile(file_info, fo)
-
-
-def _tar_path_subprocess(source_path: str, target_file: str, compression: bool = False) -> None:
- """Create tar from directory using `tar`
-
- Parameters
- ----------
- source_path: str
- Source directory or file
- target_file
- Target tar file
- compression: bool, default False
- Enable compression, which is disabled by default.
-
- """
- # Only add compression when users explicitly request it.
- # We do this because it takes too long to compress
- # large datastores.
- tar_flags = "-cvf"
- if compression:
- tar_flags = "-zcvf"
- if os.path.isdir(source_path):
- command = f"tar -C {source_path} {tar_flags} {target_file} ./"
- else:
- abs_path = os.path.abspath(source_path)
- parent_dir = os.path.dirname(abs_path)
- base_name = os.path.basename(abs_path)
- command = f"tar -C {parent_dir} {tar_flags} {target_file} {base_name}"
-
- subprocess.check_call(
- command,
- stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL,
- shell=True,
- env={"GZIP": "-9", "COPYFILE_DISABLE": "1"},
- )
diff --git a/src/lightning/app/source_code/uploader.py b/src/lightning/app/source_code/uploader.py
deleted file mode 100644
index 82336c7b0b96f..0000000000000
--- a/src/lightning/app/source_code/uploader.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import time
-
-import requests
-from requests.adapters import HTTPAdapter
-from rich.progress import BarColumn, Progress, TextColumn
-from urllib3.util.retry import Retry
-
-
-class FileUploader:
- """This class uploads a source file with presigned url to S3.
-
- Attributes
- ----------
- source_file: str
- Source file to upload
- presigned_url: str
- Presigned urls dictionary, with key as part number and values as urls
- retries: int
- Amount of retries when requests encounter an error
- total_size: int
- Size of all files to upload
- name: str
- Name of this upload to display progress
-
- """
-
- workers: int = 8
- retries: int = 10000
- disconnect_retry_wait_seconds: int = 5
- progress = Progress(
- TextColumn("[bold blue]{task.description}", justify="left"),
- BarColumn(bar_width=None),
- "[self.progress.percentage]{task.percentage:>3.1f}%",
- )
-
- def __init__(self, presigned_url: str, source_file: str, total_size: int, name: str, use_progress: bool = True):
- self.presigned_url = presigned_url
- self.source_file = source_file
- self.total_size = total_size
- self.name = name
- self.use_progress = use_progress
- self.task_id = None
-
- def upload_data(self, url: str, data: bytes, retries: int, disconnect_retry_wait_seconds: int) -> str:
- """Send data to url.
-
- Parameters
- ----------
- url: str
- url string to send data to
- data: bytes
- Bytes of data to send to url
- retries: int
- Amount of retries
- disconnect_retry_wait_seconds: int
- Amount of seconds between disconnect retry
-
- Returns
- -------
- str
- ETag from response
-
- """
- disconnect_retries = retries
- while disconnect_retries > 0:
- try:
- retries = Retry(total=10)
- with requests.Session() as s:
- s.mount("https://", HTTPAdapter(max_retries=retries))
- return self._upload_data(s, url, data)
- except BrokenPipeError:
- time.sleep(disconnect_retry_wait_seconds)
- disconnect_retries -= 1
-
- raise ValueError("Unable to upload file after multiple attempts")
-
- def _upload_data(self, s: requests.Session, url: str, data: bytes):
- resp = s.put(url, data=data)
- if "ETag" not in resp.headers:
- raise ValueError(f"Unexpected response from {url}, response: {resp.content}")
- return resp.headers["ETag"]
-
- def upload(self) -> None:
- """Upload files from source dir into target path in S3."""
- no_task = self.task_id is None
- if self.use_progress and no_task:
- self.task_id = self.progress.add_task("upload", filename=self.name, total=self.total_size)
- self.progress.start()
- try:
- with open(self.source_file, "rb") as f:
- data = f.read()
- self.upload_data(self.presigned_url, data, self.retries, self.disconnect_retry_wait_seconds)
- if self.use_progress:
- self.progress.update(self.task_id, advance=len(data))
- finally:
- if self.use_progress and no_task:
- self.progress.stop()
diff --git a/src/lightning/app/storage/__init__.py b/src/lightning/app/storage/__init__.py
deleted file mode 100644
index 8ed541dd2de7e..0000000000000
--- a/src/lightning/app/storage/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from lightning.app.storage.drive import Drive # noqa: F401
-from lightning.app.storage.filesystem import FileSystem # noqa: F401
-from lightning.app.storage.mount import Mount # noqa: F401
-from lightning.app.storage.orchestrator import StorageOrchestrator # noqa: F401
-from lightning.app.storage.path import Path # noqa: F401
-from lightning.app.storage.payload import Payload # noqa: F401
diff --git a/src/lightning/app/storage/copier.py b/src/lightning/app/storage/copier.py
deleted file mode 100644
index f4bf799e2f395..0000000000000
--- a/src/lightning/app/storage/copier.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import concurrent.futures
-import pathlib
-import threading
-from threading import Thread
-from time import time
-from typing import TYPE_CHECKING, Optional, Union
-
-from fsspec import AbstractFileSystem
-from fsspec.implementations.local import LocalFileSystem
-
-from lightning.app.core.queues import BaseQueue
-from lightning.app.storage.path import _filesystem
-from lightning.app.storage.requests import _ExistsRequest, _GetRequest
-from lightning.app.utilities.app_helpers import Logger
-
-_PathRequest = Union[_GetRequest, _ExistsRequest]
-
-_logger = Logger(__name__)
-
-num_workers = 8
-if TYPE_CHECKING:
- import lightning.app
-
-
-class _Copier(Thread):
- """The Copier is a thread running alongside a LightningWork.
-
- It maintains two queues that connect to the central
- :class:`~lightning.app.storage.orchestrator.StorageOrchestrator`,
- the request queue and the response queue. The Copier waits for a request to be pushed to the request queue,
- processes it and sends back the request through the response queue. In the current implementation, the Copier
- simply copies the requested file from the local filesystem to a shared directory (determined by
- :func:`~lightning.app.storage.path.shared_storage_path`). Any errors raised during the copy will be added to the
- response and get re-raised within the corresponding LightningWork.
-
- Args:
- copy_request_queue: A queue connecting the central StorageOrchestrator with the Copier. The orchestrator
- will send requests to this queue.
- copy_response_queue: A queue connecting the central StorageOrchestrator with the Copier. The Copier
- will send a response to this queue whenever a requested copy has finished.
-
- """
-
- def __init__(
- self, work: "lightning.app.LightningWork", copy_request_queue: "BaseQueue", copy_response_queue: "BaseQueue"
- ) -> None:
- super().__init__(daemon=True)
- self._work = work
- self.copy_request_queue = copy_request_queue
- self.copy_response_queue = copy_response_queue
- self._exit_event = threading.Event()
- self._sleep_time = 0.1
-
- def run(self) -> None:
- while not self._exit_event.is_set():
- self._exit_event.wait(self._sleep_time)
- self.run_once()
-
- def join(self, timeout: Optional[float] = None) -> None:
- self._exit_event.set()
- super().join(timeout)
-
- def run_once(self):
- request: _PathRequest = self.copy_request_queue.get() # blocks until we get a request
-
- t0 = time()
-
- obj: Optional[lightning.app.storage.path.Path] = _find_matching_path(self._work, request)
- if obj is None:
- # If it's not a path, it must be a payload
- obj: lightning.app.storage.Payload = getattr(self._work, request.name)
-
- if isinstance(request, _ExistsRequest):
- response = obj._handle_exists_request(self._work, request)
- elif isinstance(request, _GetRequest):
- response = obj._handle_get_request(self._work, request)
- else:
- raise TypeError(
- f"The file copy request had an invalid type. Expected PathGetRequest or PathExistsRequest, got:"
- f" {type(request)}"
- )
-
- response.timedelta = time() - t0
- self.copy_response_queue.put(response)
-
-
-def _find_matching_path(work, request: _GetRequest) -> Optional["lightning.app.storage.path.Path"]:
- for name in work._paths:
- candidate: lightning.app.storage.path.Path = getattr(work, name)
- if candidate.hash == request.hash:
- return candidate
- return None
-
-
-def _copy_files(
- source_path: pathlib.Path,
- destination_path: pathlib.Path,
- fs: Optional[AbstractFileSystem] = None,
-) -> None:
- """Copy files from one path to another.
-
- The source path must either be an existing file or folder. If the source is a folder, the destination path is
- interpreted as a folder as well. If the source is a file, the destination path is interpreted as a file too.
-
- Files in a folder are copied recursively and efficiently using multiple threads.
-
- """
- if fs is None:
- fs = _filesystem()
-
- def _copy(from_path: pathlib.Path, to_path: pathlib.Path) -> Optional[Exception]:
- _logger.debug(f"Copying {str(from_path)} -> {str(to_path)}")
-
- try:
- # NOTE: S3 does not have a concept of directories, so we do not need to create one.
- if isinstance(fs, LocalFileSystem):
- fs.makedirs(str(to_path.parent), exist_ok=True)
-
- fs.put(str(from_path), str(to_path), recursive=False)
- except Exception as ex:
- # Return the exception so that it can be handled in the main thread
- return ex
-
- # NOTE: Cannot use `S3FileSystem.put(recursive=True)` because it tries to access parent directories
- # which it does not have access to.
- if source_path.is_dir():
- src = [file for file in source_path.rglob("*") if file.is_file()]
- dst = [destination_path / file.relative_to(source_path) for file in src]
-
- with concurrent.futures.ThreadPoolExecutor(num_workers) as executor:
- results = executor.map(_copy, src, dst)
-
- # Raise the first exception found
- exception = next((e for e in results if isinstance(e, Exception)), None)
- if exception:
- raise exception
- else:
- if isinstance(fs, LocalFileSystem):
- fs.makedirs(str(destination_path.parent), exist_ok=True)
-
- fs.put(str(source_path), str(destination_path))
diff --git a/src/lightning/app/storage/drive.py b/src/lightning/app/storage/drive.py
deleted file mode 100644
index b3f5f8f56726c..0000000000000
--- a/src/lightning/app/storage/drive.py
+++ /dev/null
@@ -1,341 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import pathlib
-import shutil
-import sys
-from copy import deepcopy
-from time import sleep, time
-from typing import Dict, List, Optional, Union
-
-from lightning.app.storage.path import LocalFileSystem, _filesystem, _shared_storage_path
-from lightning.app.utilities.component import _is_flow_context
-
-
-class Drive:
- __IDENTIFIER__ = "__drive__"
- __PROTOCOLS__ = ["lit://"]
-
- def __init__(
- self,
- id: str,
- allow_duplicates: bool = False,
- component_name: Optional[str] = None,
- root_folder: Optional[str] = None,
- ):
- """The Drive object provides a shared space to write and read files from.
-
- When the drive object is passed from one component to another, a copy is made and ownership
- is transferred to the new component.
-
- Arguments:
- id: Unique identifier for this Drive.
- allow_duplicates: Whether to enable files duplication between components.
- component_name: The component name which owns this drive.
- When not provided, it is automatically inferred by Lightning.
- root_folder: This is the folder from where the Drive perceives the data (e.g this acts as a mount dir).
-
- """
- if id.startswith("s3://"):
- raise ValueError(
- "Using S3 buckets in a Drive is no longer supported. Please pass an S3 `Mount` to "
- "a Work's CloudCompute config in order to mount an s3 bucket as a filesystem in a work.\n"
- f"`CloudCompute(mount=Mount({id}), ...)`"
- )
-
- self.id = None
- self.protocol = None
- for protocol in self.__PROTOCOLS__:
- if id.startswith(protocol):
- self.protocol = protocol
- self.id = id.replace(protocol, "")
- break
- else: # N.B. for-else loop
- raise ValueError(
- f"Unknown protocol for the drive 'id' argument '{id}`. The 'id' string "
- f"must start with one of the following prefixes {self.__PROTOCOLS__}"
- )
-
- if not self.id:
- raise Exception(f"The Drive id needs to start with one of the following protocols: {self.__PROTOCOLS__}")
-
- if "/" in self.id:
- raise Exception(f"The id should be unique to identify your drive. Found `{self.id}`.")
-
- self.root_folder = pathlib.Path(root_folder).resolve() if root_folder else pathlib.Path(os.getcwd())
- if self.protocol != "s3://" and not os.path.isdir(self.root_folder):
- raise Exception(f"The provided root_folder isn't a directory: {root_folder}")
- self.component_name = component_name
- self.allow_duplicates = allow_duplicates
- self.fs = _filesystem()
-
- @property
- def root(self) -> pathlib.Path:
- root_path = self.drive_root / self.component_name
- if isinstance(self.fs, LocalFileSystem):
- self.fs.makedirs(root_path, exist_ok=True)
- return root_path
-
- @property
- def drive_root(self) -> pathlib.Path:
- return _shared_storage_path() / "artifacts" / "drive" / self.id
-
- def put(self, path: str) -> None:
- """This method enables to put a file to the Drive in a blocking fashion.
-
- Arguments:
- path: The relative path to your files to be added to the Drive.
-
- """
- if not self.component_name:
- raise Exception("The component name needs to be known to put a path to the Drive.")
- if _is_flow_context():
- raise Exception("The flow isn't allowed to put files into a Drive.")
-
- self._validate_path(path)
-
- if not self.allow_duplicates:
- self._check_for_allow_duplicates(path)
-
- from lightning.app.storage.copier import _copy_files
-
- src = pathlib.Path(os.path.join(self.root_folder, path)).resolve()
- dst = self._to_shared_path(path, component_name=self.component_name)
-
- _copy_files(src, dst)
-
- def list(self, path: Optional[str] = ".", component_name: Optional[str] = None) -> List[str]:
- """This method enables to list files under the provided path from the Drive in a blocking fashion.
-
- Arguments:
- path: The relative path you want to list files from the Drive.
- component_name: By default, the Drive lists files across all components.
- If you provide a component name, the listing is specific to this component.
-
- """
- if _is_flow_context():
- raise Exception("The flow isn't allowed to list files from a Drive.")
-
- if component_name:
- paths = [
- self._to_shared_path(
- path,
- component_name=component_name,
- )
- ]
- else:
- paths = [
- self._to_shared_path(
- path,
- component_name=component_name,
- )
- for component_name in self._collect_component_names()
- ]
-
- files = []
- sep = "\\" if sys.platform == "win32" else "/"
- prefix_len = len(str(self.root).split(sep))
- for p in paths:
- if self.fs.exists(p):
- for f in self.fs.ls(p):
- files.append(str(pathlib.Path(*pathlib.Path(f).parts[prefix_len:])))
- return files
-
- def get(
- self,
- path: str,
- component_name: Optional[str] = None,
- timeout: Optional[float] = None,
- overwrite: bool = False,
- ) -> None:
- """This method enables to get files under the provided path from the Drive in a blocking fashion.
-
- Arguments:
- path: The relative path you want to list files from the Drive.
- component_name: By default, the Drive get the matching files across all components.
- If you provide a component name, the matching is specific to this component.
- timeout: Whether to wait for the files to be available if not created yet.
- overwrite: Whether to override the provided path if it exists.
-
- """
- if _is_flow_context():
- raise Exception("The flow isn't allowed to get files from a Drive.")
-
- if component_name:
- shared_path = self._to_shared_path(
- path,
- component_name=component_name,
- )
- if timeout:
- start_time = time()
- while not self.fs.exists(shared_path):
- sleep(1)
- if (time() - start_time) > timeout:
- raise Exception(f"The following {path} wasn't found in {timeout} seconds")
- break
-
- self._get(
- self.fs,
- shared_path,
- pathlib.Path(os.path.join(self.root_folder, path)).resolve(),
- overwrite=overwrite,
- )
- else:
- if timeout:
- start_time = time()
- while True:
- if (time() - start_time) > timeout:
- raise Exception(f"The following {path} wasn't found in {timeout} seconds.")
- match = self._find_match(path)
- if match is None:
- sleep(1)
- continue
- break
- else:
- match = self._find_match(path)
- if not match:
- raise Exception(f"We didn't find any match for the associated {path}.")
-
- self._get(self.fs, match, pathlib.Path(os.path.join(self.root_folder, path)).resolve(), overwrite=overwrite)
-
- def delete(self, path: str) -> None:
- """This method enables to delete files under the provided path from the Drive in a blocking fashion. Only the
- component which added a file can delete them.
-
- Arguments:
- path: The relative path you want to delete files from the Drive.
-
- """
- if not self.component_name:
- raise Exception("The component name needs to be known to delete a path to the Drive.")
-
- shared_path = self._to_shared_path(
- path,
- component_name=self.component_name,
- )
- if self.fs.exists(str(shared_path)):
- self.fs.rm(str(shared_path))
- else:
- raise Exception(f"The file {path} doesn't exists in the component_name space {self.component_name}.")
-
- def to_dict(self):
- return {
- "type": self.__IDENTIFIER__,
- "id": self.id,
- "protocol": self.protocol,
- "allow_duplicates": self.allow_duplicates,
- "component_name": self.component_name,
- "root_folder": str(self.root_folder),
- }
-
- @classmethod
- def from_dict(cls, dict: Dict) -> "Drive":
- assert dict["type"] == cls.__IDENTIFIER__
- drive = cls(
- dict["protocol"] + dict["id"],
- allow_duplicates=dict["allow_duplicates"],
- root_folder=dict["root_folder"],
- )
- drive.component_name = dict["component_name"]
- return drive
-
- def __deepcopy__(self, memo):
- cls = self.__class__
- result = cls.__new__(cls)
- memo[id(self)] = result
- for k, v in self.__dict__.items():
- setattr(result, k, deepcopy(v, memo))
- return result
-
- def _collect_component_names(self) -> List[str]:
- sep = "/"
- if self.fs.exists(self.drive_root):
- # Invalidate cache before running ls in case new directories have been added
- # TODO: Re-evaluate this - may lead to performance issues
- self.fs.invalidate_cache()
- return [str(p.split(sep)[-1]) for p in self.fs.ls(self.drive_root)]
- return []
-
- def _to_shared_path(self, path: str, component_name: Optional[str] = None) -> pathlib.Path:
- shared_path = self.drive_root
- if component_name:
- shared_path /= component_name
- shared_path /= path
- return shared_path
-
- def _get(self, fs, src: pathlib.Path, dst: pathlib.Path, overwrite: bool):
- if fs.isdir(src):
- if isinstance(fs, LocalFileSystem):
- dst = dst.resolve()
- if fs.exists(dst):
- if overwrite:
- fs.rm(str(dst), recursive=True)
- else:
- raise FileExistsError(f"The file {dst} was found. Add get(..., overwrite=True) to replace it.")
-
- shutil.copytree(src, dst)
- else:
- glob = f"{str(src)}/**"
- fs.get(glob, str(dst.absolute()), recursive=False)
- else:
- fs.get(str(src), str(dst.absolute()), recursive=False)
-
- def _find_match(self, path: str) -> Optional[pathlib.Path]:
- matches = []
- for component_name in self._collect_component_names():
- possible_path = self._to_shared_path(path, component_name=component_name)
- if self.fs.exists(possible_path):
- matches.append(possible_path)
-
- if not matches:
- return None
-
- if len(matches) > 1:
- sep = "\\" if sys.platform == "win32" else "/"
- prefix_len = len(str(self.root).split(sep))
- matches = [str(pathlib.Path(*pathlib.Path(p).parts[prefix_len:])) for p in matches]
- raise Exception(f"We found several matching files created by multiples components: {matches}.")
-
- return matches[0]
-
- def _check_for_allow_duplicates(self, path):
- possible_paths = [
- self._to_shared_path(
- path,
- component_name=component_name,
- )
- for component_name in self._collect_component_names()
- if component_name != self.component_name
- ]
- matches = [self.fs.exists(p) for p in possible_paths]
-
- if sum(matches):
- raise Exception(f"The file {path} can't be added as already found in the Drive.")
-
- def _validate_path(self, path: str) -> None:
- if not os.path.exists(os.path.join(self.root_folder, path)):
- raise FileExistsError(f"The provided path {path} doesn't exists")
-
- def __str__(self) -> str:
- assert self.id
- return self.id
-
-
-def _maybe_create_drive(component_name: str, state: Dict) -> Union[Dict, Drive]:
- if state.get("type") == Drive.__IDENTIFIER__:
- drive = Drive.from_dict(state)
- drive.component_name = component_name
- return drive
- return state
diff --git a/src/lightning/app/storage/filesystem.py b/src/lightning/app/storage/filesystem.py
deleted file mode 100644
index 943a6a750bd2b..0000000000000
--- a/src/lightning/app/storage/filesystem.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import os
-import shutil
-from pathlib import Path
-from typing import Callable, List
-
-from fsspec.implementations.local import LocalFileSystem
-
-from lightning.app.storage.copier import _copy_files
-from lightning.app.storage.path import _filesystem, _shared_storage_path
-
-
-def _get_files(fs, src: Path, dst: Path, overwrite: bool = True):
- dst = dst.resolve()
- if fs.isdir(src):
- if isinstance(fs, LocalFileSystem):
- dst = dst.resolve()
- if fs.exists(dst):
- if overwrite:
- fs.rm(str(dst), recursive=True)
- else:
- raise FileExistsError(f"The file {dst} was found. Add get(..., overwrite=True) to replace it.")
-
- shutil.copytree(src, dst)
- else:
- glob = f"{str(src)}/**"
- fs.get(glob, str(dst), recursive=False)
- else:
- fs.get(str(src), str(dst), recursive=False)
-
-
-class FileSystem:
- """This filesystem enables to easily move files from and to the shared storage."""
-
- def __init__(self) -> None:
- self._fs = _filesystem()
- self._root = str(_shared_storage_path())
-
- def put(self, src_path: str, dst_path: str, put_fn: Callable = _copy_files) -> None:
- """This method enables to put a file to the shared storage in a blocking fashion.
-
- Arguments:
- src_path: The path to your files locally
- dst_path: The path to your files transfered in the shared storage.
- put_fn: The method to use to put files in the shared storage.
-
- """
- if not os.path.exists(Path(src_path).resolve()):
- raise FileExistsError(f"The provided path {src_path} doesn't exist")
-
- if not dst_path.startswith("/"):
- raise Exception(f"The provided destination {dst_path} needs to start with `/`.")
-
- if dst_path == "/":
- dst_path = os.path.join(self._root, os.path.basename(src_path))
- else:
- dst_path = os.path.join(self._root, dst_path[1:])
-
- src = Path(src_path).resolve()
- dst = Path(dst_path).resolve()
-
- return put_fn(src, dst, fs=self._fs)
-
- def get(self, src_path: str, dst_path: str, overwrite: bool = True, get_fn: Callable = _get_files) -> None:
- """This method enables to get files from the shared storage in a blocking fashion.
-
- Arguments:
- src_path: The path to your files in the shared storage
- dst_path: The path to your files transfered locally
- get_fn: The method to use to put files in the shared storage.
-
- """
- if not src_path.startswith("/"):
- raise Exception(f"The provided destination {src_path} needs to start with `/`.")
-
- src = Path(os.path.join(self._root, src_path[1:])).resolve()
- dst = Path(dst_path).resolve()
-
- return get_fn(fs=self._fs, src=src, dst=dst, overwrite=overwrite)
-
- def listdir(self, path: str) -> List[str]:
- """This method enables to list files from the shared storage in a blocking fashion.
-
- Arguments:
- path: The path to files to list.
-
- """
- if not path.startswith("/"):
- raise Exception(f"The provided destination {path} needs to start with `/`.")
-
- shared_path = Path(os.path.join(self._root, path[1:])).resolve()
-
- if not self._fs.exists(shared_path):
- raise RuntimeError(f"The provided path {shared_path} doesn't exist.")
-
- # Invalidate cache before running ls in case new directories have been added
- # TODO: Re-evaluate this - may lead to performance issues
- self._fs.invalidate_cache()
-
- paths = self._fs.ls(shared_path)
- if not paths:
- return paths
-
- return sorted([path.replace(self._root + os.sep, "") for path in paths if not path.endswith("info.txt")])
-
- def walk(self, path: str) -> List[str]:
- """This method enables to list files from the shared storage in a blocking fashion.
-
- Arguments:
- path: The path to files to list.
-
- """
- if not path.startswith("/"):
- raise Exception(f"The provided destination {path} needs to start with `/`.")
-
- shared_path = Path(os.path.join(self._root, path[1:])).resolve()
-
- if not self._fs.exists(shared_path):
- raise RuntimeError(f"The provided path {shared_path} doesn't exist.")
-
- # Invalidate cache before running ls in case new directories have been added
- # TODO: Re-evaluate this - may lead to performance issues
- self._fs.invalidate_cache()
-
- paths = self._fs.ls(shared_path)
- if not paths:
- return paths
-
- out = []
-
- for shared_path in paths:
- path = str(shared_path).replace(self._root, "")
- if self._fs.isdir(shared_path):
- out.extend(self.walk(path))
- else:
- if path.endswith("info.txt"):
- continue
- out.append(path[1:])
- return sorted(out)
-
- def rm(self, path) -> None:
- if not path.startswith("/"):
- raise Exception(f"The provided destination {path} needs to start with `/`.")
-
- delete_path = Path(os.path.join(self._root, path[1:])).resolve()
-
- if self._fs.exists(str(delete_path)):
- if self._fs.isdir(str(delete_path)):
- self._fs.rmdir(str(delete_path))
- else:
- self._fs.rm(str(delete_path))
- else:
- raise Exception(f"The file path {path} doesn't exist.")
-
- def isfile(self, path: str) -> bool:
- if not path.startswith("/"):
- raise Exception(f"The provided destination {path} needs to start with `/`.")
-
- path = Path(os.path.join(self._root, path[1:])).resolve()
- return self._fs.isfile(path)
-
- def isdir(self, path: str) -> bool:
- if not path.startswith("/"):
- raise Exception(f"The provided destination {path} needs to start with `/`.")
-
- path = Path(os.path.join(self._root, path[1:])).resolve()
- return self._fs.isdir(path)
diff --git a/src/lightning/app/storage/mount.py b/src/lightning/app/storage/mount.py
deleted file mode 100644
index 8142b4574a8b0..0000000000000
--- a/src/lightning/app/storage/mount.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from dataclasses import dataclass
-from pathlib import Path
-from typing import List
-
-__MOUNT_IDENTIFIER__: str = "__mount__"
-__MOUNT_PROTOCOLS__: List[str] = ["s3://"]
-
-
-@dataclass
-class Mount:
- """Allows you to mount the contents of an AWS S3 bucket on disk when running an app on the cloud.
-
- Arguments:
- source: The location which contains the external data which should be mounted in the
- running work. At the moment, only AWS S3 mounts are supported. This must be a full
- `s3` style identifier pointing to a bucket and (optionally) prefix to mount. For
- example: `s3://foo/bar/`.
-
- mount_path: An absolute directory path in the work where external data source should
- be mounted as a filesystem. This path should not already exist in your codebase.
- If not included, then the root_dir will be set to `/data/`
-
- """
-
- source: str = ""
- mount_path: str = ""
-
- def __post_init__(self) -> None:
- for protocol in __MOUNT_PROTOCOLS__:
- if self.source.startswith(protocol):
- protocol = protocol
- break
- else: # N.B. for-else loop
- raise ValueError(
- f"Unknown protocol for the mount 'source' argument '{self.source}`. The 'source' "
- f"string must start with one of the following prefixes: {__MOUNT_PROTOCOLS__}"
- )
-
- if protocol == "s3://" and not self.source.endswith("/"):
- raise ValueError(
- "S3 mounts must end in a trailing slash (`/`) to indicate a folder is being mounted. "
- f"Received: '{self.source}'. Mounting a single file is not currently supported."
- )
-
- if self.mount_path == "":
- self.mount_path = f"/data/{Path(self.source).stem}"
-
- if not os.path.isabs(self.mount_path):
- raise ValueError(
- f"mount_path argument must be an absolute path to a "
- f"location; received relative path {self.mount_path}"
- )
-
- @property
- def protocol(self) -> str:
- """The backing storage protocol indicated by this drive source."""
- for protocol in __MOUNT_PROTOCOLS__:
- if self.source.startswith(protocol):
- return protocol
- return ""
diff --git a/src/lightning/app/storage/orchestrator.py b/src/lightning/app/storage/orchestrator.py
deleted file mode 100644
index adb7078ff3360..0000000000000
--- a/src/lightning/app/storage/orchestrator.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import threading
-import traceback
-from queue import Empty
-from threading import Thread
-from typing import TYPE_CHECKING, Dict, Optional, Union
-
-from lightning.app.core.queues import BaseQueue
-from lightning.app.storage.path import _filesystem, _path_to_work_artifact
-from lightning.app.storage.requests import _ExistsRequest, _ExistsResponse, _GetRequest, _GetResponse
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.enum import WorkStageStatus
-
-if TYPE_CHECKING:
- from lightning.app.core.app import LightningApp
-
-
-_PathRequest = Union[_GetRequest, _ExistsRequest]
-_PathResponse = Union[_ExistsResponse, _GetResponse]
-_logger = Logger(__name__)
-
-
-class StorageOrchestrator(Thread):
- """The StorageOrchestrator processes file transfer requests from Work that need file(s) from other Work.
-
- Args:
- app: A reference to the ``LightningApp`` which holds the copy request- and response queues for storage.
- request_queues: A dictionary with Queues connected to consumer Work. The Queue will contain transfer requests
- coming from a consumer Work.
- response_queues: A dictionary with Queues connected to consumer Work.
- The Queue will contain the confirmation responses to the consumer Work that files were transferred.
- copy_request_queues: A dictionary of Queues where each Queue connects to one Work. The orchestrator will
- put requests on this queue for the file-transfer thread to complete.
- copy_response_queues: A dictionary of Queues where each Queue connects to one Work. The queue is expected to
- contain the completion response from the file-transfer thread running in the Work process.
-
- """
-
- def __init__(
- self,
- app: "LightningApp",
- request_queues: Dict[str, BaseQueue],
- response_queues: Dict[str, BaseQueue],
- copy_request_queues: Dict[str, BaseQueue],
- copy_response_queues: Dict[str, BaseQueue],
- ) -> None:
- super().__init__(daemon=True)
- self.app = app
- self.request_queues = request_queues
- self.response_queues = response_queues
- self.copy_request_queues = copy_request_queues
- self.copy_response_queues = copy_response_queues
- self.waiting_for_response: Dict[str, str] = {}
- self._validate_queues()
- self._exit_event = threading.Event()
-
- # Note: Use different sleep time locally and in the cloud
- # to reduce queue calls.
- self._sleep_time = 0.1 if "LIGHTNING_APP_STATE_URL" not in os.environ else 2
- self._fs = None
-
- @property
- def fs(self):
- if self._fs is None:
- self._fs = _filesystem()
- return self._fs
-
- def _validate_queues(self):
- assert (
- self.request_queues.keys()
- == self.response_queues.keys()
- == self.copy_request_queues.keys()
- == self.copy_response_queues.keys()
- )
-
- def run(self) -> None:
- while not self._exit_event.is_set():
- for work_name in list(self.request_queues.keys()):
- try:
- self.run_once(work_name)
- except Exception:
- _logger.error(traceback.format_exc())
- self._exit_event.wait(self._sleep_time)
-
- def join(self, timeout: Optional[float] = None) -> None:
- self._exit_event.set()
- super().join(timeout)
-
- def run_once(self, work_name: str) -> None:
- if work_name not in self.waiting_for_response:
- # check if there is a new request from this work for a file transfer
- # there can only be one pending request per work
- request_queue = self.request_queues[work_name]
- try:
- request: _PathRequest = request_queue.get(timeout=0) # this should not block
- # This should not happen under normal conditions, but it has occurred.
- # For now we are tolerant with respect to requests being None in the queue
- # and just move on.
- if request is None:
- raise Empty
- except Empty:
- pass
- else:
- request.destination = work_name
- source_work = self.app.get_component_by_name(request.source)
- maybe_artifact_path = str(_path_to_work_artifact(request.path, source_work))
-
- if self.fs.exists(maybe_artifact_path):
- # First check if the shared filesystem has the requested file stored as an artifact
- # If so, we will let the destination Work access this file directly
- # NOTE: This is NOT the right thing to do, because the Work could still be running and producing
- # a newer version of the requested file, but we can't rely on the Work status to be accurate
- # (at the moment)
- if isinstance(request, _GetRequest):
- response = _GetResponse(
- source=request.source,
- name=request.name,
- path=maybe_artifact_path,
- hash=request.hash,
- size=self.fs.info(maybe_artifact_path)["size"],
- destination=request.destination,
- )
- if isinstance(request, _ExistsRequest):
- response = _ExistsResponse(
- source=request.source,
- path=maybe_artifact_path,
- name=request.name,
- hash=request.hash,
- destination=request.destination,
- exists=True,
- )
- response_queue = self.response_queues[response.destination]
- response_queue.put(response)
- elif source_work.status.stage not in (
- WorkStageStatus.NOT_STARTED,
- WorkStageStatus.STOPPED,
- WorkStageStatus.FAILED,
- ):
- _logger.debug(
- f"Request for File Transfer received from {work_name}: {request}. Sending request to"
- f" {request.source} to copy the file."
- )
- # The Work is running, and we can send a request to the copier for moving the file to the
- # shared storage
- self.copy_request_queues[request.source].put(request)
- # Store a destination to source mapping.
- self.waiting_for_response[work_name] = request.source
- else:
- if isinstance(request, _GetRequest):
- response = _GetResponse(
- source=request.source,
- path=request.path,
- name=request.name,
- hash=request.hash,
- size=0,
- destination=request.destination,
- )
- if isinstance(request, _ExistsRequest):
- response = _ExistsResponse(
- source=request.source,
- path=request.path,
- hash=request.hash,
- destination=request.destination,
- exists=False,
- name=request.name,
- )
- response.exception = FileNotFoundError(
- "The work is not running and the requested object is not available in the artifact store."
- )
- response_queue = self.response_queues[response.destination]
- response_queue.put(response)
-
- # Check the current work is within the sources.
- # It is possible to have multiple destination targeting
- # the same source concurrently.
- if work_name in self.waiting_for_response.values():
- # check if the current work has responses for file transfers to other works.
- copy_response_queue = self.copy_response_queues[work_name]
- try:
- # check if the share-point file manager has confirmed a copy request
- response: _PathResponse = copy_response_queue.get(timeout=0) # this should not block
- except Empty:
- pass
- else:
- _logger.debug(
- f"Received confirmation of a completed file copy request from {work_name}:{response}."
- f" Sending the confirmation back to {response.destination}."
- )
- destination = response.destination
- assert response.source == work_name
- response_queue = self.response_queues[destination]
- response_queue.put(response)
- # the request has been processed, allow new requests to come in for the destination work
- del self.waiting_for_response[destination]
diff --git a/src/lightning/app/storage/path.py b/src/lightning/app/storage/path.py
deleted file mode 100644
index 0d1297d998fb2..0000000000000
--- a/src/lightning/app/storage/path.py
+++ /dev/null
@@ -1,453 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import hashlib
-import os
-import pathlib
-import shutil
-import sys
-from time import sleep
-from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Union
-
-from fsspec import AbstractFileSystem
-from fsspec.implementations.local import LocalFileSystem
-
-from lightning.app.core.constants import REMOTE_STORAGE_WAIT
-from lightning.app.core.queues import BaseQueue
-from lightning.app.storage.requests import _ExistsRequest, _ExistsResponse, _GetRequest, _GetResponse
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.component import _is_flow_context
-from lightning.app.utilities.imports import _is_s3fs_available
-
-if _is_s3fs_available():
- from s3fs import S3FileSystem
-
-PathlibPath = type(pathlib.Path()) # PosixPath or a WindowsPath depending on the platform
-
-if TYPE_CHECKING:
- from lightning.app.core.work import LightningWork
-
-num_workers = 8
-
-_logger = Logger(__name__)
-
-
-class Path(PathlibPath):
- """A drop-in replacement for :class:`pathlib.Path` for all paths in Lightning.
-
- The Lightning Path works exactly the same as :class:`pathlib.Path` but it also remembers in which LightningWork
- it was created. If the Path gets passed to a different LightningWork, the file or folder can then be easily
- accessed no matter where it is located in the other Work's filesystem.
-
- Args:
- *args: Accepts the same arguments as in :class:`pathlib.Path`
- **kwargs: Accepts the same keyword arguments as in :class:`pathlib.Path`
-
- """
-
- @classmethod
- def _from_parts(cls, args: Any, **__unused) -> "Path":
- """This gets called from the super class in ``pathlib.Path.__new__``.
-
- The Lightning Path overrides this to validate the instantiation in the case parts are passed in individually. In
- such a case we need to validate that all parts have the same `origin` and if not, an error is raised.
-
- """
- if args and isinstance(args[0], str) and args[0].startswith("lit://"):
- parts = list(args)
- parts[0] = parts[0][len("lit://") :]
- args = (_storage_root_dir(), *parts)
-
- if (sys.version_info.major, sys.version_info.minor) < (3, 10):
- __unused.setdefault("init", True)
- new_path = super()._from_parts(args, **__unused)
- else:
- new_path = super()._from_parts(args)
-
- new_path._init_attributes() # we use this instead of defining a __init__() method
-
- paths_from_parts = [part for part in args if isinstance(part, Path)]
- if not paths_from_parts:
- return new_path
- top_path = paths_from_parts[0]
- origins = [part._origin for part in paths_from_parts]
- if not all(origins[0] == origin or origin is None for origin in origins):
- raise TypeError(
- "Tried to instantiate a Lightning Path from multiple other Paths that originate from different"
- " LightningWork."
- )
- new_path._copy_properties_from(top_path)
- return new_path
-
- def _init_attributes(self):
- self._name: Optional[str] = None
- # the origin is the work that created this Path and wants to expose file(s)
- self._origin: Optional[Union["LightningWork", str]] = None
- # the consumer is the Work that needs access to the file(s) from the consumer
- self._consumer: Optional[Union["LightningWork", str]] = None
- self._metadata = {}
- # request queue: used to transfer message to storage orchestrator
- self._request_queue: Optional[BaseQueue] = None
- # response queue: used to receive status message from storage orchestrator
- self._response_queue: Optional[BaseQueue] = None
-
- @property
- def origin_name(self) -> str:
- """The name of the LightningWork where this path was first created.
-
- Attaching a Path to a LightningWork will automatically make it the `origin`.
-
- """
- from lightning.app.core.work import LightningWork
-
- return self._origin.name if isinstance(self._origin, LightningWork) else self._origin
-
- @property
- def consumer_name(self) -> str:
- """The name of the LightningWork where this path is being accessed.
-
- By default, this is the same as the :attr:`origin_name`.
-
- """
- from lightning.app.core.work import LightningWork
-
- return self._consumer.name if isinstance(self._consumer, LightningWork) else self._consumer
-
- @property
- def hash(self) -> Optional[str]:
- """The hash of this Path uniquely identifies the file path and the associated origin Work.
-
- Returns ``None`` if the origin is not defined, i.e., this Path did not yet get attached to a LightningWork.
-
- """
- if self._origin is None:
- return None
- contents = f"{self.origin_name}/{self}"
- return hashlib.sha1(contents.encode("utf-8")).hexdigest()
-
- @property
- def parents(self) -> Sequence["Path"]:
- parents: List["Path"] = list(super().parents)
- for parent in parents:
- parent._copy_properties_from(self)
- return parents
-
- @property
- def parent(self) -> "Path":
- parent: Path = super().parent
- parent._copy_properties_from(self)
- return parent
-
- def exists(self) -> bool:
- """Check if the path exists locally or remotely.
-
- If the path exists locally, this method immediately returns ``True``, otherwise it will make a RPC call
- to the attached origin Work and check if the path exists remotely.
- If you strictly want to check local existence only, use :meth:`exists_local` instead. If you strictly want
- to check existence on the remote (regardless of whether the file exists locally or not), use
- :meth:`exists_remote`.
-
- """
- return self.exists_local() or (self._origin and self.exists_remote())
-
- def exists_local(self) -> bool:
- """Check if the path exists locally."""
- return super().exists()
-
- def exists_remote(self) -> bool:
- """Check if the path exists remotely on the attached orgin Work.
-
- Raises:
- RuntimeError: If the path is not attached to any Work (origin undefined).
-
- """
- # Fail early if we need to check the remote but an origin is not defined
- if not self._origin or self._request_queue is None or self._response_queue is None:
- raise RuntimeError(
- f"Trying to check if the file {self} exists, but the path is not attached to a LightningWork."
- f" Set it as an attribute to a LightningWork or pass it to the `run()` method."
- )
-
- # 1. Send message to orchestrator through queue that with a request for a path existence check
- request = _ExistsRequest(source=self.origin_name, path=str(self), name=self._name, hash=self.hash)
- self._request_queue.put(request)
-
- # 2. Wait for the response to come back
- response: _ExistsResponse = self._response_queue.get() # blocking
- return response.exists
-
- def get(self, overwrite: bool = False) -> None:
- if _is_flow_context():
- raise RuntimeError("`Path.get()` can only be called from within the `run()` method of LightningWork.")
- if self._request_queue is None or self._response_queue is None:
- raise RuntimeError(
- f"Trying to get the file {self}, but the path is not attached to a LightningApp."
- f" Are you trying to get the file from within `__init__`?"
- )
- if self._origin is None:
- raise RuntimeError(
- f"Trying to get the file {self}, but the path is not attached to a LightningWork. Set it as an"
- f" attribute to a LightningWork or pass it to the `run()` method."
- )
-
- if self.exists_local() and not overwrite:
- raise FileExistsError(
- f"The file or folder {self} exists locally. Pass `overwrite=True` if you wish to replace it"
- f" with the new contents."
- )
-
- # 1. Send message to orchestrator through queue with details of the transfer
- # the source is the name of the work that owns the file that we request
- # the destination is determined by the queue, since each work has a dedicated send and recv queue
- request = _GetRequest(source=self.origin_name, path=str(self), hash=self.hash, name=self._name)
- self._request_queue.put(request)
-
- # 2. Wait for the transfer to finish
- response: _GetResponse = self._response_queue.get() # blocking
- self._validate_get_response(response)
-
- fs = _filesystem()
-
- # 3. Wait until the file appears in shared storage
- while not fs.exists(response.path) or fs.info(response.path)["size"] != response.size:
- sleep(REMOTE_STORAGE_WAIT)
-
- if self.exists_local() and self.is_dir():
- # Delete the directory, otherwise we can't overwrite it
- shutil.rmtree(self)
-
- # 4. Copy the file from the shared storage to the destination on the local filesystem
- if fs.isdir(response.path):
- if isinstance(fs, LocalFileSystem):
- shutil.copytree(response.path, self.resolve())
- else:
- glob = f"{str(response.path)}/**"
- _logger.debug(f"Attempting to copy {glob} -> {str(self.absolute())}")
- fs.get(glob, str(self.absolute()), recursive=False)
- else:
- _logger.debug(f"Attempting to copy {str(response.path)} -> {str(self.absolute())}")
- fs.get(str(response.path), str(self.absolute()), recursive=False)
-
- def to_dict(self) -> dict:
- """Serialize this Path to a dictionary."""
- return {
- "path": str(self),
- "origin_name": self.origin_name,
- "consumer_name": self.consumer_name,
- "metadata": self._metadata,
- }
-
- @classmethod
- def from_dict(cls, content: dict) -> "Path":
- """Instantiate a Path from a dictionary."""
- path = cls(content["path"])
- path._origin = content["origin_name"]
- path._consumer = content["consumer_name"]
- path._metadata = content["metadata"]
- return path
-
- def _validate_get_response(self, response: "_GetResponse") -> None:
- if response.source != self._origin or response.hash != self.hash:
- raise RuntimeError(
- f"Tried to get the file {self} but received a response for a request it did not send. The response"
- f" contents are: {response}"
- )
-
- if response.exception is not None:
- raise RuntimeError(
- f"An exception was raised while trying to transfer the contents at {response.path}"
- f" from Work {response.source} to {response.destination}. See the full stacktrace above."
- ) from response.exception
-
- def _attach_work(self, work: "LightningWork") -> None:
- """Attach a LightningWork to this Path.
-
- The first work to be attached becomes the `origin`, i.e., the Work that is meant to expose the file to other
- Work. Attaching a Work to a Path that already has an `origin` Work will make it a `consumer`. A consumer Work
- is a work that can access the file only by first transferring it via :meth:`transfer`.
-
- Args:
- work: LightningWork to be attached to this Path.
-
- """
- if self._origin is None:
- # Can become an owner only if there is not already one
- self._origin = work
- self._consumer = work
-
- def _attach_queues(self, request_queue: BaseQueue, response_queue: BaseQueue) -> None:
- """Attaches the queues for communication with the Storage Orchestrator."""
- self._request_queue = request_queue
- self._response_queue = response_queue
-
- def _sanitize(self) -> None:
- """Sanitize this Path so that it can be deep-copied."""
- self._origin = self.origin_name
- self._consumer = self.consumer_name
- self._request_queue = None
- self._response_queue = None
-
- def _copy_properties_from(self, other: "Path") -> None:
- self._origin = other._origin
- self._consumer = other._consumer
- self._metadata = other._metadata
- self._request_queue = other._request_queue
- self._response_queue = other._response_queue
-
- def with_name(self, name: str) -> "Path":
- path: Path = super().with_name(name)
- path._copy_properties_from(self)
- return path
-
- def with_stem(self, stem: str) -> "Path":
- path: Path = super().with_stem(stem)
- path._copy_properties_from(self)
- return path
-
- def with_suffix(self, suffix: str) -> "Path":
- path: Path = super().with_suffix(suffix)
- path._copy_properties_from(self)
- return path
-
- def relative_to(self, *other) -> "Path":
- path: Path = super().relative_to(*other)
- path._copy_properties_from(self)
- return path
-
- def __truediv__(self, other: Union["Path", PathlibPath, str]) -> "Path":
- path: Path = super().__truediv__(other)
- path._copy_properties_from(self)
- return path
-
- def __rtruediv__(self, other: Union["Path", PathlibPath, str]) -> "Path":
- path: Path = super().__rtruediv__(other)
- path._copy_properties_from(self)
- return path
-
- def __reduce__(self):
- return Path.from_dict, (self.to_dict(),)
-
- def __json__(self) -> dict:
- """Converts the Path to a json-serializable dict object."""
- return self.to_dict()
-
- @staticmethod
- def _handle_exists_request(work: "LightningWork", request: _ExistsRequest) -> _ExistsResponse:
- return _ExistsResponse(
- source=request.source,
- name=request.name,
- hash=request.hash,
- path=request.path,
- destination=request.destination,
- exists=os.path.exists(request.path),
- )
-
- @staticmethod
- def _handle_get_request(work: "LightningWork", request: _GetRequest) -> _GetResponse:
- from lightning.app.storage.copier import _copy_files
-
- source_path = pathlib.Path(request.path)
- destination_path = _shared_storage_path() / request.hash
- response = _GetResponse(
- source=request.source,
- name=request.name,
- path=str(destination_path),
- hash=request.hash,
- size=source_path.stat().st_size,
- destination=request.destination,
- )
-
- try:
- _copy_files(source_path, destination_path)
- _logger.debug(f"All files copied from {request.path} to {response.path}.")
- except Exception as ex:
- response.exception = ex
- return response
-
-
-def _is_lit_path(path: Union[str, Path]) -> bool:
- path = Path(path)
- return path == _storage_root_dir() or _storage_root_dir() in path.parents
-
-
-def _shared_local_mount_path() -> pathlib.Path:
- """Returns the shared directory through which the Copier threads move files from one Work filesystem to another.
-
- The shared directory can be set via the environment variable ``SHARED_MOUNT_DIRECTORY`` and should be pointing to a
- directory that all Works have mounted (shared filesystem).
-
- """
- path = pathlib.Path(os.environ.get("SHARED_MOUNT_DIRECTORY", ".shared"))
- path.mkdir(parents=True, exist_ok=True)
- return path.absolute()
-
-
-def _storage_root_dir() -> pathlib.Path:
- path = pathlib.Path(os.environ.get("STORAGE_ROOT_DIR", "./.storage")).absolute()
- path.mkdir(parents=True, exist_ok=True)
- return path
-
-
-def _shared_storage_path() -> pathlib.Path:
- """Returns the shared path through which the Copier threads move files from one Work filesystem to another.
-
- The shared path gets set by the environment. Locally, it is pointing to a directory determined by the
- ``SHARED_MOUNT_DIRECTORY`` environment variable. In the cloud, the shared path will point to a S3 bucket. All Works
- have access to this shared dropbox.
-
- """
- storage_path = os.getenv("LIGHTNING_STORAGE_PATH", "")
- if storage_path != "":
- return pathlib.Path(storage_path)
-
- # TODO[dmitsf]: this logic is still needed for compatibility reasons.
- # We should remove it after some time.
- bucket_name = os.getenv("LIGHTNING_BUCKET_NAME", "")
- app_id = os.getenv("LIGHTNING_CLOUD_APP_ID", "")
-
- if bucket_name != "" and app_id != "":
- return pathlib.Path(f"{bucket_name}/lightningapps/{app_id}")
-
- return _shared_local_mount_path()
-
-
-def _artifacts_path(work: "LightningWork") -> pathlib.Path:
- return _shared_storage_path() / "artifacts" / work.name
-
-
-def _path_to_work_artifact(path: Union[Path, pathlib.Path, str], work: "LightningWork") -> pathlib.Path:
- return _artifacts_path(work) / pathlib.Path(*pathlib.Path(path).absolute().parts[1:])
-
-
-def _filesystem() -> AbstractFileSystem:
- fs = LocalFileSystem()
-
- endpoint_url = os.getenv("LIGHTNING_BUCKET_ENDPOINT_URL", "")
- bucket_name = os.getenv("LIGHTNING_BUCKET_NAME", "")
- if endpoint_url != "" and bucket_name != "":
- # FIXME: Temporary fix until we remove the injection from the platform
- if "AWS_ACCESS_KEY_ID" in os.environ:
- del os.environ["AWS_ACCESS_KEY_ID"]
- del os.environ["AWS_SECRET_ACCESS_KEY"]
-
- fs = S3FileSystem()
-
- app_id = os.getenv("LIGHTNING_CLOUD_APP_ID", "")
- if app_id == "":
- raise RuntimeError("missing LIGHTNING_CLOUD_APP_ID")
-
- if not fs.exists(_shared_storage_path()):
- raise RuntimeError(f"shared filesystem {_shared_storage_path()} does not exist")
-
- return fs
diff --git a/src/lightning/app/storage/payload.py b/src/lightning/app/storage/payload.py
deleted file mode 100644
index 03dd018bd8fcc..0000000000000
--- a/src/lightning/app/storage/payload.py
+++ /dev/null
@@ -1,274 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import hashlib
-import pathlib
-import pickle
-from abc import ABC, abstractmethod
-from time import sleep
-from typing import TYPE_CHECKING, Any, Optional, Union
-
-from lightning.app.core.constants import REMOTE_STORAGE_WAIT
-from lightning.app.core.queues import BaseQueue
-from lightning.app.storage.path import Path, _filesystem, _shared_storage_path
-from lightning.app.storage.requests import _ExistsRequest, _ExistsResponse, _GetRequest, _GetResponse
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.component import _is_flow_context
-
-_logger = Logger(__name__)
-
-if TYPE_CHECKING:
- from lightning.app.core.work import LightningWork
-
-
-class _BasePayload(ABC):
- def __init__(self, value: Any) -> None:
- self._value = value
- # the attribute name given to the payload
- self._name: Optional[str] = None
- # the origin is the work that created this Path and wants to expose file(s)
- self._origin: Optional[Union["LightningWork", str]] = None
- # the consumer is the Work that needs access to the file(s) from the consumer
- self._consumer: Optional[Union["LightningWork", str]] = None
- self._metadata = {}
- # request queue: used to transfer message to storage orchestrator
- self._request_queue: Optional[BaseQueue] = None
- # response queue: used to receive status message from storage orchestrator
- self._response_queue: Optional[BaseQueue] = None
-
- @property
- def name(self) -> Optional[str]:
- return self._name
-
- @property
- def value(self) -> Optional[Any]:
- """The real object that this payload holds."""
- return self._value
-
- @property
- def hash(self) -> Optional[str]:
- """The hash of this Payload uniquely identifies the payload and the associated origin Work.
-
- Returns ``None`` if the origin is not defined, i.e., this Path did not yet get attached to a LightningWork.
-
- """
- if self._origin is None:
- return None
- contents = f"{self.origin_name}/{self.consumer_name}/{self.name}"
- return hashlib.sha1(contents.encode("utf-8")).hexdigest()
-
- @property
- def origin_name(self) -> str:
- """The name of the LightningWork where this payload was first created.
-
- Attaching a Payload to a LightningWork will automatically make it the `origin`.
-
- """
- from lightning.app.core.work import LightningWork
-
- return self._origin.name if isinstance(self._origin, LightningWork) else self._origin
-
- @property
- def consumer_name(self) -> str:
- """The name of the LightningWork where this payload is being accessed.
-
- By default, this is the same as the :attr:`origin_name`.
-
- """
- from lightning.app.core.work import LightningWork
-
- return self._consumer.name if isinstance(self._consumer, LightningWork) else self._consumer
-
- @property
- def _path(self) -> Optional[Path]:
- """Path to the file that the payload value gets serialized to."""
- if not self._name:
- return None
- return Path("lit://", self._name)
-
- @abstractmethod
- def save(self, obj: Any, path: str) -> None:
- """Override this method with your own saving logic."""
-
- @abstractmethod
- def load(self, path: str) -> Any:
- """Override this method with your own loading logic."""
-
- def _attach_work(self, work: "LightningWork") -> None:
- """Attach a LightningWork to this PayLoad.
-
- Args:
- work: LightningWork to be attached to this Payload.
-
- """
- if self._origin is None:
- # Can become an owner only if there is not already one
- self._origin = work.name
- self._consumer = work.name
-
- def _attach_queues(self, request_queue: BaseQueue, response_queue: BaseQueue) -> None:
- """Attaches the queues for communication with the Storage Orchestrator."""
- self._request_queue = request_queue
- self._response_queue = response_queue
-
- def _sanitize(self) -> None:
- """Sanitize this Payload so that it can be deep-copied."""
- self._origin = self.origin_name
- self._consumer = self.consumer_name
- self._request_queue = None
- self._response_queue = None
-
- def exists_remote(self):
- """Check if the payload exists remotely on the attached orgin Work.
-
- Raises:
- RuntimeError: If the payload is not attached to any Work (origin undefined).
-
- """
- # Fail early if we need to check the remote but an origin is not defined
- if not self._origin or self._request_queue is None or self._response_queue is None:
- raise RuntimeError(
- f"Trying to check if the payload {self} exists, but the payload is not attached to a LightningWork."
- f" Set it as an attribute to a LightningWork or pass it to the `run()` method."
- )
-
- # 1. Send message to orchestrator through queue that with a request for a path existence check
- request = _ExistsRequest(source=self.origin_name, name=self._name, path=str(self._path), hash=self.hash)
- self._request_queue.put(request)
-
- # 2. Wait for the response to come back
- response: _ExistsResponse = self._response_queue.get() # blocking
- return response.exists
-
- def get(self) -> Any:
- if _is_flow_context():
- raise RuntimeError("`Payload.get()` can only be called from within the `run()` method of LightningWork.")
-
- if self._request_queue is None or self._response_queue is None:
- raise RuntimeError(
- f"Trying to get the file {self}, but the payload is not attached to a LightningApp."
- f" Are you trying to get the file from within `__init__`?"
- )
- if self._origin is None:
- raise RuntimeError(
- f"Trying to get the file {self}, but the payload is not attached to a LightningWork. Set it as an"
- f" attribute to a LightningWork or pass it to the `run()` method."
- )
-
- # 1. Send message to orchestrator through queue with details of the transfer
- # the source is the name of the work that owns the file that we request
- # the destination is determined by the queue, since each work has a dedicated send and recv queue
- request = _GetRequest(source=self.origin_name, name=self._name, path=str(self._path), hash=self.hash)
- self._request_queue.put(request)
-
- # 2. Wait for the transfer to finish
- response: _GetResponse = self._response_queue.get() # blocking
- self._validate_get_response(response)
-
- fs = _filesystem()
-
- # 3. Wait until the file appears in shared storage
- while not fs.exists(response.path) or fs.info(response.path)["size"] != response.size:
- sleep(REMOTE_STORAGE_WAIT)
-
- # 4. Copy the file from the shared storage to the destination on the local filesystem
- local_path = self._path
- _logger.debug(f"Attempting to copy {str(response.path)} -> {str(local_path)}")
- fs.get(str(response.path), str(local_path), recursive=False)
-
- # Ensure the file is properly written
- sleep(0.5)
-
- self._value = self.load(local_path)
- return self._value
-
- def _validate_get_response(self, response: "_GetResponse") -> None:
- if response.source != self._origin or response.hash != self.hash:
- raise RuntimeError(
- f"Tried to get the file {self} but received a response for a request it did not send. The response"
- f" contents are: {response}"
- )
-
- if response.exception is not None:
- raise RuntimeError(
- f"An exception was raised while trying to transfer the contents at {response.path}"
- f" from Work {response.source} to {response.destination}. See the full stacktrace above."
- ) from response.exception
-
- def to_dict(self) -> dict:
- """Serialize this Path to a dictionary."""
- return {
- "name": self.name,
- "origin_name": self.origin_name,
- "consumer_name": self.consumer_name,
- "metadata": self._metadata,
- }
-
- @classmethod
- def from_dict(cls, content: dict) -> "_BasePayload":
- """Instantiate a Payload from a dictionary."""
- payload = cls(None)
- payload._name = content["name"]
- payload._origin = content["origin_name"]
- payload._consumer = content["consumer_name"]
- payload._metadata = content["metadata"]
- return payload
-
- @staticmethod
- def _handle_exists_request(work: "LightningWork", request: _ExistsRequest) -> _ExistsResponse:
- return _ExistsResponse(
- source=request.source,
- path=request.path,
- name=request.name,
- destination=request.destination,
- hash=request.hash,
- exists=getattr(work, request.name, None) is not None,
- )
-
- @staticmethod
- def _handle_get_request(work: "LightningWork", request: _GetRequest) -> _GetResponse:
- from lightning.app.storage.copier import _copy_files
-
- source_path = pathlib.Path(request.path)
- destination_path = _shared_storage_path() / request.hash
- response = _GetResponse(
- source=request.source,
- name=request.name,
- path=str(destination_path),
- hash=request.hash,
- destination=request.destination,
- )
-
- try:
- payload = getattr(work, request.name)
- payload.save(payload.value, source_path)
- response.size = source_path.stat().st_size
- _copy_files(source_path, destination_path)
- _logger.debug(f"All files copied from {request.path} to {response.path}.")
- except Exception as ex:
- response.exception = ex
- return response
-
-
-class Payload(_BasePayload):
- """The Payload object enables to transfer python objects from one work to another in a similar fashion as
- :class:`~lightning.app.storage.path.Path`."""
-
- def save(self, obj: Any, path: str) -> None:
- with open(path, "wb") as f:
- pickle.dump(obj, f)
-
- def load(self, path: str) -> Any:
- with open(path, "rb") as f:
- return pickle.load(f)
diff --git a/src/lightning/app/storage/requests.py b/src/lightning/app/storage/requests.py
deleted file mode 100644
index 83f430c452ce0..0000000000000
--- a/src/lightning/app/storage/requests.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dataclasses import dataclass
-from typing import Optional
-
-
-@dataclass
-class _GetRequest:
- source: str
- name: str
- path: str
- hash: str
- destination: str = ""
-
-
-@dataclass
-class _GetResponse:
- source: str
- name: str
- path: str
- hash: str
- size: int = 0
- destination: str = ""
- exception: Optional[Exception] = None
- timedelta: Optional[float] = None
-
-
-@dataclass
-class _ExistsRequest:
- source: str
- name: str
- path: str
- hash: str
- destination: str = ""
-
-
-@dataclass
-class _ExistsResponse:
- source: str
- name: str
- path: str
- hash: str
- destination: str = ""
- exists: Optional[bool] = None
- timedelta: Optional[float] = None
diff --git a/src/lightning/app/structures/__init__.py b/src/lightning/app/structures/__init__.py
deleted file mode 100644
index a432fb6daf1b5..0000000000000
--- a/src/lightning/app/structures/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from lightning.app.structures.dict import Dict
-from lightning.app.structures.list import List
-
-__all__ = ["Dict", "List"]
diff --git a/src/lightning/app/structures/dict.py b/src/lightning/app/structures/dict.py
deleted file mode 100644
index 7accc0c1db627..0000000000000
--- a/src/lightning/app/structures/dict.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import typing as t
-
-from lightning.app.utilities.app_helpers import _LightningAppRef, _set_child_name
-
-T = t.TypeVar("T")
-
-if t.TYPE_CHECKING:
- from lightning.app.utilities.types import Component
-
-
-def _prepare_name(component: "Component") -> str:
- return str(component.name.split(".")[-1])
-
-
-# TODO: add support and tests for dict operations (insertion, update, etc.)
-class Dict(t.Dict[str, T]):
- def __init__(self, **kwargs: T):
- """The Dict Object is used to represents dict collection of :class:`~lightning.app.core.work.LightningWork` or
- :class:`~lightning.app.core.flow.LightningFlow`.
-
- Example:
-
- >>> from lightning.app import LightningFlow, LightningWork
- >>> from lightning.app.structures import Dict
- >>> class CounterWork(LightningWork):
- ... def __init__(self):
- ... super().__init__()
- ... self.counter = 0
- ... def run(self):
- ... self.counter += 1
- ...
- >>> class RootFlow(LightningFlow):
- ... def __init__(self):
- ... super().__init__()
- ... self.dict = Dict(**{"work_0": CounterWork(), "work_1": CounterWork()})
- ... def run(self):
- ... for work_name, work in self.dict.items():
- ... work.run()
- ...
- >>> flow = RootFlow()
- >>> flow.run()
- >>> assert flow.dict["work_0"].counter == 1
-
- Arguments:
- items: A sequence of LightningWork or LightningFlow.
-
- """
- super().__init__(**kwargs)
- from lightning.app.runners.backends import Backend
-
- self._name: t.Optional[str] = ""
- self._backend: t.Optional[Backend] = None
- for k, v in kwargs.items():
- if "." in k:
- raise Exception(f"The provided name {k} contains . which is forbidden.")
- _set_child_name(self, v, k)
-
- def __setitem__(self, k, v):
- from lightning.app.core import LightningFlow, LightningWork
-
- if not isinstance(k, str):
- raise Exception("The provided key should be an string")
-
- if isinstance(k, str) and "." in k:
- raise Exception(f"The provided name {k} contains . which is forbidden.")
-
- _set_child_name(self, v, k)
- if self._backend:
- if isinstance(v, LightningFlow):
- LightningFlow._attach_backend(v, self._backend)
- elif isinstance(v, LightningWork):
- self._backend._wrap_run_method(_LightningAppRef().get_current(), v)
- v._name = f"{self.name}.{k}"
- super().__setitem__(k, v)
-
- @property
- def works(self):
- from lightning.app.core import LightningFlow, LightningWork
-
- works = [item for item in self.values() if isinstance(item, LightningWork)]
- for flow in [item for item in self.values() if isinstance(item, LightningFlow)]:
- for child_work in flow.works(recurse=False):
- works.append(child_work)
- return works
-
- @property
- def flows(self):
- from lightning.app.core.flow import LightningFlow
- from lightning.app.structures import Dict as _Dict
- from lightning.app.structures import List as _List
-
- flows = {}
- for item in self.values():
- if isinstance(item, LightningFlow):
- flows[item.name] = item
- for child_flow in item.flows.values():
- flows[child_flow.name] = child_flow
- if isinstance(item, (_Dict, _List)):
- for child_flow in item.flows.values():
- flows[child_flow.name] = child_flow
- return flows
-
- @property
- def name(self):
- return self._name or "root"
-
- @property
- def state(self):
- """Returns the state of its flows and works."""
- from lightning.app.core import LightningFlow, LightningWork
-
- return {
- "works": {key: item.state for key, item in self.items() if isinstance(item, LightningWork)},
- "flows": {key: item.state for key, item in self.items() if isinstance(item, LightningFlow)},
- }
-
- @property
- def state_vars(self):
- from lightning.app.core import LightningFlow, LightningWork
-
- return {
- "works": {key: item.state_vars for key, item in self.items() if isinstance(item, LightningWork)},
- "flows": {key: item.state_vars for key, item in self.items() if isinstance(item, LightningFlow)},
- }
-
- @property
- def state_with_changes(self):
- from lightning.app.core import LightningFlow, LightningWork
-
- return {
- "works": {key: item.state_with_changes for key, item in self.items() if isinstance(item, LightningWork)},
- "flows": {key: item.state_with_changes for key, item in self.items() if isinstance(item, LightningFlow)},
- }
-
- def set_state(self, state):
- state_keys = set(list(state["works"].keys()) + list(state["flows"].keys()))
- current_state_keys = set(self.keys())
- if current_state_keys != state_keys:
- key_diff = (current_state_keys - state_keys) | (state_keys - current_state_keys)
- raise Exception(
- f"The provided state doesn't match the `Dict` {self.name}. Found `{key_diff}` un-matching keys"
- )
- for work_key, work_state in state["works"].items():
- self[work_key].set_state(work_state)
- for child_key, child_state in state["flows"].items():
- self[child_key].set_state(child_state)
diff --git a/src/lightning/app/structures/list.py b/src/lightning/app/structures/list.py
deleted file mode 100644
index f2d81ac9f86dc..0000000000000
--- a/src/lightning/app/structures/list.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import typing as t
-
-from lightning.app.utilities.app_helpers import _LightningAppRef, _set_child_name
-
-T = t.TypeVar("T")
-
-if t.TYPE_CHECKING:
- from lightning.app.utilities.types import Component
-
-
-def _prepare_name(component: "Component") -> str:
- return str(component.name.split(".")[-1])
-
-
-# TODO: add support and tests for list operations (concatenation, deletion, insertion, etc.)
-class List(t.List[T]):
- def __init__(self, *items: T):
- """The List Object is used to represents list collection of :class:`~lightning.app.core.work.LightningWork` or
- :class:`~lightning.app.core.flow.LightningFlow`.
-
- Example:
-
- >>> from lightning.app import LightningFlow, LightningWork
- >>> from lightning.app.structures import List
- >>> class CounterWork(LightningWork):
- ... def __init__(self):
- ... super().__init__()
- ... self.counter = 0
- ... def run(self):
- ... self.counter += 1
- ...
- >>> class RootFlow(LightningFlow):
- ... def __init__(self):
- ... super().__init__()
- ... self.list = List(*[CounterWork(), CounterWork()])
- ... def run(self):
- ... for work in self.list:
- ... work.run()
- ...
- >>> flow = RootFlow()
- >>> flow.run()
- >>> assert flow.list[0].counter == 1
-
- Arguments:
- items: A sequence of LightningWork or LightningFlow.
-
- """
- super().__init__()
- from lightning.app.runners.backends import Backend
-
- self._name: t.Optional[str] = ""
- self._last_index = 0
- self._backend: t.Optional[Backend] = None
- for item in items:
- self.append(item)
-
- def append(self, v):
- from lightning.app.core import LightningFlow, LightningWork
-
- _set_child_name(self, v, str(self._last_index))
- if self._backend:
- if isinstance(v, LightningFlow):
- LightningFlow._attach_backend(v, self._backend)
- elif isinstance(v, LightningWork):
- self._backend._wrap_run_method(_LightningAppRef().get_current(), v)
- v._name = f"{self.name}.{self._last_index}"
- self._last_index += 1
- super().append(v)
-
- @property
- def name(self):
- """Returns the name of this List object."""
- return self._name or "root"
-
- @property
- def works(self):
- from lightning.app.core import LightningFlow, LightningWork
-
- works = [item for item in self if isinstance(item, LightningWork)]
- for flow in [item for item in self if isinstance(item, LightningFlow)]:
- for child_work in flow.works(recurse=False):
- works.append(child_work)
- return works
-
- @property
- def flows(self):
- from lightning.app.core import LightningFlow
- from lightning.app.structures import Dict as _Dict
- from lightning.app.structures import List as _List
-
- flows = {}
- for item in self:
- if isinstance(item, LightningFlow):
- flows[item.name] = item
- for child_flow in item.flows.values():
- flows[child_flow.name] = child_flow
- if isinstance(item, (_Dict, _List)):
- for child_flow in item.flows.values():
- flows[child_flow.name] = child_flow
- return flows
-
- @property
- def state(self):
- """Returns the state of its flows and works."""
- from lightning.app.core import LightningFlow, LightningWork
-
- works = [item for item in self if isinstance(item, LightningWork)]
- children = [item for item in self if isinstance(item, LightningFlow)]
- return {
- "works": {_prepare_name(w): w.state for w in works},
- "flows": {_prepare_name(flow): flow.state for flow in children},
- }
-
- @property
- def state_vars(self):
- from lightning.app.core import LightningFlow, LightningWork
-
- works = [item for item in self if isinstance(item, LightningWork)]
- children = [item for item in self if isinstance(item, LightningFlow)]
- return {
- "works": {_prepare_name(w): w.state_vars for w in works},
- "flows": {_prepare_name(flow): flow.state_vars for flow in children},
- }
-
- @property
- def state_with_changes(self):
- from lightning.app.core import LightningFlow, LightningWork
-
- works = [item for item in self if isinstance(item, LightningWork)]
- children = [item for item in self if isinstance(item, LightningFlow)]
- return {
- "works": {str(_prepare_name(w)): w.state_with_changes for w in works},
- "flows": {_prepare_name(flow): flow.state_with_changes for flow in children},
- }
-
- def set_state(self, state):
- """Method to set the state of the list and its children."""
- from lightning.app.core import LightningFlow, LightningWork
-
- works = [item for item in self if isinstance(item, LightningWork)]
- children = [item for item in self if isinstance(item, LightningFlow)]
-
- current_state_keys = {_prepare_name(w) for w in self}
- state_keys = set(list(state["works"].keys()) + list(state["flows"].keys()))
-
- if current_state_keys != state_keys:
- key_diff = (current_state_keys - state_keys) | (state_keys - current_state_keys)
- raise Exception(
- f"The provided state doesn't match the `List` {self.name}. Found `{key_diff}` un-matching keys"
- )
-
- for work_key, work_state in state["works"].items():
- for work in works:
- if _prepare_name(work) == work_key:
- work.set_state(work_state)
- for child_key, child_state in state["flows"].items():
- for child in children:
- if _prepare_name(child) == child_key:
- child.set_state(child_state)
-
- def __len__(self):
- """Returns the number of elements within this List."""
- return sum(1 for _ in self)
diff --git a/src/lightning/app/testing/__init__.py b/src/lightning/app/testing/__init__.py
deleted file mode 100644
index 1c4e2e171608d..0000000000000
--- a/src/lightning/app/testing/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from lightning.app.testing.helpers import EmptyFlow, EmptyWork
-from lightning.app.testing.testing import (
- LightningTestApp,
- application_testing,
- delete_cloud_lightning_apps,
- run_app_in_cloud,
- run_work_isolated,
- wait_for,
-)
-
-__all__ = [
- "application_testing",
- "run_work_isolated",
- "LightningTestApp",
- "delete_cloud_lightning_apps",
- "run_app_in_cloud",
- "wait_for",
- "EmptyFlow",
- "EmptyWork",
-]
diff --git a/src/lightning/app/testing/config.py b/src/lightning/app/testing/config.py
deleted file mode 100644
index 677c382f5b5ca..0000000000000
--- a/src/lightning/app/testing/config.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from dataclasses import dataclass
-
-
-@dataclass
-class _Config:
- id = os.getenv("LIGHTNING_USER_ID")
- key = os.getenv("LIGHTNING_API_KEY")
- url = os.getenv("LIGHTNING_CLOUD_URL", "https://lightning.ai")
- api_key = os.getenv("LIGHTNING_API_KEY")
- username = os.getenv("LIGHTNING_USERNAME")
- video_location = os.getenv("VIDEO_LOCATION", "./artifacts/videos")
- har_location = os.getenv("HAR_LOCATION", "./artifacts/hars")
- slowmo = os.getenv("SLOW_MO", "0")
diff --git a/src/lightning/app/testing/helpers.py b/src/lightning/app/testing/helpers.py
deleted file mode 100644
index 61a00f957299e..0000000000000
--- a/src/lightning/app/testing/helpers.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import subprocess
-import sys
-from queue import Empty
-from typing import Any, List, Optional, Tuple
-
-from packaging.version import Version
-
-from lightning.app.core import LightningFlow, LightningWork
-from lightning.app.core.queues import BaseQueue
-from lightning.app.utilities.imports import (
- _CLOUD_TEST_RUN,
- _is_lightning_flash_available,
- _is_pytorch_lightning_available,
-)
-
-
-def _call_script(
- filepath: str,
- args: Optional[List[str]] = None,
- timeout: Optional[int] = 60 * 10,
-) -> Tuple[int, str, str]:
- if args is None:
- args = []
- args = [str(a) for a in args]
- command = [sys.executable, filepath] + args # todo: add back coverage
- p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- try:
- stdout, stderr = p.communicate(timeout=timeout)
- except subprocess.TimeoutExpired:
- p.kill()
- stdout, stderr = p.communicate()
- stdout = stdout.decode("utf-8")
- stderr = stderr.decode("utf-8")
- return p.returncode, stdout, stderr
-
-
-def _run_script(filepath):
- code, stdout, stderr = _call_script(filepath)
- print(f"{filepath} STDOUT: {stdout}")
- print(f"{filepath} STDERR: {stderr}")
- assert not code, code
-
-
-class _RunIf:
- """RunIf wrapper for simple marking specific cases, fully compatible with pytest.mark::
-
- @RunIf(...)
- @pytest.mark.parametrize("arg1", [1, 2.0])
- def test_wrapper(arg1):
- assert arg1 > 0.0
-
- """
-
- def __new__(
- self,
- *args: Any,
- pl: bool = False,
- flash: bool = False,
- min_python: Optional[str] = None,
- skip_windows: bool = False,
- skip_linux: bool = False,
- skip_mac_os: bool = False,
- local_end_to_end: bool = False,
- cloud: bool = False,
- **kwargs: Any,
- ):
- """
- Args:
- *args: Any :class:`pytest.mark.skipif` arguments.
- pl: Requires that PyTorch Lightning is installed.
- flash: Requires that Flash is installed.
- min_python: Require that Python is greater or equal than this version.
- skip_windows: Skip for Windows platform.
- skip_linux: Skip for Linux platform.
- skip_mac_os: Skip for Mac Os Platform.
- **kwargs: Any :class:`pytest.mark.skipif` keyword arguments.
- """
- import pytest
-
- conditions = []
- reasons = []
-
- if min_python:
- py_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
- conditions.append(Version(py_version) < Version(min_python))
- reasons.append(f"python>={min_python}")
-
- if skip_windows:
- conditions.append(sys.platform == "win32")
- reasons.append("unimplemented on Windows")
-
- if skip_linux:
- conditions.append(sys.platform == "linux")
- reasons.append("unimplemented on linux")
-
- if skip_mac_os:
- conditions.append(sys.platform == "darwin")
- reasons.append("unimplemented on MacOS")
-
- if pl:
- conditions.append(not _is_pytorch_lightning_available())
- reasons.append("PyTorch Lightning is required.")
-
- if flash:
- conditions.append(not _is_lightning_flash_available())
- reasons.append("Lightning Flash is required.")
-
- if cloud:
- conditions.append(not _CLOUD_TEST_RUN)
- reasons.append("Cloud End to End tests should not run.")
-
- reasons = [rs for cond, rs in zip(conditions, reasons) if cond]
- return pytest.mark.skipif(
- *args, condition=any(conditions), reason=f"Requires: [{' + '.join(reasons)}]", **kwargs
- )
-
-
-class _MockQueue(BaseQueue):
- def __init__(self, name: str = "", default_timeout: float = 0):
- super().__init__(name, default_timeout)
- self._queue = []
-
- def put(self, item):
- self._queue.append(item)
-
- def get(self, timeout: int = 0):
- if not self._queue:
- raise Empty()
- return self._queue.pop(0)
-
- def batch_get(self, timeout: int = 0, count: int = None):
- if not self._queue:
- raise Empty()
- return [self._queue.pop(0)]
-
- def __contains__(self, item):
- return item in self._queue
-
- def __len__(self):
- return len(self._queue)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self._queue})"
-
-
-class EmptyFlow(LightningFlow):
- """A LightningFlow that implements all abstract methods to do nothing.
-
- Useful for mocking in tests.
-
- """
-
- def run(self):
- pass
-
-
-class EmptyWork(LightningWork):
- """A LightningWork that implements all abstract methods to do nothing.
-
- Useful for mocking in tests.
-
- """
-
- def run(self):
- pass
diff --git a/src/lightning/app/testing/testing.py b/src/lightning/app/testing/testing.py
deleted file mode 100644
index c1f50fc3f3643..0000000000000
--- a/src/lightning/app/testing/testing.py
+++ /dev/null
@@ -1,535 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import datetime
-import json
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-import time
-from contextlib import contextmanager
-from multiprocessing import Process
-from subprocess import Popen
-from time import sleep
-from typing import Any, Callable, Dict, Generator, List, Optional, Type
-
-import requests
-from lightning_cloud.openapi import V1LightningappInstanceState
-from lightning_cloud.openapi.rest import ApiException
-from requests import Session
-from rich import print
-from rich.color import ANSI_COLOR_NAMES
-
-from lightning.app.cli.lightning_cli import run_app
-from lightning.app.core import LightningApp, LightningFlow, constants
-from lightning.app.runners.multiprocess import MultiProcessRuntime
-from lightning.app.testing.config import _Config
-from lightning.app.utilities.app_logs import _app_logs_reader
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.enum import CacheCallsKeys
-from lightning.app.utilities.imports import _is_playwright_available, requires
-from lightning.app.utilities.log import get_logfile
-from lightning.app.utilities.logs_socket_api import _LightningLogsSocketAPI
-from lightning.app.utilities.network import LightningClient, _configure_session
-from lightning.app.utilities.packaging.lightning_utils import get_dist_path_if_editable_install
-from lightning.app.utilities.proxies import ProxyWorkRun
-
-if _is_playwright_available():
- from playwright.sync_api import HttpCredentials, sync_playwright
-
-
-def _on_error_callback(ws_app, *_):
- ws_app.close()
-
-
-def _print_logs(app_id: str):
- client = LightningClient()
- project = _get_project(client)
-
- works = client.lightningwork_service_list_lightningwork(
- project_id=project.project_id,
- app_id=app_id,
- ).lightningworks
- component_names = ["flow"] + [w.name for w in works]
-
- rich_colors = list(ANSI_COLOR_NAMES)
- colors = {c: rich_colors[i + 1] for i, c in enumerate(component_names)}
-
- max_length = max(len(c.replace("root.", "")) for c in component_names)
- identifiers = []
-
- print("################### PRINTING LOGS ###################")
-
- logs_api_client = _LightningLogsSocketAPI(client.api_client)
-
- while True:
- gen = _app_logs_reader(
- logs_api_client=logs_api_client,
- project_id=project.project_id,
- app_id=app_id,
- component_names=component_names,
- follow=False,
- on_error_callback=_on_error_callback,
- )
- for log_event in gen:
- message = log_event.message
- identifier = f"{log_event.timestamp}{log_event.message}"
- if identifier not in identifiers:
- date = log_event.timestamp.strftime("%m/%d/%Y %H:%M:%S")
- identifiers.append(identifier)
- color = colors[log_event.component_name]
- padding = (max_length - len(log_event.component_name)) * " "
- print(f"[{color}]{log_event.component_name}{padding}[/{color}] {date} {message}")
-
-
-class LightningTestApp(LightningApp):
- def __init__(self, *args: Any, **kwargs: Any):
- super().__init__(*args, **kwargs)
- self.counter = 0
-
- @staticmethod
- def _configure_session() -> Session:
- return _configure_session()
-
- def make_request(self, fn, *args: Any, **kwargs: Any):
- loop = asyncio.new_event_loop()
- loop.run_until_complete(self._make_request(fn, *args, **kwargs))
-
- async def _make_request(self, fn: Callable, *args: Any, **kwargs: Any):
- from lightning.app.utilities.state import AppState
-
- state = AppState()
- state._request_state()
- fn(state, *args, **kwargs)
- state.send_delta()
-
- def on_before_run_once(self):
- pass
-
- def on_after_run_once(self):
- self.counter += 1
-
- def run_once(self):
- before_done = self.on_before_run_once()
- if before_done is not None:
- return before_done
- done = super().run_once()
- after_done = self.on_after_run_once()
- if after_done is not None:
- return after_done
- return done
-
- def kill_work(self, work_name: str, sleep_time: int = 1):
- """Use this method to kill a specific work by its name."""
- self.processes[work_name].kill()
-
- def restart_work(self, work_name: str):
- """Use this method to restart a specific work by its name."""
- self.processes[work_name].restart()
-
-
-@requires("click")
-def application_testing(lit_app_cls: Type[LightningTestApp] = LightningTestApp, command_line: List[str] = []) -> Any:
- from unittest import mock
-
- from click.testing import CliRunner
-
- with mock.patch("lightning.app.LightningApp", lit_app_cls):
- original = sys.argv
- sys.argv = command_line
- runner = CliRunner()
- result = runner.invoke(run_app, command_line, catch_exceptions=False)
- sys.argv = original
- return result
-
-
-class _SingleWorkFlow(LightningFlow):
- def __init__(self, work, args, kwargs):
- super().__init__()
- self.work = work
- self.args = args
- self.kwargs = kwargs
-
- def run(self):
- if self.work.has_succeeded or self.work.has_failed:
- self.stop()
- self.work.run(*self.args, **self.kwargs)
-
-
-def run_work_isolated(work, *args: Any, start_server: bool = False, **kwargs: Any):
- """This function is used to run a work a single time with multiprocessing runtime."""
- MultiProcessRuntime(
- LightningApp(_SingleWorkFlow(work, args, kwargs), log_level="debug"),
- start_server=start_server,
- ).dispatch()
- # pop the stopped status.
- call_hash = work._calls[CacheCallsKeys.LATEST_CALL_HASH]
-
- if call_hash in work._calls:
- work._calls[call_hash]["statuses"].pop(-1)
-
- if isinstance(work.run, ProxyWorkRun):
- work.run = work.run.work_run
-
-
-def _browser_context_args(browser_context_args: Dict) -> Dict:
- return {
- **browser_context_args,
- "viewport": {
- "width": 1920,
- "height": 1080,
- },
- "ignore_https_errors": True,
- }
-
-
-@contextmanager
-def _run_cli(args) -> Generator:
- """This utility is used to automate end-to-end testing of the Lightning AI CLI."""
- cmd = [
- sys.executable,
- "-m",
- "lightning",
- ] + args
-
- with tempfile.TemporaryDirectory() as tmpdir:
- env_copy = os.environ.copy()
- process = Popen(
- cmd,
- cwd=tmpdir,
- env=env_copy,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- process.wait()
-
- yield process.stdout.read().decode("UTF-8"), process.stderr.read().decode("UTF-8")
-
-
-def _fetch_app_by_name(client, project_id, name):
- lit_apps = [
- app
- for app in client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id).lightningapps
- if app.name == name or getattr(app, "display_name", None) == name
- ]
- if not len(lit_apps) == 1:
- raise ValueError(f"Expected to find just one app, found {len(lit_apps)}")
- return lit_apps[0]
-
-
-@requires("playwright")
-@contextmanager
-def run_app_in_cloud(
- app_folder: str, app_name: str = "app.py", extra_args: List[str] = [], debug: bool = True
-) -> Generator:
- """This utility is used to automate testing e2e application with lightning.ai."""
- # 1. Validate the provide app_folder is correct.
- if not os.path.exists(os.path.join(app_folder, app_name)):
- raise Exception(f"The app folder should contain an {app_name} file.")
- if app_folder.endswith("/"):
- app_folder = app_folder[:-1]
-
- # 2. Create the right application name.
- basename = app_folder.split("/")[-1]
- PR_NUMBER = os.getenv("PR_NUMBER", None)
-
- is_editable_mode = get_dist_path_if_editable_install("lightning")
- if not is_editable_mode and PR_NUMBER is not None:
- raise Exception("Lightning requires to be installed in editable mode in the CI.")
-
- TEST_APP_NAME = os.getenv("TEST_APP_NAME", basename)
- os.environ["TEST_APP_NAME"] = TEST_APP_NAME
-
- if PR_NUMBER:
- name = f"test-{PR_NUMBER}-{TEST_APP_NAME}-" + str(int(time.time()))
- else:
- name = f"test-{TEST_APP_NAME}-" + str(int(time.time()))
-
- os.environ["LIGHTNING_APP_NAME"] = name
-
- url = _Config.url
- if url.endswith("/"):
- url = url[:-1]
- payload = {"apiKey": _Config.api_key, "username": _Config.username}
- url_login = url + "/v1/auth/login"
- res = requests.post(url_login, data=json.dumps(payload))
- if "token" not in res.json():
- raise RuntimeError(
- f"You haven't properly setup your environment variables with {url_login} and data: \n{payload}"
- )
-
- token = res.json()["token"]
-
- # 3. Disconnect from the App if any.
- Popen("lightning_app logout", shell=True).wait()
-
- # 4. Launch the application in the cloud from the Lightning CLI.
- with tempfile.TemporaryDirectory() as tmpdir:
- env_copy = os.environ.copy()
- env_copy["PACKAGE_LIGHTNING"] = "1"
- env_copy["LIGHTING_TESTING"] = "1"
- if debug:
- print("Debug mode is enabled")
- env_copy["LIGHTNING_DEBUG"] = "1"
- shutil.copytree(app_folder, tmpdir, dirs_exist_ok=True)
- # TODO - add -no-cache to the command line.
- stdout_path = get_logfile(f"run_app_in_cloud_{name}")
-
- cmd_extra_args = []
-
- with open(stdout_path, "w") as stdout:
- cmd = [
- sys.executable,
- "-m",
- "lightning",
- "run",
- "app",
- app_name,
- "--cloud",
- "--name",
- name,
- "--open-ui",
- "false",
- *cmd_extra_args,
- ]
- print(f"Command: {cmd}")
- process = Popen((cmd + extra_args), cwd=tmpdir, env=env_copy, stdout=stdout, stderr=sys.stderr)
- process.wait()
-
- # Fallback URL to prevent failures in case we don't get the admin URL
- admin_url = _Config.url
- with open(stdout_path) as fo:
- for line in fo.readlines():
- if line.startswith("APP_LOGS_URL: "):
- admin_url = line.replace("APP_LOGS_URL: ", "")
- break
-
- if is_editable_mode:
- # Added to ensure the current code is properly uploaded.
- # Otherwise, it could result in un-tested PRs.
- pkg_found = False
- with open(stdout_path) as fo:
- for line in fo.readlines():
- if "Packaged Lightning with your application" in line:
- pkg_found = True
- print(line) # TODO: use logging
- assert pkg_found
- os.remove(stdout_path)
-
- # 5. Print your application name
- print(f"The Lightning App Name is: [bold magenta]{name}[/bold magenta]")
-
- # 6. Create chromium browser, auth to lightning.app.ai and yield the admin and view pages.
- with sync_playwright() as p:
- browser = p.chromium.launch(headless=bool(int(os.getenv("HEADLESS", "0"))))
- context = browser.new_context(
- # Eventually this will need to be deleted
- http_credentials=HttpCredentials({
- "username": os.getenv("LAI_USER", "").strip(),
- "password": os.getenv("LAI_PASS", ""),
- }),
- record_video_dir=os.path.join(_Config.video_location, TEST_APP_NAME),
- record_har_path=_Config.har_location,
- )
-
- client = LightningClient()
- project_id = _get_project(client).project_id
-
- app = _fetch_app_by_name(client, project_id, name)
- app_id = app.id
- print(f"The Lightning App ID is: {app.id}") # useful for Grafana
-
- if debug:
- process = Process(target=_print_logs, kwargs={"app_id": app_id})
- process.start()
-
- admin_page = context.new_page()
- admin_page.goto(admin_url)
- admin_page.evaluate(
- """data => {
- window.localStorage.setItem('gridUserId', data[0]);
- window.localStorage.setItem('gridUserKey', data[1]);
- window.localStorage.setItem('gridUserToken', data[2]);
- }
- """,
- [_Config.id, _Config.key, token],
- )
- if constants.LIGHTNING_CLOUD_PROJECT_ID:
- admin_page.evaluate(
- """data => {
- window.localStorage.setItem('gridDefaultProjectIdOverride', JSON.stringify(data[0]));
- }
- """,
- [constants.LIGHTNING_CLOUD_PROJECT_ID],
- )
-
- admin_page.reload()
-
- view_page = context.new_page()
- i = 1
- while True:
- app = _fetch_app_by_name(client, project_id, name)
- msg = f"Still in phase {app.status.phase}"
-
- # wait until the app is running and openapi.json is ready
- if app.status.phase == V1LightningappInstanceState.RUNNING:
- status_code = requests.get(f"{app.status.url}/openapi.json").status_code
- if status_code == 200:
- print("App is running, continuing with testing...")
- view_page.goto(f"{app.status.url}/view")
- break
- msg = f"Received status code {status_code} at {app.status.url!r}"
- elif app.status.phase not in (V1LightningappInstanceState.PENDING, V1LightningappInstanceState.NOT_STARTED):
- # there's a race condition if the app goes from pending to running to something else before we evaluate
- # the condition above. avoid it by checking stopped explicitly
- print(f"App finished with phase {app.status.phase}, finished testing...")
- break
- if debug and i % 30 == 0:
- print(f"{msg}, continuing infinite loop...")
- i += 1
- sleep(1)
-
- logs_api_client = _LightningLogsSocketAPI(client.api_client)
-
- def fetch_logs(component_names: Optional[List[str]] = None) -> Generator:
- """This methods creates websockets connection in threads and returns the logs to the main thread."""
- if not component_names:
- works = client.lightningwork_service_list_lightningwork(
- project_id=project_id,
- app_id=app_id,
- ).lightningworks
-
- component_names = ["flow"] + [w.name for w in works]
- else:
-
- def add_prefix(c: str) -> str:
- if c == "flow":
- return c
- if not c.startswith("root."):
- return "root." + c
- return c
-
- component_names = [add_prefix(c) for c in component_names]
-
- gen = _app_logs_reader(
- logs_api_client=logs_api_client,
- project_id=project_id,
- app_id=app_id,
- component_names=component_names,
- follow=False,
- on_error_callback=_on_error_callback,
- )
- for log_event in gen:
- yield log_event.message
-
- try:
- yield admin_page, view_page, fetch_logs, name
- except KeyboardInterrupt:
- pass
- finally:
- if debug:
- process.kill()
-
- context.close()
- browser.close()
- Popen("lightning disconnect", shell=True).wait()
-
- delete_cloud_lightning_apps(name=name)
-
-
-def wait_for(page, callback: Callable, *args: Any, **kwargs: Any) -> Any:
- import playwright
-
- while True:
- try:
- res = callback(*args, **kwargs)
- if res:
- return res
- except (playwright._impl._api_types.Error, playwright._impl._api_types.TimeoutError) as err:
- print(err)
- try:
- sleep(7)
- page.reload()
- except (playwright._impl._api_types.Error, playwright._impl._api_types.TimeoutError) as err:
- print(err)
- pass
- sleep(3)
-
-
-def _delete_lightning_app(client, project_id, app_id, app_name):
- print(f"Deleting {app_name} id: {app_id}")
- try:
- res = client.lightningapp_instance_service_delete_lightningapp_instance(
- project_id=project_id,
- id=app_id,
- )
- assert res == {}
- except ApiException as ex:
- print(f"Failed to delete {app_name}. Exception {ex}")
-
-
-def _delete_cloud_space(client, project_id, cloud_space_id, app_name):
- """Used to delete the parent cloudspace."""
- print(f"Deleting {app_name} id: {cloud_space_id}")
- try:
- res = client.cloud_space_service_delete_cloud_space(
- project_id=project_id,
- id=cloud_space_id,
- )
- assert res == {}
- except ApiException as ex:
- print(f"Failed to delete {app_name}. Exception {ex}")
-
-
-def delete_cloud_lightning_apps(name=None):
- """Cleanup cloud apps that start with the name test-{PR_NUMBER}-{TEST_APP_NAME}.
-
- PR_NUMBER and TEST_APP_NAME are environment variables.
-
- """
-
- client = LightningClient()
-
- try:
- pr_number = int(os.getenv("PR_NUMBER", None))
- except (TypeError, ValueError):
- # Failed when the PR is running master or 'PR_NUMBER' isn't defined.
- pr_number = ""
-
- app_name = os.getenv("TEST_APP_NAME", "").replace("_", "-")
-
- print(f"deleting apps for pr_number: {pr_number}, app_name: {app_name}")
- project_id = _get_project(client).project_id
- list_apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id)
-
- if pr_number and app_name:
- for lit_app in list_apps.lightningapps:
- if name == lit_app.name or (str(pr_number) in lit_app.name and app_name in lit_app.name):
- _delete_lightning_app(client, project_id=project_id, app_id=lit_app.id, app_name=lit_app.name)
- _delete_cloud_space(
- client, project_id=project_id, cloud_space_id=lit_app.spec.cloud_space_id, app_name=lit_app.name
- )
-
- print("deleting apps that were created more than 20 minutes ago.")
-
- for lit_app in list_apps.lightningapps:
- time_diff = datetime.datetime.now(lit_app.created_at.tzinfo) - lit_app.created_at
- if time_diff > datetime.timedelta(minutes=20):
- _delete_lightning_app(client, project_id=project_id, app_id=lit_app.id, app_name=lit_app.name)
- _delete_cloud_space(
- client, project_id=project_id, cloud_space_id=lit_app.spec.cloud_space_id, app_name=lit_app.name
- )
diff --git a/src/lightning/app/utilities/__init__.py b/src/lightning/app/utilities/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/utilities/app_commands.py b/src/lightning/app/utilities/app_commands.py
deleted file mode 100644
index e3e8af50d3e23..0000000000000
--- a/src/lightning/app/utilities/app_commands.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import logging
-import os
-import subprocess
-from dataclasses import dataclass, field
-from typing import List
-
-from lightning.app.utilities.exceptions import MisconfigurationException
-
-logger = logging.getLogger(__name__)
-
-# These are common lines at the top of python files which conflict with our
-# command syntax but which should not be executed. This is non-exhaustive,
-# and it may be better to just ignoring shebang lines if we see problems here.
-APP_COMMAND_LINES_TO_IGNORE = {
- "#!/usr/bin/python",
- "#!/usr/local/bin/python",
- "#!/usr/bin/env python",
- "#!/usr/bin/env python3",
-}
-
-
-@dataclass
-class CommandLines:
- file: str
- commands: List[str] = field(default_factory=list)
- line_numbers: List[int] = field(default_factory=list)
-
-
-def _extract_commands_from_file(file_name: str) -> CommandLines:
- """Extract all lines at the top of the file which contain commands to execute.
-
- The return struct contains a list of commands to execute with the corresponding line number the command executed on.
-
- """
- cl = CommandLines(
- file=file_name,
- )
- with open(file_name) as f:
- file_lines = f.readlines()
-
- for line_number, line in enumerate(file_lines):
- line = line.strip()
- if line in APP_COMMAND_LINES_TO_IGNORE:
- continue
-
- # stop parsing at first non-comment line at top of file
- if not line.startswith("#"):
- continue
-
- # remove comment marker and any leading / trailing whitespaces
- line = line.lstrip("#").strip()
- if len(line) == 0:
- # do not stop parsing on empty on comment lines
- continue
-
- # only run commands starting with a bang (!) & strip the bang from the executed command.
- if line[0] != "!":
- continue
- line = line[1:].strip()
-
- cl.commands.append(line)
- # add 1 to line number because enumerate returns indexes starting at 0, while
- # text exitors list lines beginning at index 1.
- cl.line_numbers.append(line_number + 1)
-
- return cl
-
-
-def _execute_app_commands(cl: CommandLines) -> None:
- """Open a subprocess shell to execute app commands.
-
- The calling app environment is used in the current environment the code is running in
-
- """
- for command, line_number in zip(cl.commands, cl.line_numbers):
- logger.info(f"Running app setup command: {command}")
- completed = subprocess.run(
- command,
- shell=True,
- env=os.environ,
- )
- try:
- completed.check_returncode()
- except subprocess.CalledProcessError:
- err_txt = (
- f"There was a problem on line {line_number} of {cl.file} while executing the command: "
- f"{command}. More information on the problem is shown in the output above this "
- f"message. After editing this line to fix the problem you can run the app again."
- )
- logger.error(err_txt)
- raise MisconfigurationException(err_txt) from None
-
-
-def run_app_commands(file: str) -> None:
- """Extract all lines at the top of the file which contain commands & execute them.
-
- Commands to execute are comment lines whose first non-whitespace character begins with the "bang" symbol (`!`).
- After the first non comment line we stop parsing the rest of the file. Running environment is preserved in the
- subprocess shell.
-
- For example:
-
- # some file <--- not a command # !echo "hello world" <--- a command # ! pip install foo <--- a command #
- foo! bar <--- not a command import lightning <--- not a command, end parsing.
-
- where `echo "hello world" && pip install foo` would be executed in the current running environment.
-
- """
- cl = _extract_commands_from_file(file_name=file)
- if len(cl.commands) == 0:
- logger.debug("No in app commands to install.")
- return
- _execute_app_commands(cl=cl)
diff --git a/src/lightning/app/utilities/app_helpers.py b/src/lightning/app/utilities/app_helpers.py
deleted file mode 100644
index 9efb173a001d4..0000000000000
--- a/src/lightning/app/utilities/app_helpers.py
+++ /dev/null
@@ -1,582 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import asyncio
-import builtins
-import enum
-import functools
-import inspect
-import json
-import logging
-import os
-import sys
-import threading
-import time
-from abc import ABC, abstractmethod
-from contextlib import contextmanager
-from copy import deepcopy
-from dataclasses import dataclass, field
-from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Mapping, Optional, Tuple, Type
-from unittest.mock import MagicMock
-
-import websockets
-from deepdiff import Delta
-
-import lightning.app
-from lightning.app.utilities.exceptions import LightningAppStateException
-from lightning.app.utilities.tree import breadth_first
-
-if TYPE_CHECKING:
- from lightning.app.core.app import LightningApp
- from lightning.app.core.flow import LightningFlow
- from lightning.app.utilities.types import Component
-
-logger = logging.getLogger(__name__)
-
-
-@dataclass
-class StateEntry:
- """Dataclass used to keep track the latest state shared through the app REST API."""
-
- app_state: Mapping = field(default_factory=dict)
- served_state: Mapping = field(default_factory=dict)
- session_id: Optional[str] = None
-
-
-class StateStore(ABC):
- """Base class of State store that provides simple key, value store to keep track of app state, served app state."""
-
- @abstractmethod
- def __init__(self):
- pass
-
- @abstractmethod
- def add(self, k: str):
- """Creates a new empty state with input key 'k'."""
- pass
-
- @abstractmethod
- def remove(self, k: str):
- """Deletes a state with input key 'k'."""
- pass
-
- @abstractmethod
- def get_app_state(self, k: str) -> Mapping:
- """Returns a stored appstate for an input key 'k'."""
- pass
-
- @abstractmethod
- def get_served_state(self, k: str) -> Mapping:
- """Returns a last served app state for an input key 'k'."""
- pass
-
- @abstractmethod
- def get_served_session_id(self, k: str) -> str:
- """Returns session id for state of a key 'k'."""
- pass
-
- @abstractmethod
- def set_app_state(self, k: str, v: Mapping):
- """Sets the app state for state of a key 'k'."""
- pass
-
- @abstractmethod
- def set_served_state(self, k: str, v: Mapping):
- """Sets the served state for state of a key 'k'."""
- pass
-
- @abstractmethod
- def set_served_session_id(self, k: str, v: str):
- """Sets the session id for state of a key 'k'."""
- pass
-
-
-class InMemoryStateStore(StateStore):
- """In memory simple store to keep track of state through the app REST API."""
-
- def __init__(self):
- self.store = {}
- self.counter = 0
-
- def add(self, k):
- self.store[k] = StateEntry()
-
- def remove(self, k):
- del self.store[k]
-
- def get_app_state(self, k):
- return self.store[k].app_state
-
- def get_served_state(self, k):
- return self.store[k].served_state
-
- def get_served_session_id(self, k):
- return self.store[k].session_id
-
- def set_app_state(self, k, v):
- state_size = sys.getsizeof(v)
- if state_size > lightning.app.core.constants.APP_STATE_MAX_SIZE_BYTES:
- raise LightningAppStateException(
- f"App state size is {state_size} bytes, which is larger than the recommended size "
- f"of {lightning.app.core.constants.APP_STATE_MAX_SIZE_BYTES}. Please investigate this."
- )
- self.store[k].app_state = deepcopy(v)
- self.counter += 1
-
- def set_served_state(self, k, v):
- self.store[k].served_state = deepcopy(v)
-
- def set_served_session_id(self, k, v):
- self.store[k].session_id = v
-
-
-class _LightningAppRef:
- _app_instance: Optional["LightningApp"] = None
-
- @classmethod
- def connect(cls, app_instance: "LightningApp") -> None:
- cls._app_instance = app_instance
-
- @classmethod
- def get_current(cls) -> Optional["LightningApp"]:
- if cls._app_instance:
- return cls._app_instance
- return None
-
-
-def affiliation(component: "Component") -> Tuple[str, ...]:
- """Returns the affiliation of a component."""
- if component.name in ("root", ""):
- return ()
- return tuple(component.name.split(".")[1:])
-
-
-class AppStateType(str, enum.Enum):
- STREAMLIT = "STREAMLIT"
- DEFAULT = "DEFAULT"
-
-
-class BaseStatePlugin(abc.ABC):
- def __init__(self):
- self.authorized = None
-
- @abc.abstractmethod
- def should_update_app(self, deep_diff):
- pass
-
- @abc.abstractmethod
- def get_context(self):
- pass
-
- @abc.abstractmethod
- def render_non_authorized(self):
- pass
-
-
-class AppStatePlugin(BaseStatePlugin):
- def should_update_app(self, deep_diff):
- return deep_diff
-
- def get_context(self):
- return {"type": AppStateType.DEFAULT.value}
-
- def render_non_authorized(self):
- pass
-
-
-def target_fn():
- try:
- # streamlit >= 1.14.0
- from streamlit import runtime
-
- get_instance = runtime.get_instance
- exists = runtime.exists()
- except ImportError:
- # Older versions
- from streamlit.server.server import Server
-
- get_instance = Server.get_current
- exists = bool(Server._singleton)
-
- async def update_fn():
- runtime_instance = get_instance()
- sessions = list(runtime_instance._session_info_by_id.values())
- url = (
- "localhost:8080"
- if "LIGHTNING_APP_STATE_URL" in os.environ
- else f"localhost:{lightning.app.core.constants.APP_SERVER_PORT}"
- )
- ws_url = f"ws://{url}/api/v1/ws"
- last_updated = time.time()
- async with websockets.connect(ws_url) as websocket:
- while True:
- try:
- _ = await websocket.recv()
-
- while (time.time() - last_updated) < 1:
- time.sleep(0.1)
- for session in sessions:
- session = session.session
- session.request_rerun(session._client_state)
- last_updated = time.time()
- except websockets.exceptions.ConnectionClosedOK:
- # The websocket is not enabled
- break
-
- if exists:
- asyncio.run(update_fn())
-
-
-class StreamLitStatePlugin(BaseStatePlugin):
- def __init__(self):
- super().__init__()
- import streamlit as st
-
- if hasattr(st, "session_state") and "websocket_thread" not in st.session_state:
- thread = threading.Thread(target=target_fn)
- st.session_state.websocket_thread = thread
- thread.setDaemon(True)
- thread.start()
-
- def should_update_app(self, deep_diff):
- return deep_diff
-
- def get_context(self):
- return {"type": AppStateType.DEFAULT.value}
-
- def render_non_authorized(self):
- pass
-
-
-def is_overridden(method_name: str, instance: Optional[object] = None, parent: Optional[Type[object]] = None) -> bool:
- if instance is None:
- return False
- if parent is None:
- if isinstance(instance, lightning.app.LightningFlow):
- parent = lightning.app.LightningFlow
- elif isinstance(instance, lightning.app.LightningWork):
- parent = lightning.app.LightningWork
- if parent is None:
- raise ValueError("Expected a parent")
- from lightning_utilities.core.overrides import is_overridden
-
- return is_overridden(method_name, instance, parent)
-
-
-def _is_json_serializable(x: Any) -> bool:
- """Test whether a variable can be encoded as json."""
- if type(x) in lightning.app.core.constants.SUPPORTED_PRIMITIVE_TYPES:
- # shortcut for primitive types that are not containers
- return True
- try:
- json.dumps(x, cls=LightningJSONEncoder)
- return True
- except (TypeError, OverflowError):
- # OverflowError is raised if number is too large to encode
- return False
-
-
-def _set_child_name(component: "Component", child: "Component", new_name: str) -> str:
- """Computes and sets the name of a child given the parent, and returns the name."""
- child_name = f"{component.name}.{new_name}"
- child._name = child_name
-
- # the name changed, so recursively update the names of the children of this child
- if isinstance(child, lightning.app.core.LightningFlow):
- for n in child._flows:
- c = getattr(child, n)
- _set_child_name(child, c, n)
- for n in child._works:
- c = getattr(child, n)
- _set_child_name(child, c, n)
- for n in child._structures:
- s = getattr(child, n)
- _set_child_name(child, s, n)
- if isinstance(child, lightning.app.structures.Dict):
- for n, c in child.items():
- _set_child_name(child, c, n)
- if isinstance(child, lightning.app.structures.List):
- for c in child:
- _set_child_name(child, c, c.name.split(".")[-1])
-
- return child_name
-
-
-def _delta_to_app_state_delta(root: "LightningFlow", component: "Component", delta: Delta) -> Delta:
- delta_dict = delta.to_dict()
- for changed in delta_dict.values():
- for delta_key in changed.copy():
- val = changed[delta_key]
-
- new_prefix = "root"
- for p, c in _walk_to_component(root, component):
- if isinstance(c, lightning.app.core.LightningWork):
- new_prefix += "['works']"
-
- if isinstance(c, lightning.app.core.LightningFlow):
- new_prefix += "['flows']"
-
- if isinstance(c, (lightning.app.structures.Dict, lightning.app.structures.List)):
- new_prefix += "['structures']"
-
- c_n = c.name.split(".")[-1]
- new_prefix += f"['{c_n}']"
-
- delta_key_without_root = delta_key[4:] # the first 4 chars are the word 'root', strip it
- new_key = new_prefix + delta_key_without_root
- if new_key != delta_key:
- changed[new_key] = val
- del changed[delta_key]
-
- return Delta(delta_dict)
-
-
-def _walk_to_component(
- root: "LightningFlow",
- component: "Component",
-) -> Generator[Tuple["Component", "Component"], None, None]:
- """Returns a generator that runs through the tree starting from the root down to the given component.
-
- At each node, yields parent and child as a tuple.
-
- """
- from lightning.app.structures import Dict, List
-
- name_parts = component.name.split(".")[1:] # exclude 'root' from the name
- parent = root
- for n in name_parts:
- if isinstance(parent, (Dict, List)):
- child = parent[n] if isinstance(parent, Dict) else parent[int(n)]
- else:
- child = getattr(parent, n)
- yield parent, child
- parent = child
-
-
-def _collect_child_process_pids(pid: int) -> List[int]:
- """Function to return the list of child process pid's of a process."""
- processes = os.popen("ps -ej | grep -i 'python' | grep -v 'grep' | awk '{ print $2,$3 }'").read()
- processes = [p.split(" ") for p in processes.split("\n")[:-1]]
- return [int(child) for child, parent in processes if parent == str(pid) and child != str(pid)]
-
-
-def _print_to_logger_info(*args: Any, **kwargs: Any):
- # TODO Find a better way to re-direct print to loggers.
- lightning.app._logger.info(" ".join([str(v) for v in args]))
-
-
-def convert_print_to_logger_info(func: Callable) -> Callable:
- """This function is used to transform any print into logger.info calls, so it gets tracked in the cloud."""
-
- @functools.wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> Any:
- original_print = __builtins__["print"]
- __builtins__["print"] = _print_to_logger_info
- res = func(*args, **kwargs)
- __builtins__["print"] = original_print
- return res
-
- return wrapper
-
-
-def pretty_state(state: Dict) -> Dict:
- """Utility to prettify the state by removing hidden attributes."""
- new_state = {}
- for k, v in state["vars"].items():
- if not k.startswith("_"):
- if "vars" not in new_state:
- new_state["vars"] = {}
- new_state["vars"][k] = v
- if "flows" in state:
- for k, v in state["flows"].items():
- if "flows" not in new_state:
- new_state["flows"] = {}
- new_state["flows"][k] = pretty_state(state["flows"][k])
- if "works" in state:
- for k, v in state["works"].items():
- if "works" not in new_state:
- new_state["works"] = {}
- new_state["works"][k] = pretty_state(state["works"][k])
- return new_state
-
-
-class LightningJSONEncoder(json.JSONEncoder):
- def default(self, obj: Any) -> Any:
- if callable(getattr(obj, "__json__", None)):
- return obj.__json__()
- return json.JSONEncoder.default(self, obj)
-
-
-class Logger:
- """This class is used to improve the debugging experience."""
-
- def __init__(self, name: str):
- self.logger = logging.getLogger(name)
- self.level = None
-
- def info(self, msg, *args: Any, **kwargs: Any):
- self.logger.info(msg, *args, **kwargs)
-
- def warn(self, msg, *args: Any, **kwargs: Any):
- self._set_level()
- self.logger.warn(msg, *args, **kwargs)
-
- def debug(self, msg, *args: Any, **kwargs: Any):
- self._set_level()
- self.logger.debug(msg, *args, **kwargs)
-
- def error(self, msg, *args: Any, **kwargs: Any):
- self._set_level()
- self.logger.error(msg, *args, **kwargs)
-
- def _set_level(self):
- """Lazily set the level once set by the users."""
- # Set on the first from either log, warn, debug or error call.
- if self.level is None:
- self.level = logging.DEBUG if bool(int(os.getenv("LIGHTNING_DEBUG", "0"))) else logging.INFO
- self.logger.setLevel(self.level)
-
-
-def _state_dict(flow: "LightningFlow"):
- state = {}
- flows = [flow] + list(flow.flows.values())
- for f in flows:
- state[f.name] = f.state_dict()
- for w in flow.works():
- state[w.name] = w.state
- return state
-
-
-def _load_state_dict(root_flow: "LightningFlow", state: Dict[str, Any], strict: bool = True) -> None:
- """This function is used to reload the state assuming dynamic components creation.
-
- When a component isn't found but its state exists, its state is passed up to its closest existing parent.
-
- Arguments:
- root_flow: The flow at the top of the component tree.
- state: The collected state dict.
- strict: Whether to validate all components have been re-created.
-
- """
- # 1: Reload the state of the existing works
- for w in root_flow.works():
- w.set_state(state.pop(w.name))
-
- # 2: Collect the existing flows
- flows = [root_flow] + list(root_flow.flows.values())
- flow_map = {f.name: f for f in flows}
-
- # 3: Find the state of the all dynamic components
- dynamic_components = {k: v for k, v in state.items() if k not in flow_map}
-
- # 4: Propagate the state of the dynamic components to their closest parents
- dynamic_children_state = {}
- for name, component_state in dynamic_components.items():
- affiliation = name.split(".")
- for idx in range(0, len(affiliation)):
- parent_name = ".".join(affiliation[:-idx])
- has_matched = False
- for flow_name, flow in flow_map.items():
- if flow_name == parent_name:
- if flow_name not in dynamic_children_state:
- dynamic_children_state[flow_name] = {}
-
- dynamic_children_state[flow_name].update({name.replace(parent_name + ".", ""): component_state})
- has_matched = True
- break
- if has_matched:
- break
-
- # 5: Reload the flow states
- for flow_name, flow in flow_map.items():
- flow.load_state_dict(state.pop(flow_name), dynamic_children_state.get(flow_name, {}), strict=strict)
-
- # 6: Verify all dynamic components has been re-created.
- if strict:
- components_names = (
- [root_flow.name] + [f.name for f in root_flow.flows.values()] + [w.name for w in root_flow.works()]
- )
- for component_name in dynamic_components:
- if component_name not in components_names:
- raise Exception(f"The component {component_name} was re-created during state reloading.")
-
-
-class _MagicMockJsonSerializable(MagicMock):
- @staticmethod
- def __json__():
- return "{}"
-
-
-def _mock_import(*args, original_fn=None):
- try:
- return original_fn(*args)
- except Exception:
- return _MagicMockJsonSerializable()
-
-
-@contextmanager
-def _mock_missing_imports():
- original_fn = builtins.__import__
- builtins.__import__ = functools.partial(_mock_import, original_fn=original_fn)
- try:
- yield
- finally:
- builtins.__import__ = original_fn
-
-
-def is_static_method(klass_or_instance, attr) -> bool:
- return isinstance(inspect.getattr_static(klass_or_instance, attr), staticmethod)
-
-
-def _lightning_dispatched() -> bool:
- return bool(int(os.getenv("LIGHTNING_DISPATCHED", 0)))
-
-
-def _using_debugger() -> bool:
- """This method is used to detect whether the app is run with a debugger attached."""
- if "LIGHTNING_DETECTED_DEBUGGER" in os.environ:
- return True
-
- # Collect the information about the process.
- parent_process = os.popen(f"ps -ax | grep -i {os.getpid()} | grep -v grep").read()
-
- # Detect whether VSCode or PyCharm debugger are used
- use_debugger = "debugpy" in parent_process or "pydev" in parent_process
-
- # Store the result to avoid multiple popen calls.
- if use_debugger:
- os.environ["LIGHTNING_DETECTED_DEBUGGER"] = "1"
- return use_debugger
-
-
-def _should_dispatch_app() -> bool:
- return (
- not _lightning_dispatched()
- and "LIGHTNING_APP_STATE_URL" not in os.environ
- # Keep last to avoid running it if already dispatched
- and _using_debugger()
- )
-
-
-def _is_headless(app: "LightningApp") -> bool:
- """Utility which returns True if the given App has no ``Frontend`` objects or URLs exposed through
- ``configure_layout``."""
- if app.frontends:
- return False
- for component in breadth_first(app.root, types=(lightning.app.LightningFlow,)):
- for entry in component._layout:
- if "target" in entry:
- return False
- return True
diff --git a/src/lightning/app/utilities/app_logs.py b/src/lightning/app/utilities/app_logs.py
deleted file mode 100644
index 446418f9b18e2..0000000000000
--- a/src/lightning/app/utilities/app_logs.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import json
-import queue
-from dataclasses import dataclass
-from threading import Thread
-from typing import Callable, Iterator, List, Optional
-
-import dateutil.parser
-from websocket import WebSocketApp
-
-from lightning.app.utilities.log_helpers import _error_callback, _OrderedLogEntry
-from lightning.app.utilities.logs_socket_api import _LightningLogsSocketAPI
-
-
-@dataclass
-class _LogEventLabels:
- app: Optional[str] = None
- container: Optional[str] = None
- filename: Optional[str] = None
- job: Optional[str] = None
- namespace: Optional[str] = None
- node_name: Optional[str] = None
- pod: Optional[str] = None
- component: Optional[str] = None
- projectID: Optional[str] = None
- stream: Optional[str] = None
-
-
-@dataclass
-class _LogEvent(_OrderedLogEntry):
- component_name: str
- labels: _LogEventLabels
-
-
-def _push_log_events_to_read_queue_callback(component_name: str, read_queue: queue.PriorityQueue):
- """Pushes _LogEvents from websocket to read_queue.
-
- Returns callback function used with `on_message_callback` of websocket.WebSocketApp.
-
- """
-
- def callback(ws_app: WebSocketApp, msg: str):
- # We strongly trust that the contract on API will hold atm :D
- event_dict = json.loads(msg)
- labels = _LogEventLabels(**event_dict.get("labels", {}))
-
- if "message" in event_dict:
- message = event_dict["message"]
- timestamp = dateutil.parser.isoparse(event_dict["timestamp"])
- event = _LogEvent(
- message=message,
- timestamp=timestamp,
- component_name=component_name,
- labels=labels,
- )
- read_queue.put(event)
-
- return callback
-
-
-def _app_logs_reader(
- logs_api_client: _LightningLogsSocketAPI,
- project_id: str,
- app_id: str,
- component_names: List[str],
- follow: bool,
- on_error_callback: Optional[Callable] = None,
-) -> Iterator[_LogEvent]:
- read_queue = queue.PriorityQueue()
-
- # We will use a socket per component
- log_sockets = [
- logs_api_client.create_lightning_logs_socket(
- project_id=project_id,
- app_id=app_id,
- component=component_name,
- on_message_callback=_push_log_events_to_read_queue_callback(component_name, read_queue),
- on_error_callback=on_error_callback or _error_callback,
- )
- for component_name in component_names
- ]
-
- # And each socket on separate thread pushing log event to print queue
- # run_forever() will run until we close() the connection from outside
- log_threads = [Thread(target=work.run_forever, daemon=True) for work in log_sockets]
-
- # Establish connection and begin pushing logs to the print queue
- for th in log_threads:
- th.start()
-
- # Print logs from queue when log event is available
- flow = "Your app has started."
- work = "USER_RUN_WORK"
- start_timestamps = {}
-
- # Print logs from queue when log event is available
- try:
- while True:
- log_event: _LogEvent = read_queue.get(timeout=None if follow else 1.0)
-
- token = flow if log_event.component_name == "flow" else work
- if token in log_event.message:
- start_timestamps[log_event.component_name] = log_event.timestamp
-
- timestamp = start_timestamps.get(log_event.component_name, None)
- if timestamp and log_event.timestamp >= timestamp and "launcher" not in log_event.message:
- yield log_event
-
- except queue.Empty:
- # Empty is raised by queue.get if timeout is reached. Follow = False case.
- pass
-
- except KeyboardInterrupt:
- # User pressed CTRL+C to exit, we should respect that
- pass
-
- finally:
- # Close connections - it will cause run_forever() to finish -> thread as finishes aswell
- for socket in log_sockets:
- socket.close()
-
- # Because all socket were closed, we can just wait for threads to finish.
- for th in log_threads:
- th.join()
diff --git a/src/lightning/app/utilities/app_status.py b/src/lightning/app/utilities/app_status.py
deleted file mode 100644
index 1f40da05bc140..0000000000000
--- a/src/lightning/app/utilities/app_status.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from datetime import datetime
-from typing import Any, Dict, Optional
-
-from pydantic import BaseModel
-
-
-class WorkStatus(BaseModel):
- """The ``WorkStatus`` captures the status of a work according to the app."""
-
- stage: str
- timestamp: float
- reason: Optional[str] = None
- message: Optional[str] = None
- count: int = 1
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- super().__init__(*args, **kwargs)
-
- assert self.timestamp > 0
- assert self.timestamp < (int(datetime.now().timestamp()) + 10)
-
-
-class AppStatus(BaseModel):
- """The ``AppStatus`` captures the current status of the app and its components."""
-
- # ``True`` when the app UI is ready to be viewed
- is_ui_ready: bool
-
- # The statuses of ``LightningWork`` objects currently associated with this app
- work_statuses: Dict[str, WorkStatus]
diff --git a/src/lightning/app/utilities/auth.py b/src/lightning/app/utilities/auth.py
deleted file mode 100644
index 2ccb2d068109f..0000000000000
--- a/src/lightning/app/utilities/auth.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Dict
-
-from lightning_cloud.openapi import ApiClient, AuthServiceApi, V1LoginRequest
-
-from lightning.app.utilities.login import Auth
-
-
-# This class joins common things for reading logs,
-# initialization and getting API token
-class _AuthTokenGetter:
- def __init__(self, api_client: ApiClient):
- self.api_client = api_client
- self._auth = Auth()
- self._auth.authenticate()
- self._auth_service = AuthServiceApi(api_client)
-
- def _get_api_token(self) -> str:
- token_resp = self._auth_service.auth_service_login(
- body=V1LoginRequest(
- username=self._auth.username,
- api_key=self._auth.api_key,
- )
- )
- return token_resp.token
-
-
-def _credential_string_to_basic_auth_params(credential_string: str) -> Dict[str, str]:
- """Returns the name/ID pair for each given Secret name.
-
- Raises a `ValueError` if any of the given Secret names do not exist.
-
- """
- if credential_string.count(":") != 1:
- raise ValueError(
- "Credential string must follow the format username:password; "
- + f"the provided one ('{credential_string}') does not."
- )
-
- username, password = credential_string.split(":")
-
- if not username:
- raise ValueError("Username cannot be empty.")
-
- if not password:
- raise ValueError("Password cannot be empty.")
-
- return {"username": username, "password": password}
diff --git a/src/lightning/app/utilities/cli_helpers.py b/src/lightning/app/utilities/cli_helpers.py
deleted file mode 100644
index 45428e44c7310..0000000000000
--- a/src/lightning/app/utilities/cli_helpers.py
+++ /dev/null
@@ -1,358 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import contextlib
-import functools
-import json
-import os
-import re
-import subprocess
-import sys
-from typing import Dict, Optional
-
-import arrow
-import click
-import packaging
-import requests
-import rich
-from lightning_cloud.openapi import Externalv1LightningappInstance
-
-from lightning.app import __package_name__, __version__
-from lightning.app.core.constants import APP_SERVER_PORT
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.network import LightningClient
-
-logger = Logger(__name__)
-
-
-def _format_input_env_variables(env_list: tuple) -> Dict[str, str]:
- """
- Args:
- env_list:
- List of str for the env variables, e.g. ['foo=bar', 'bla=bloz']
-
- Returns:
- Dict of the env variables with the following format
- key: env variable name
- value: env variable value
- """
- env_vars_dict = {}
- for env_str in env_list:
- var_parts = env_str.split("=")
- if len(var_parts) != 2 or not var_parts[0]:
- raise Exception(
- f"Invalid format of environment variable {env_str}, "
- f"please ensure that the variable is in the format e.g. foo=bar."
- )
- var_name, value = var_parts
-
- if var_name in env_vars_dict:
- raise Exception(f"Environment variable '{var_name}' is duplicated. Please only include it once.")
-
- if not re.match(r"[0-9a-zA-Z_]+", var_name):
- raise ValueError(
- f"Environment variable '{var_name}' is not a valid name. It is only allowed to contain digits 0-9, "
- f"letters A-Z, a-z and _ (underscore)."
- )
-
- env_vars_dict[var_name] = value
- return env_vars_dict
-
-
-def _is_url(id: Optional[str]) -> bool:
- if isinstance(id, str) and (id.startswith("https://") or id.startswith("http://")):
- return True
- return False
-
-
-def _get_metadata_from_openapi(paths: Dict, path: str):
- parameters = paths[path]["post"].get("parameters", {})
- tag = paths[path]["post"].get("tags", [None])[0]
- cls_path = paths[path]["post"].get("cls_path", None)
- cls_name = paths[path]["post"].get("cls_name", None)
- description = paths[path]["post"].get("description", None)
- requirements = paths[path]["post"].get("requirements", None)
- app_info = paths[path]["post"].get("app_info", None)
-
- metadata = {"tag": tag, "parameters": {}}
-
- if cls_path:
- metadata["cls_path"] = cls_path
-
- if cls_name:
- metadata["cls_name"] = cls_name
-
- if description:
- metadata["description"] = description
-
- if description:
- metadata["requirements"] = requirements
-
- if app_info:
- metadata["app_info"] = app_info
-
- if not parameters:
- return metadata
-
- metadata["parameters"].update({d["name"]: d["schema"]["type"] for d in parameters})
- return metadata
-
-
-def _extract_command_from_openapi(openapi_resp: Dict) -> Dict[str, Dict[str, str]]:
- command_paths = [p for p in openapi_resp["paths"] if p.startswith("/command/")]
- return {p.replace("/command/", ""): _get_metadata_from_openapi(openapi_resp["paths"], p) for p in command_paths}
-
-
-def _get_app_display_name(app: Externalv1LightningappInstance) -> str:
- return getattr(app, "display_name", None) or app.name
-
-
-class _LightningAppOpenAPIRetriever:
- def __init__(
- self,
- app_id_or_name_or_url: Optional[str],
- use_cache: bool = False,
- ):
- """This class encapsulates the logic to collect the openapi.json file from the app to use the CLI Commands.
-
- Arguments:
- app_id_or_name_or_url: An identified for the app.
- use_cache: Whether to load the openapi spec from the cache.
-
- """
- self.app_id_or_name_or_url = app_id_or_name_or_url
- self.url = None
- self.openapi = None
- self.api_commands = None
- self.app_id = None
- self.app_name = None
- home = os.path.expanduser("~")
- if use_cache:
- cache_openapi = os.path.join(home, ".lightning", "lightning_connection", "commands", "openapi.json")
- if os.path.exists(cache_openapi):
- with open(cache_openapi) as f:
- self.openapi = json.load(f)
- self.api_commands = _extract_command_from_openapi(self.openapi)
-
- if not self.api_commands:
- self._collect_open_api_json()
- if self.openapi:
- self.api_commands = _extract_command_from_openapi(self.openapi)
-
- def is_alive(self) -> bool:
- """Returns whether the Lightning App Rest API is available."""
- if self.url is None:
- self._maybe_find_url()
- if self.url is None:
- return False
- resp = requests.get(self.url)
- return resp.status_code == 200
-
- def _maybe_find_url(self):
- """Tries to resolve the app url from the provided `app_id_or_name_or_url`."""
- if _is_url(self.app_id_or_name_or_url):
- self.url = self.app_id_or_name_or_url
- assert self.url
- return
-
- if self.app_id_or_name_or_url is None:
- url = f"http://localhost:{APP_SERVER_PORT}"
- resp = requests.get(f"{self.url}/openapi.json")
- if resp.status_code == 200:
- self.url = url
- return
-
- app = self._maybe_find_matching_cloud_app()
- if app:
- self.url = app.status.url
-
- def _maybe_find_matching_cloud_app(self):
- """Tries to resolve the app url from the provided `app_id_or_name_or_url`."""
- client = LightningClient(retry=False)
- project = _get_project(client)
- list_apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project.project_id)
-
- app_names = [_get_app_display_name(lit_app) for lit_app in list_apps.lightningapps]
-
- if not self.app_id_or_name_or_url:
- print(f"ERROR: Provide an application name, id or url with --app_id=X. Found {app_names}")
- sys.exit(0)
-
- for app in list_apps.lightningapps:
- if app.id == self.app_id_or_name_or_url or _get_app_display_name(app) == self.app_id_or_name_or_url:
- if app.status.url == "":
- print("The application is starting. Try in a few moments.")
- sys.exit(0)
- return app
- return None
-
- def _collect_open_api_json(self):
- """This function is used to retrieve the current url associated with an id."""
- if _is_url(self.app_id_or_name_or_url):
- self.url = self.app_id_or_name_or_url
- assert self.url
- resp = requests.get(self.url + "/openapi.json")
- if resp.status_code != 200:
- print(f"ERROR: The server didn't process the request properly. Found {resp.json()}")
- sys.exit(0)
- self.openapi = resp.json()
- return
-
- # 2: If no identifier has been provided, evaluate the local application
- if self.app_id_or_name_or_url is None:
- with contextlib.suppress(requests.exceptions.ConnectionError):
- self.url = f"http://localhost:{APP_SERVER_PORT}"
- resp = requests.get(f"{self.url}/openapi.json")
- if resp.status_code != 200:
- raise Exception(f"The server didn't process the request properly. Found {resp.json()}")
- self.openapi = resp.json()
-
- # 3: If an identified was provided or the local evaluation has failed, evaluate the cloud.
- else:
- app = self._maybe_find_matching_cloud_app()
- if app:
- if app.status.url == "":
- raise Exception("The application is starting. Try in a few moments.")
- resp = requests.get(app.status.url + "/openapi.json")
- if resp.status_code != 200:
- raise Exception(
- "The server didn't process the request properly. " "Try once your application is ready."
- )
- self.url = app.status.url
- self.openapi = resp.json()
- self.app_id = app.id
- self.app_name = _get_app_display_name(app)
-
-
-def _arrow_time_callback(
- _ctx: "click.core.Context", _param: "click.core.Option", value: str, arw_now=arrow.utcnow()
-) -> arrow.Arrow:
- try:
- return arw_now.dehumanize(value)
- except ValueError:
- try:
- return arrow.get(value)
- except (ValueError, TypeError):
- raise click.ClickException(f"cannot parse time {value}")
-
-
-@functools.lru_cache(maxsize=1)
-def _get_newer_version() -> Optional[str]:
- """Check PyPI for newer versions of ``lightning``, returning the newest version if different from the current or
- ``None`` otherwise."""
- if packaging.version.parse(__version__).is_prerelease:
- return None
- try:
- response = requests.get(f"https://pypi.org/pypi/{__package_name__}/json")
- response_json = response.json()
- releases = response_json["releases"]
- if __version__ not in releases:
- # Always return None if not installed from PyPI (e.g. dev versions)
- return None
- latest_version = response_json["info"]["version"]
- parsed_version = packaging.version.parse(latest_version)
- is_invalid = response_json["info"]["yanked"] or parsed_version.is_devrelease or parsed_version.is_prerelease
- return None if __version__ == latest_version or is_invalid else latest_version
- except Exception:
- # Return None if any exception occurs
- return None
-
-
-def _redirect_command(executable: str):
- """Redirect the current lightning CLI call to the given executable."""
- subprocess.run(
- [executable, "-m", "lightning"] + sys.argv[1:],
- env=os.environ,
- )
-
- sys.exit()
-
-
-def _check_version_and_upgrade():
- """Checks that the current version of ``lightning`` is the latest on PyPI.
-
- If not, prompt the user to upgrade ``lightning`` for them and re-run the current call in the new version.
-
- """
- new_version = _get_newer_version()
- if new_version:
- prompt = f"A newer version of {__package_name__} is available ({new_version}). Would you like to upgrade?"
-
- if click.confirm(prompt, default=True):
- command = f"pip install {__package_name__}=={new_version}"
-
- logger.info(f"⚡ RUN: {command}")
-
- # Upgrade
- subprocess.run(
- [sys.executable, "-m"] + command.split(" "),
- check=True,
- )
-
- # Re-launch
- _redirect_command(sys.executable)
- return
-
-
-def _check_environment_and_redirect():
- """Checks that the current ``sys.executable`` is the same as the executable resolved from the current environment.
-
- If not, this utility tries to redirect the ``lightning`` call to the environment executable (prompting the user to
- install lightning for them there if needed).
-
- """
- process = subprocess.run(
- ["python", "-c", "import sys; print(sys.executable)"],
- capture_output=True,
- env=os.environ,
- check=True,
- )
-
- env_executable = os.path.realpath(process.stdout.decode().strip())
- sys_executable = os.path.realpath(sys.executable)
-
- # on windows, the extension might be different, where one uses `.EXE` and the other `.exe`
- if env_executable.lower() != sys_executable.lower():
- logger.info(
- "Lightning is running from outside your current environment. Switching to your current environment."
- )
-
- process = subprocess.run(
- [env_executable, "-m", "lightning", "--version"],
- capture_output=True,
- text=True,
- )
-
- if "No module named lightning" in process.stderr:
- prompt = f"The {__package_name__} package is not installed. Would you like to install it? [Y/n (exit)]"
-
- if click.confirm(prompt, default=True, show_default=False):
- command = f"pip install {__package_name__}"
-
- logger.info(f"⚡ RUN: {command}")
-
- subprocess.run(
- [env_executable, "-m"] + command.split(" "),
- check=True,
- )
- else:
- sys.exit()
-
- _redirect_command(env_executable)
- return
-
-
-def _error_and_exit(msg: str) -> None:
- rich.print(f"[red]ERROR[/red]: {msg}")
- sys.exit(0)
diff --git a/src/lightning/app/utilities/cloud.py b/src/lightning/app/utilities/cloud.py
deleted file mode 100644
index 95c76c1be926a..0000000000000
--- a/src/lightning/app/utilities/cloud.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from typing import Optional
-
-from lightning_cloud.openapi import V1Membership
-
-import lightning.app
-from lightning.app.core.constants import LIGHTNING_CLOUD_PROJECT_ID
-from lightning.app.utilities.enum import AppStage
-from lightning.app.utilities.network import LightningClient
-
-
-def _get_project(client: LightningClient, project_id: Optional[str] = None, verbose: bool = True) -> V1Membership:
- """Get a project membership for the user from the backend."""
- if project_id is None:
- project_id = LIGHTNING_CLOUD_PROJECT_ID
-
- if project_id is not None:
- project = client.projects_service_get_project(project_id)
- if not project:
- raise ValueError(
- "Environment variable `LIGHTNING_CLOUD_PROJECT_ID` is set but could not find an associated project."
- )
- return V1Membership(
- name=project.name,
- display_name=project.display_name,
- description=project.description,
- created_at=project.created_at,
- project_id=project.id,
- owner_id=project.owner_id,
- owner_type=project.owner_type,
- quotas=project.quotas,
- updated_at=project.updated_at,
- )
-
- projects = client.projects_service_list_memberships()
- if len(projects.memberships) == 0:
- raise ValueError("No valid projects found. Please reach out to lightning.ai team to create a project")
- if len(projects.memberships) > 1 and verbose:
- print(f"Defaulting to the project: {projects.memberships[0].name}")
- return projects.memberships[0]
-
-
-def _sigterm_flow_handler(*_, app: "lightning.app.LightningApp"):
- app.stage = AppStage.STOPPING
-
-
-def is_running_in_cloud() -> bool:
- """Returns True if the Lightning App is running in the cloud."""
- return bool(int(os.environ.get("LAI_RUNNING_IN_CLOUD", "0"))) or "LIGHTNING_APP_STATE_URL" in os.environ
diff --git a/src/lightning/app/utilities/clusters.py b/src/lightning/app/utilities/clusters.py
deleted file mode 100644
index 663ba66d456e9..0000000000000
--- a/src/lightning/app/utilities/clusters.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import random
-
-from lightning_cloud.openapi import ProjectIdProjectclustersbindingsBody, V1ClusterType
-from lightning_cloud.openapi.rest import ApiException
-
-from lightning.app.utilities.network import LightningClient
-
-
-def _ensure_cluster_project_binding(client: LightningClient, project_id: str, cluster_id: str) -> None:
- cluster_bindings = client.projects_service_list_project_cluster_bindings(project_id=project_id)
-
- for cluster_binding in cluster_bindings.clusters:
- if cluster_binding.cluster_id != cluster_id:
- continue
- if cluster_binding.project_id == project_id:
- return
-
- client.projects_service_create_project_cluster_binding(
- project_id=project_id,
- body=ProjectIdProjectclustersbindingsBody(cluster_id=cluster_id),
- )
-
-
-def _get_default_cluster(client: LightningClient, project_id: str) -> str:
- """This utility implements a minimal version of the cluster selection logic used in the cloud.
-
- TODO: This should be requested directly from the platform.
-
- """
- cluster_bindings = client.projects_service_list_project_cluster_bindings(project_id=project_id).clusters
-
- if not cluster_bindings:
- raise ValueError(f"No clusters are bound to the project {project_id}.")
-
- if len(cluster_bindings) == 1:
- return cluster_bindings[0].cluster_id
-
- clusters = []
- for cluster_binding in cluster_bindings:
- try:
- clusters.append(client.cluster_service_get_cluster(cluster_binding.cluster_id))
- except ApiException:
- # If we failed to get the cluster, ignore it
- continue
-
- # Filter global clusters
- clusters = [cluster for cluster in clusters if cluster.spec.cluster_type == V1ClusterType.GLOBAL]
-
- if len(clusters) == 0:
- raise RuntimeError(f"No clusters found on `{client.api_client.configuration.host}`.")
-
- return random.choice(clusters).id # noqa: S311
diff --git a/src/lightning/app/utilities/commands/__init__.py b/src/lightning/app/utilities/commands/__init__.py
deleted file mode 100644
index 14e842687e43a..0000000000000
--- a/src/lightning/app/utilities/commands/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from lightning.app.utilities.commands.base import ClientCommand
-
-__all__ = ["ClientCommand"]
diff --git a/src/lightning/app/utilities/commands/base.py b/src/lightning/app/utilities/commands/base.py
deleted file mode 100644
index ca09a53318f74..0000000000000
--- a/src/lightning/app/utilities/commands/base.py
+++ /dev/null
@@ -1,308 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import errno
-import inspect
-import os
-import os.path as osp
-import shutil
-import sys
-import traceback
-from dataclasses import asdict
-from getpass import getuser
-from importlib.util import module_from_spec, spec_from_file_location
-from tempfile import gettempdir
-from typing import Any, Callable, Dict, List, Optional, Union
-
-import requests
-from fastapi import HTTPException
-from pydantic import BaseModel
-
-from lightning.app.api.http_methods import Post
-from lightning.app.api.request_types import _APIRequest, _CommandRequest, _RequestResponse
-from lightning.app.utilities import frontend
-from lightning.app.utilities.app_helpers import Logger, is_overridden
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.network import LightningClient
-from lightning.app.utilities.state import AppState
-
-logger = Logger(__name__)
-
-
-def makedirs(path: str):
- r"""Recursive directory creation function."""
- try:
- os.makedirs(osp.expanduser(osp.normpath(path)))
- except OSError as ex:
- if ex.errno != errno.EEXIST and osp.isdir(path):
- raise ex
-
-
-class ClientCommand:
- description: str = ""
- requirements: List[str] = []
-
- def __init__(self, method: Callable):
- self.method = method
- if not self.description:
- self.description = self.method.__doc__ or ""
- flow = getattr(self.method, "__self__", None)
- self.owner = flow.name if flow else None
- self.models: Optional[Dict[str, BaseModel]] = None
- self.app_url = None
- self._state = None
-
- def _setup(self, command_name: str, app_url: str) -> None:
- self.command_name = command_name
- self.app_url = app_url
-
- @property
- def state(self):
- if self._state is None:
- assert self.app_url
- # TODO: Resolve this hack
- os.environ["LIGHTNING_APP_STATE_URL"] = "1"
- self._state = AppState(host=self.app_url)
- self._state._request_state()
- os.environ.pop("LIGHTNING_APP_STATE_URL")
- return self._state
-
- def run(self, **cli_kwargs) -> None:
- """Overrides with the logic to execute on the client side."""
-
- def invoke_handler(self, config: Optional[BaseModel] = None) -> Dict[str, Any]:
- command = self.command_name.replace(" ", "_")
- resp = requests.post(self.app_url + f"/command/{command}", data=config.json() if config else None)
- if resp.status_code != 200:
- try:
- detail = str(resp.json())
- except Exception:
- detail = "Internal Server Error"
- print(f"Failed with status code {resp.status_code}. Detail: {detail}")
- sys.exit(0)
-
- return resp.json()
-
- def _to_dict(self):
- return {"owner": self.owner, "requirements": self.requirements}
-
- def __call__(self, **kwargs: Any):
- return self.method(**kwargs)
-
-
-def _download_command(
- command_name: str,
- cls_path: str,
- cls_name: str,
- app_id: Optional[str] = None,
- debug_mode: bool = False,
- target_file: Optional[str] = None,
-) -> ClientCommand:
- # TODO: This is a skateboard implementation and the final version will rely on versioned
- # immutable commands for security concerns
- command_name = command_name.replace(" ", "_")
- tmpdir = None
- if not target_file:
- tmpdir = osp.join(gettempdir(), f"{getuser()}_commands")
- makedirs(tmpdir)
- target_file = osp.join(tmpdir, f"{command_name}.py")
-
- if not debug_mode:
- if app_id:
- if not os.path.exists(target_file):
- client = LightningClient(retry=False)
- project_id = _get_project(client).project_id
- response = client.lightningapp_instance_service_list_lightningapp_instance_artifacts(
- project_id=project_id, id=app_id
- )
- for artifact in response.artifacts:
- if f"commands/{command_name}.py" == artifact.filename:
- resp = requests.get(artifact.url, allow_redirects=True)
-
- with open(target_file, "wb") as f:
- f.write(resp.content)
- else:
- shutil.copy(cls_path, target_file)
-
- spec = spec_from_file_location(cls_name, target_file)
- mod = module_from_spec(spec)
- sys.modules[cls_name] = mod
- spec.loader.exec_module(mod)
- command_type = getattr(mod, cls_name)
- if issubclass(command_type, ClientCommand):
- command = command_type(method=None)
- else:
- raise ValueError(f"Expected class {cls_name} for command {command_name} to be a `ClientCommand`.")
- if tmpdir and os.path.exists(tmpdir):
- shutil.rmtree(tmpdir)
- return command
-
-
-def _to_annotation(anno: str) -> str:
- anno = anno.split("'")[1]
- if "." in anno:
- return anno.split(".")[-1]
- return anno
-
-
-def _validate_client_command(command: ClientCommand):
- """Extract method and its metadata from a ClientCommand."""
- params = inspect.signature(command.method).parameters
- command_metadata = {
- "cls_path": inspect.getfile(command.__class__),
- "cls_name": command.__class__.__name__,
- "params": {p.name: _to_annotation(str(p.annotation)) for p in params.values()},
- **command._to_dict(),
- }
- method = command.method
- command.models = {}
- for k, v in command_metadata["params"].items():
- if v == "_empty":
- raise Exception(
- f"Please, annotate your method {method} with pydantic BaseModel. Refer to the documentation."
- )
- config = getattr(sys.modules[command.__module__], v, None)
- if config is None:
- config = getattr(sys.modules[method.__module__], v, None)
- if config:
- raise Exception(
- f"The provided annotation for the argument {k} should in the file "
- f"{inspect.getfile(command.__class__)}, not {inspect.getfile(command.method)}."
- )
- if config is None or not issubclass(config, BaseModel):
- raise Exception(
- f"The provided annotation for the argument {k} shouldn't an instance of pydantic BaseModel."
- )
-
-
-def _upload(name: str, prefix: str, obj: Any) -> Optional[str]:
- from lightning.app.storage.path import _filesystem, _is_s3fs_available, _shared_storage_path
-
- name = name.replace(" ", "_")
- filepath = f"{prefix}/{name}.py"
- fs = _filesystem()
-
- if _is_s3fs_available():
- from s3fs import S3FileSystem
-
- if not isinstance(fs, S3FileSystem):
- return None
-
- source_file = str(inspect.getfile(obj.__class__))
- remote_url = str(_shared_storage_path() / "artifacts" / filepath)
- fs.put(source_file, remote_url)
- return filepath
- return None
-
-
-def _prepare_commands(app) -> List:
- if not is_overridden("configure_commands", app.root):
- return []
-
- # 1: Upload the command to s3.
- commands = app.root.configure_commands()
- for command_mapping in commands:
- for command_name, command in command_mapping.items():
- if isinstance(command, ClientCommand):
- _upload(command_name, "commands", command)
-
- # 2: Cache the commands on the app.
- app.commands = commands
- return commands
-
-
-def _process_api_request(app, request: _APIRequest):
- flow = app.get_component_by_name(request.name)
- method = getattr(flow, request.method_name)
- try:
- response = _RequestResponse(content=method(*request.args, **request.kwargs), status_code=200)
- except HTTPException as ex:
- logger.error(repr(ex))
- response = _RequestResponse(status_code=ex.status_code, content=ex.detail)
- except Exception:
- logger.error(traceback.print_exc())
- response = _RequestResponse(status_code=500)
- return {"response": response, "id": request.id}
-
-
-def _process_command_requests(app, request: _CommandRequest):
- for command in app.commands:
- for command_name, method in command.items():
- command_name = command_name.replace(" ", "_")
- if request.method_name == command_name:
- # 2.1: Evaluate the method associated to a specific command.
- # Validation is done on the CLI side.
- try:
- response = _RequestResponse(content=method(*request.args, **request.kwargs), status_code=200)
- except HTTPException as ex:
- logger.error(repr(ex))
- response = _RequestResponse(status_code=ex.status_code, content=ex.detail)
- except Exception:
- logger.error(traceback.print_exc())
- response = _RequestResponse(status_code=500)
- return {"response": response, "id": request.id}
- return None
-
-
-def _process_requests(app, requests: List[Union[_APIRequest, _CommandRequest]]) -> None:
- """Convert user commands to API endpoint."""
- responses = []
- for request in requests:
- if isinstance(request, _APIRequest):
- response = _process_api_request(app, request)
- else:
- response = _process_command_requests(app, request)
-
- if response:
- responses.append(response)
-
- app.api_response_queue.put(responses)
-
-
-def _collect_open_api_extras(command, info) -> Dict:
- if not isinstance(command, ClientCommand):
- if command.__doc__ is not None:
- return {"description": command.__doc__}
- return {}
-
- extras = {
- "cls_path": inspect.getfile(command.__class__),
- "cls_name": command.__class__.__name__,
- "description": command.description,
- }
- if command.requirements:
- extras.update({"requirements": command.requirements})
- if info:
- extras.update({"app_info": asdict(info)})
- return extras
-
-
-def _commands_to_api(
- commands: List[Dict[str, Union[Callable, ClientCommand]]], info: Optional[frontend.AppInfo] = None
-) -> List:
- """Convert user commands to API endpoint."""
- api = []
- for command in commands:
- for k, v in command.items():
- k = k.replace(" ", "_")
- api.append(
- Post(
- f"/command/{k}",
- v.method if isinstance(v, ClientCommand) else v,
- method_name=k,
- tags=["app_client_command"] if isinstance(v, ClientCommand) else ["app_command"],
- openapi_extra=_collect_open_api_extras(v, info),
- )
- )
- return api
diff --git a/src/lightning/app/utilities/component.py b/src/lightning/app/utilities/component.py
deleted file mode 100644
index 29a60168bf1eb..0000000000000
--- a/src/lightning/app/utilities/component.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from contextlib import contextmanager
-from typing import TYPE_CHECKING, Any, Dict, Generator, Optional
-
-from deepdiff.helper import NotPresent
-from lightning_utilities.core.apply_func import apply_to_collection
-
-from lightning.app.utilities.app_helpers import is_overridden
-from lightning.app.utilities.enum import ComponentContext
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-from lightning.app.utilities.tree import breadth_first
-
-if TYPE_CHECKING:
- from lightning.app.core import LightningFlow
-
-COMPONENT_CONTEXT: Optional[ComponentContext] = None
-
-
-def _convert_paths_after_init(root: "LightningFlow"):
- """Converts the path attributes on a component to a dictionary.
-
- This is necessary because at the time of instantiating the component, its full affiliation is not known and Paths
- that get passed to other componenets during ``__init__`` are otherwise not able to reference their origin or
- consumer.
-
- """
- from lightning.app.core import LightningFlow, LightningWork
- from lightning.app.storage.path import Path
-
- for component in breadth_first(root, types=(LightningFlow, LightningWork)):
- for attr in list(component.__dict__.keys()):
- value = getattr(component, attr)
- if isinstance(value, Path):
- delattr(component, attr)
- component._paths[attr] = value.to_dict()
-
-
-def _sanitize_state(state: Dict[str, Any]) -> Dict[str, Any]:
- """Utility function to sanitize the state of a component.
-
- Sanitization enables the state to be deep-copied and hashed.
-
- """
- from lightning.app.storage import Drive, Path
- from lightning.app.storage.payload import _BasePayload
-
- def sanitize_path(path: Path) -> Path:
- path_copy = Path(path)
- path_copy._sanitize()
- return path_copy
-
- def sanitize_payload(payload: _BasePayload):
- return type(payload).from_dict(content=payload.to_dict())
-
- def sanitize_drive(drive: Drive) -> Dict:
- return drive.to_dict()
-
- def sanitize_cloud_compute(cloud_compute: CloudCompute) -> Dict:
- return cloud_compute.to_dict()
-
- state = apply_to_collection(state, dtype=Path, function=sanitize_path)
- state = apply_to_collection(state, dtype=_BasePayload, function=sanitize_payload)
- state = apply_to_collection(state, dtype=Drive, function=sanitize_drive)
- state = apply_to_collection(state, dtype=CloudCompute, function=sanitize_cloud_compute)
- return state
-
-
-def _state_to_json(state: Dict[str, Any]) -> Dict[str, Any]:
- """Utility function to make sure that state dict is json serializable."""
- from lightning.app.storage.path import Path
- from lightning.app.storage.payload import _BasePayload
-
- state_paths_cleaned = apply_to_collection(state, dtype=(Path, _BasePayload), function=lambda x: x.to_dict())
- return apply_to_collection(state_paths_cleaned, dtype=type(NotPresent), function=lambda x: None)
-
-
-def _set_context(name: Optional[str]) -> None:
- global COMPONENT_CONTEXT
- COMPONENT_CONTEXT = os.getenv("COMPONENT_CONTEXT") if name is None else ComponentContext(name)
-
-
-def _get_context() -> Optional[ComponentContext]:
- global COMPONENT_CONTEXT
- return COMPONENT_CONTEXT
-
-
-def _set_flow_context() -> None:
- global COMPONENT_CONTEXT
- COMPONENT_CONTEXT = ComponentContext.FLOW
-
-
-def _set_work_context() -> None:
- global COMPONENT_CONTEXT
- COMPONENT_CONTEXT = ComponentContext.WORK
-
-
-def _set_frontend_context() -> None:
- global COMPONENT_CONTEXT
- COMPONENT_CONTEXT = ComponentContext.FRONTEND
-
-
-def _is_flow_context() -> bool:
- global COMPONENT_CONTEXT
- return COMPONENT_CONTEXT == ComponentContext.FLOW
-
-
-def _is_work_context() -> bool:
- global COMPONENT_CONTEXT
- return COMPONENT_CONTEXT == ComponentContext.WORK
-
-
-def _is_frontend_context() -> bool:
- global COMPONENT_CONTEXT
- return COMPONENT_CONTEXT == ComponentContext.FRONTEND
-
-
-@contextmanager
-def _context(ctx: str) -> Generator[None, None, None]:
- """Set the global component context for the block below this context manager.
-
- The context is used to determine whether the current process is running for a LightningFlow or for a LightningWork.
- See also :func:`_get_context`, :func:`_set_context`. For internal use only.
-
- """
- prev = _get_context()
- _set_context(ctx)
- yield
- _set_context(prev)
-
-
-def _validate_root_flow(flow: "LightningFlow") -> None:
- from lightning.app.core.flow import LightningFlow
-
- if not is_overridden("run", instance=flow, parent=LightningFlow):
- raise TypeError(
- "The root flow passed to `LightningApp` does not override the `run()` method. This is required. Please"
- f" implement `run()` in your `{flow.__class__.__name__}` class."
- )
diff --git a/src/lightning/app/utilities/data_structures.py b/src/lightning/app/utilities/data_structures.py
deleted file mode 100644
index 495c43fd0ea24..0000000000000
--- a/src/lightning/app/utilities/data_structures.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any, Dict, Optional
-
-
-class AttributeDict(Dict):
- """Extended dictionary accessible with dot notation.
-
- >>> ad = AttributeDict({'key1': 1, 'key2': 'abc'})
- >>> ad.key1
- 1
- >>> ad.update({'my-key': 3.14})
- >>> ad.update(new_key=42)
- >>> ad.key1 = 2
- >>> ad
- "key1": 2
- "key2": abc
- "my-key": 3.14
- "new_key": 42
-
- """
-
- def __getattr__(self, key: str) -> Optional[Any]:
- try:
- return self[key]
- except KeyError as exp:
- raise AttributeError(f'Missing attribute "{key}"') from exp
-
- def __setattr__(self, key: str, val: Any) -> None:
- self[key] = val
-
- def __repr__(self) -> str:
- if not len(self):
- return ""
- max_key_length = max(len(str(k)) for k in self)
- tmp_name = "{:" + str(max_key_length + 3) + "s} {}"
- rows = [tmp_name.format(f'"{n}":', self[n]) for n in sorted(self.keys())]
- return "\n".join(rows)
diff --git a/src/lightning/app/utilities/dependency_caching.py b/src/lightning/app/utilities/dependency_caching.py
deleted file mode 100644
index 8cb389a20a164..0000000000000
--- a/src/lightning/app/utilities/dependency_caching.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import hashlib
-from pathlib import Path
-
-
-def get_hash(path: Path, chunk_num_blocks: int = 128) -> str:
- """Get the hash of a file."""
- h = hashlib.blake2b(digest_size=20)
- if not path.exists():
- raise FileNotFoundError(f"{path} does not exist")
- with path.open("rb") as f:
- for chunk in iter(lambda: f.read(chunk_num_blocks * h.block_size), b""):
- h.update(chunk)
- return h.hexdigest()
diff --git a/src/lightning/app/utilities/enum.py b/src/lightning/app/utilities/enum.py
deleted file mode 100644
index 1e5422289d220..0000000000000
--- a/src/lightning/app/utilities/enum.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import enum
-from datetime import datetime, timezone
-from typing import Optional
-
-
-class ComponentContext(enum.Enum):
- """Describes whether the current process is running LightningFlow or LightningWork."""
-
- FLOW = "flow"
- WORK = "work"
- FRONTEND = "frontend"
-
-
-class AppStage(enum.Enum):
- BLOCKING = "blocking"
- RUNNING = "running"
- RESTARTING = "restarting"
- STOPPING = "stopping"
- FAILED = "failed"
-
-
-class WorkFailureReasons:
- TIMEOUT = "timeout" # triggered when pending and wait timeout has been passed
- SPOT_RETRIVAL = "spot_retrival" # triggered when a SIGTERM signal is sent the spot instance work.
- USER_EXCEPTION = "user_exception" # triggered when an exception is raised by user code.
- INVALID_RETURN_VALUE = "invalid_return_value" # triggered when the return value isn't valid.
-
-
-class WorkStopReasons:
- SIGTERM_SIGNAL_HANDLER = "sigterm_signal_handler"
- PENDING = "pending"
-
-
-class WorkPendingReason(enum.Enum):
- IMAGE_BUILDING = "image_building"
- REQUESTING_RESOURCE = "requesting_ressource"
-
-
-class WorkStageStatus:
- NOT_STARTED = "not_started"
- STARTED = "started"
- STOPPED = "stopped"
- PENDING = "pending"
- RUNNING = "running"
- SUCCEEDED = "succeeded"
- FAILED = "failed"
-
-
-def make_status(stage: str, message: Optional[str] = None, reason: Optional[str] = None):
- status = {
- "stage": stage,
- "timestamp": datetime.now(tz=timezone.utc).timestamp(),
- }
- if message:
- status["message"] = message
- if reason:
- status["reason"] = reason
- return status
-
-
-class CacheCallsKeys:
- LATEST_CALL_HASH = "latest_call_hash"
-
-
-class OpenAPITags:
- APP_CLIENT_COMMAND = "app_client_command"
- APP_COMMAND = "app_command"
- APP_API = "app_api"
diff --git a/src/lightning/app/utilities/exceptions.py b/src/lightning/app/utilities/exceptions.py
deleted file mode 100644
index 3bb5eced46ed7..0000000000000
--- a/src/lightning/app/utilities/exceptions.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from json import JSONDecodeError, loads
-from typing import Any
-
-from click import ClickException, Context, Group
-from lightning_cloud.openapi.rest import ApiException
-
-
-class _ApiExceptionHandler(Group):
- """Attempts to convert ApiExceptions to ClickExceptions.
-
- This process clarifies the error for the user by:
- 1. Showing the error message from the lightning.ai servers,
- instead of showing the entire HTTP response
- 2. Suppressing long tracebacks
-
- However, if the ApiException cannot be decoded, or is not
- a 4xx error, the original ApiException will be re-raised.
-
- """
-
- def invoke(self, ctx: Context) -> Any:
- try:
- return super().invoke(ctx)
- except ApiException as api:
- exception_messages = []
- if 400 <= api.status < 500:
- try:
- body = loads(api.body)
- except JSONDecodeError:
- raise api
- exception_messages.append(body["message"])
- exception_messages.extend(body["details"])
- else:
- raise api
- raise ClickException("\n".join(exception_messages))
-
-
-class MisconfigurationException(Exception):
- """Exception used to inform users of misuse with Lightning."""
-
-
-class CacheMissException(Exception):
- """Exception used internally as a boundary to non-executed functions."""
-
-
-class ExitAppException(Exception):
- """Exception used by components to signal that App should exit."""
-
-
-class LightningComponentException(Exception):
- """Exception used to inform users of misuse with LightningComponent."""
-
-
-class InvalidPathException(Exception):
- """Exception used to inform users they are accessing an invalid path."""
-
-
-class LightningFlowException(Exception):
- """Exception used to inform users of misuse with LightningFlow."""
-
-
-class LightningWorkException(Exception):
- """Exception used to inform users of misuse with LightningWork."""
-
-
-class LightningPlatformException(Exception): # pragma: no cover
- """Exception used to inform users of issues related to platform the LightningApp is running on.
-
- It gets raised by the Lightning Launcher on the platform side when the app is running in the cloud, and is useful
- when framework or user code needs to catch exceptions specific to the platform, e.g., when resources exceed quotas.
-
- """
-
-
-class LightningAppStateException(Exception):
- """Exception to inform users of app state errors."""
-
-
-class LightningSigtermStateException(Exception):
- """Exception to propagate exception in work proxy."""
-
- def __init__(self, exit_code):
- self.exit_code = exit_code
-
-
-class LogLinesLimitExceeded(Exception):
- """Exception to inform the user that we've reached the maximum number of log lines."""
diff --git a/src/lightning/app/utilities/frontend.py b/src/lightning/app/utilities/frontend.py
deleted file mode 100644
index 29b6daef793f4..0000000000000
--- a/src/lightning/app/utilities/frontend.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dataclasses import dataclass
-from typing import List, Optional
-
-from bs4 import BeautifulSoup
-
-
-@dataclass
-class AppInfo:
- title: Optional[str] = None
- favicon: Optional[str] = None
- description: Optional[str] = None
- image: Optional[str] = None
- # ensure the meta tags are correct or the UI might fail to load.
- meta_tags: Optional[List[str]] = None
- on_connect_end: Optional[str] = None
-
-
-def update_index_file(ui_root: str, info: Optional[AppInfo] = None, root_path: str = "") -> None:
- import shutil
- from pathlib import Path
-
- entry_file = Path(ui_root) / "index.html"
- original_file = Path(ui_root) / "index.original.html"
-
- if not original_file.exists():
- shutil.copyfile(entry_file, original_file) # keep backup
- else:
- # revert index.html in case it was modified after creating original.html
- shutil.copyfile(original_file, entry_file)
-
- if info:
- with original_file.open() as f:
- original = f.read()
-
- with entry_file.open("w") as f:
- f.write(_get_updated_content(original=original, root_path=root_path, info=info))
-
- if root_path:
- root_path_without_slash = root_path.replace("/", "", 1) if root_path.startswith("/") else root_path
- src_dir = Path(ui_root)
- dst_dir = src_dir / root_path_without_slash
-
- if dst_dir.exists():
- shutil.rmtree(dst_dir, ignore_errors=True)
- # copy everything except the current root_path, this is to fix a bug if user specifies
- # /abc at first and then /abc/def, server don't start
- # ideally we should copy everything except custom root_path that user passed.
- shutil.copytree(src_dir, dst_dir, ignore=shutil.ignore_patterns(f"{root_path_without_slash}*"))
-
-
-def _get_updated_content(original: str, root_path: str, info: AppInfo) -> str:
- soup = BeautifulSoup(original, "html.parser")
-
- # replace favicon
- if info.favicon:
- soup.find("link", {"rel": "icon"}).attrs["href"] = info.favicon
-
- if info.title is not None:
- soup.find("title").string = info.title
-
- if info.description:
- soup.find("meta", {"name": "description"}).attrs["content"] = info.description
-
- if info.image:
- soup.find("meta", {"property": "og:image"}).attrs["content"] = info.image
-
- if info.meta_tags:
- for meta in info.meta_tags:
- soup.find("head").append(BeautifulSoup(meta, "html.parser"))
-
- if root_path:
- # this will be used by lightning app ui to add root_path to add requests
- soup.find("head").append(BeautifulSoup(f'', "html.parser"))
-
- return str(soup).replace("/static", f"{root_path}/static")
diff --git a/src/lightning/app/utilities/git.py b/src/lightning/app/utilities/git.py
deleted file mode 100644
index 1293a2a5095fa..0000000000000
--- a/src/lightning/app/utilities/git.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import subprocess
-from pathlib import Path
-from typing import List, Union
-
-# TODO - github utilities are already defined in GridSDK use that?
-
-
-def execute_git_command(args: List[str], cwd=None) -> str:
- """Executes a git command. This is expected to return a single string back.
-
- Returns
- -------
- output: str
- String combining stdout and stderr.
-
- """
- process = subprocess.run(["git"] + args, capture_output=True, text=True, cwd=cwd, check=False)
- return process.stdout.strip() + process.stderr.strip()
-
-
-def get_dir_name(cwd=None) -> str:
- github_repository = execute_git_command(["config", "--get", "remote.origin.url"], cwd=cwd)
- if github_repository and "github.com" in github_repository:
- return github_repository.split("/")[-1].split(".")[0]
- raise RuntimeError("Only work with github repositories.")
-
-
-def check_github_repository(cwd=None) -> bool:
- """Checks if the active directory is a GitHub repository."""
- github_repository = execute_git_command(["config", "--get", "remote.origin.url"], cwd=cwd)
-
- if not github_repository or "github.com" not in github_repository:
- return False
- return True
-
-
-def get_git_relative_path(file: Union[str, Path]) -> str:
- """Finds the relative path of the file to the git root."""
- if not check_github_repository():
- raise ValueError("Not a GitHub repository.")
- abs_path = Path(file).absolute()
- repository_path = execute_git_command(["rev-parse", "--show-toplevel"])
- return str(abs_path.relative_to(repository_path))
-
-
-def check_if_remote_head_is_different() -> Union[bool, None]:
- """Checks if remote git repository is different than the version available locally.
-
- This only compares the local SHA to the HEAD commit of a given branch. This check won't be used if user isn't in a
- HEAD locally.
-
- """
- # Check SHA values.
- local_sha = execute_git_command(["rev-parse", "@"])
- remote_sha = execute_git_command(["rev-parse", r"@{u}"])
- base_sha = execute_git_command(["merge-base", "@", r"@{u}"])
-
- # Whenever a SHA is not avaialble, just return.
- if any("fatal" in f for f in (local_sha, remote_sha, base_sha)):
- return None
-
- return local_sha not in (remote_sha, base_sha)
-
-
-def has_uncommitted_files() -> bool:
- """Checks if user has uncommited files in local repository.
-
- If there are uncommited files, then show a prompt indicating that uncommited files exist locally.
-
- """
- files = execute_git_command(["update-index", "--refresh"])
- return bool(files)
diff --git a/src/lightning/app/utilities/imports.py b/src/lightning/app/utilities/imports.py
deleted file mode 100644
index 33d6c259e09b5..0000000000000
--- a/src/lightning/app/utilities/imports.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""General utilities."""
-
-import functools
-import os
-import platform
-import sys
-import warnings
-from typing import Any, List, Union
-
-from lightning_utilities.core.imports import module_available
-from packaging.requirements import Marker, Requirement
-
-try:
- from importlib import metadata
-except ImportError:
- # Python < 3.8
- import importlib_metadata as metadata # type: ignore
-
-
-def _get_extras(extras: str) -> str:
- """Get the given extras as a space delimited string.
-
- Used by the platform to install cloud extras in the cloud.
-
- """
- from lightning.app import __package_name__
-
- requirements = {r: Requirement(r) for r in metadata.requires(__package_name__)}
- marker = Marker(f'extra == "{extras}"')
- requirements = [r for r, req in requirements.items() if str(req.marker) == str(marker)]
-
- if requirements:
- requirements = [f"'{r.split(';')[0].strip()}'" for r in requirements]
- return " ".join(requirements)
- return ""
-
-
-def requires(module_paths: Union[str, List]):
- if not isinstance(module_paths, list):
- module_paths = [module_paths]
-
- def decorator(func):
- @functools.wraps(func)
- def wrapper(*args: Any, **kwargs: Any):
- unavailable_modules = [f"'{module}'" for module in module_paths if not module_available(module)]
- if any(unavailable_modules):
- is_lit_testing = bool(int(os.getenv("LIGHTING_TESTING", "0")))
- msg = f"Required dependencies not available. Please run: pip install {' '.join(unavailable_modules)}"
- if is_lit_testing:
- warnings.warn(msg)
- else:
- raise ModuleNotFoundError(msg)
- return func(*args, **kwargs)
-
- return wrapper
-
- return decorator
-
-
-# TODO: Automatically detect dependencies
-def _is_redis_available() -> bool:
- return module_available("redis")
-
-
-def _is_torch_available() -> bool:
- return module_available("torch")
-
-
-def _is_pytorch_lightning_available() -> bool:
- return module_available("lightning.pytorch")
-
-
-def _is_torchvision_available() -> bool:
- return module_available("torchvision")
-
-
-def _is_json_argparse_available() -> bool:
- return module_available("jsonargparse")
-
-
-def _is_streamlit_available() -> bool:
- return module_available("streamlit")
-
-
-def _is_param_available() -> bool:
- return module_available("param")
-
-
-def _is_streamlit_tensorboard_available() -> bool:
- return module_available("streamlit_tensorboard")
-
-
-def _is_gradio_available() -> bool:
- return module_available("gradio")
-
-
-def _is_lightning_flash_available() -> bool:
- return module_available("flash")
-
-
-def _is_pil_available() -> bool:
- return module_available("PIL")
-
-
-def _is_numpy_available() -> bool:
- return module_available("numpy")
-
-
-def _is_docker_available() -> bool:
- return module_available("docker")
-
-
-def _is_jinja2_available() -> bool:
- return module_available("jinja2")
-
-
-def _is_playwright_available() -> bool:
- return module_available("playwright")
-
-
-def _is_s3fs_available() -> bool:
- return module_available("s3fs")
-
-
-def _is_sqlmodel_available() -> bool:
- return module_available("sqlmodel")
-
-
-def _is_aiohttp_available() -> bool:
- return module_available("aiohttp")
-
-
-_CLOUD_TEST_RUN = bool(os.getenv("CLOUD", False))
-_IS_WINDOWS = platform.system() == "Windows"
-_IS_MACOS = sys.platform == "darwin"
diff --git a/src/lightning/app/utilities/introspection.py b/src/lightning/app/utilities/introspection.py
deleted file mode 100644
index a794c6ca9fc12..0000000000000
--- a/src/lightning/app/utilities/introspection.py
+++ /dev/null
@@ -1,400 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import ast
-import inspect
-from pathlib import Path
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Type, Union
-
-if TYPE_CHECKING:
- from lightning.app.core import LightningFlow, LightningWork
-
-
-class LightningVisitor(ast.NodeVisitor):
- """Base class for visitor that finds class definitions based on class inheritance. Derived classes are expected to
- define class_name and implement the analyze_class_def method.
-
- Attributes
- ----------
- class_name: str
- Name of class to identify, to be defined in subclasses.
-
- """
-
- class_name: Optional[str] = None
-
- def __init__(self):
- self.found: List[Dict[str, Any]] = []
-
- def analyze_class_def(self, node: ast.ClassDef) -> Dict[str, Any]:
- return {}
-
- def visit_ClassDef(self, node: ast.ClassDef) -> None:
- bases = []
- for base in node.bases:
- if type(base) == ast.Attribute:
- bases.append(base.attr)
- elif type(base) == ast.Name:
- bases.append(base.id)
- if self.class_name in bases:
- entry = {"name": node.name, "type": self.class_name}
- entry.update(self.analyze_class_def(node))
- self.found.append(entry)
-
-
-class LightningModuleVisitor(LightningVisitor):
- """Finds Lightning modules based on class inheritance.
-
- Attributes
- ----------
- class_name: Optional[str]
- Name of class to identify.
- methods: Set[str]
- Names of methods that are part of the LightningModule API.
- hooks: Set[str]
- Names of hooks that are part of the LightningModule API.
-
- """
-
- class_name: Optional[str] = "LightningModule"
-
- methods: Set[str] = {
- "configure_optimizers",
- "forward",
- "freeze",
- "log",
- "log_dict",
- "print",
- "save_hyperparameters",
- "test_step",
- "test_step_end",
- "to_onnx",
- "to_torchscript",
- "training_step",
- "training_step_end",
- "unfreeze",
- "validation_step",
- "validation_step_end",
- }
-
- hooks: Set[str] = {
- "backward",
- "get_progress_bar_dict",
- "manual_backward",
- "manual_optimizer_step",
- "on_after_backward",
- "on_before_zero_grad",
- "on_fit_start",
- "on_fit_end",
- "on_load_checkpoint",
- "on_save_checkpoint",
- "on_pretrain_routine_start",
- "on_pretrain_routine_end",
- "on_test_batch_start",
- "on_test_batch_end",
- "on_test_epoch_start",
- "on_test_epoch_end",
- "on_train_batch_start",
- "on_train_batch_end",
- "on_train_epoch_start",
- "on_train_epoch_end",
- "on_validation_batch_start",
- "on_validation_batch_end",
- "on_validation_epoch_start",
- "on_validation_epoch_end",
- "optimizer_step",
- "optimizer_zero_grad",
- "prepare_data",
- "setup",
- "teardown",
- "train_dataloader",
- "val_dataloader",
- "test_dataloader",
- "transfer_batch_to_device",
- }
-
-
-class LightningDataModuleVisitor(LightningVisitor):
- """Finds Lightning data modules based on class inheritance.
-
- Attributes
- ----------
- class_name: Optional[str]
- Name of class to identify.
- methods: Set[str]
- Names of methods that are part of the LightningDataModule API.
-
- """
-
- class_name = "LightningDataModule"
-
- methods: Set[str] = {
- "prepare_data",
- "setup",
- "train_dataloader",
- "val_dataloader",
- "test_dataloader",
- "transfer_batch_to_device",
- }
-
-
-class LightningLoggerVisitor(LightningVisitor):
- """Finds Lightning loggers based on class inheritance.
-
- Attributes
- ----------
- class_name: Optional[str]
- Name of class to identify.
- methods: Set[str]
- Names of methods that are part of the Logger API.
-
- """
-
- class_name = "Logger"
-
- methods: Set[str] = {"log_hyperparams", "log_metrics"}
-
-
-class LightningCallbackVisitor(LightningVisitor):
- """Finds Lightning callbacks based on class inheritance.
-
- Attributes
- ----------
- class_name: Optional[str]
- Name of class to identify.
- methods: Set[str]
- Names of methods that are part of the Logger API.
-
- """
-
- class_name = "Callback"
-
- methods: Set[str] = {
- "setup",
- "teardown",
- "on_init_start",
- "on_init_end",
- "on_fit_start",
- "on_fit_end",
- "on_sanity_check_start",
- "on_sanity_check_end",
- "on_train_batch_start",
- "on_train_batch_end",
- "on_train_epoch_start",
- "on_train_epoch_end",
- "on_validation_epoch_start",
- "on_validation_epoch_end",
- "on_test_epoch_start",
- "on_test_epoch_end",
- "on_epoch_start",
- "on_epoch_end",
- "on_batch_start",
- "on_validation_batch_start",
- "on_validation_batch_end",
- "on_test_batch_start",
- "on_test_batch_end",
- "on_batch_end",
- "on_train_start",
- "on_train_end",
- "on_pretrain_routine_start",
- "on_pretrain_routine_end",
- "on_validation_start",
- "on_validation_end",
- "on_test_start",
- "on_test_end",
- "on_keyboard_interrupt",
- "on_save_checkpoint",
- "on_load_checkpoint",
- }
-
-
-class LightningStrategyVisitor(LightningVisitor):
- """Finds Lightning callbacks based on class inheritance.
-
- Attributes
- ----------
- class_name: Optional[str]
- Name of class to identify.
- methods: Set[str]
- Names of methods that are part of the Logger API.
-
- """
-
- class_name = "Strategy"
-
- methods: Set[str] = {
- "setup",
- "train",
- "training_step",
- "validation_step",
- "test_step",
- "backward",
- "barrier",
- "broadcast",
- "sync_tensor",
- }
-
-
-class LightningTrainerVisitor(LightningVisitor):
- class_name = "Trainer"
-
-
-class LightningCLIVisitor(LightningVisitor):
- class_name = "LightningCLI"
-
-
-class LightningPrecisionPluginVisitor(LightningVisitor):
- class_name = "PrecisionPlugin"
-
-
-class LightningAcceleratorVisitor(LightningVisitor):
- class_name = "Accelerator"
-
-
-class TorchMetricVisitor(LightningVisitor):
- class_name = "Metric"
-
-
-class FabricVisitor(LightningVisitor):
- class_name = "Fabric"
-
-
-class LightningProfilerVisitor(LightningVisitor):
- class_name = "Profiler"
-
-
-class Scanner:
- """Finds relevant Lightning objects in files in the file system.
-
- Attributes
- ----------
- visitor_classes: List[Type]
- List of visitor classes to use when traversing files.
- Parameters
- ----------
- path: str
- Path to file, or directory where to look for files to scan.
- glob_pattern: str
- Glob pattern to use when looking for files in the path,
- applied when path is a directory. Default is "**/*.py".
-
- """
-
- # TODO: Finalize introspecting the methods from all the discovered methods.
- visitor_classes: List[Type] = [
- LightningCLIVisitor,
- LightningTrainerVisitor,
- LightningModuleVisitor,
- LightningDataModuleVisitor,
- LightningCallbackVisitor,
- LightningStrategyVisitor,
- LightningPrecisionPluginVisitor,
- LightningAcceleratorVisitor,
- LightningLoggerVisitor,
- TorchMetricVisitor,
- FabricVisitor,
- LightningProfilerVisitor,
- ]
-
- def __init__(self, path: str, glob_pattern: str = "**/*.py"):
- path_ = Path(path)
- if path_.is_dir():
- self.paths = path_.glob(glob_pattern)
- else:
- self.paths = [path_]
-
- self.modules_found: List[Dict[str, Any]] = []
-
- def has_class(self, cls) -> bool:
- # This method isn't strong enough as it is using only `ImportFrom`.
- # TODO: Use proper classDef scanning.
- classes = []
-
- for path in self.paths:
- try:
- module = ast.parse(path.open().read())
- except SyntaxError:
- print(f"Error while parsing {path}: SKIPPING")
- continue
-
- for node in ast.walk(module):
- if isinstance(node, ast.ImportFrom):
- for import_from_cls in node.names:
- classes.append(import_from_cls.name)
-
- if isinstance(node, ast.Call):
- cls_name = getattr(node.func, "attr", None)
- if cls_name:
- classes.append(cls_name)
-
- return cls.__name__ in classes
-
- def scan(self) -> List[Dict[str, str]]:
- """Finds Lightning modules in files, returning importable objects.
-
- Returns
- -------
- List[Dict[str, Any]]
- List of dicts containing all metadata required
- to import modules found.
-
- """
- modules_found: Dict[str, List[Dict[str, Any]]] = {}
-
- for path in self.paths:
- try:
- module = ast.parse(path.open().read())
- except SyntaxError:
- print(f"Error while parsing {path}: SKIPPING")
- continue
- for visitor_class in self.visitor_classes:
- visitor = visitor_class()
- visitor.visit(module)
- if not visitor.found:
- continue
- _path = str(path)
- ns_info = {
- "file": _path,
- "namespace": _path.replace("/", ".").replace(".py", ""),
- }
- modules_found[visitor_class.class_name] = [{**entry, **ns_info} for entry in visitor.found]
-
- return modules_found
-
-
-def _is_method_context(component: Union["LightningFlow", "LightningWork"], selected_caller_name: str) -> bool:
- """Checks whether the call to a component originates from within the context of the component's ``__init__``
- method."""
- frame = inspect.currentframe().f_back
-
- while frame is not None:
- caller_name = frame.f_code.co_name
- caller_self = frame.f_locals.get("self")
- if caller_name == selected_caller_name and caller_self is component:
- # the call originates from a frame under component.__init__
- return True
- frame = frame.f_back
-
- return False
-
-
-def _is_init_context(component: Union["LightningFlow", "LightningWork"]) -> bool:
- """Checks whether the call to a component originates from within the context of the component's ``__init__``
- method."""
- return _is_method_context(component, "__init__")
-
-
-def _is_run_context(component: Union["LightningFlow", "LightningWork"]) -> bool:
- """Checks whether the call to a component originates from within the context of the component's ``run`` method."""
- return _is_method_context(component, "run") or _is_method_context(component, "load_state_dict")
diff --git a/src/lightning/app/utilities/layout.py b/src/lightning/app/utilities/layout.py
deleted file mode 100644
index 6da0b2ca412ea..0000000000000
--- a/src/lightning/app/utilities/layout.py
+++ /dev/null
@@ -1,215 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import inspect
-import warnings
-from typing import Dict, List, Union
-
-import lightning.app
-from lightning.app.frontend.frontend import Frontend
-from lightning.app.utilities.app_helpers import _MagicMockJsonSerializable, is_overridden
-from lightning.app.utilities.cloud import is_running_in_cloud
-
-
-def _add_comment_to_literal_code(method, contains, comment):
- """Inspects a method's code and adds a message to it.
-
- This is a nice to have, so if it fails for some reason, it shouldn't affect the program.
-
- """
- try:
- lines = inspect.getsource(method)
- lines = lines.split("\n")
- idx_list = [i for i, x in enumerate(lines) if contains in x]
- for i in idx_list:
- line = lines[i]
- line += comment
- lines[i] = line
-
- return "\n".join(lines)
-
- except Exception:
- return ""
-
-
-def _collect_layout(app: "lightning.app.LightningApp", flow: "lightning.app.LightningFlow") -> Union[Dict, List[Dict]]:
- """Process the layout returned by the ``configure_layout()`` method in each flow."""
- layout = flow.configure_layout()
-
- if isinstance(layout, Frontend):
- frontend = layout
- frontend.flow = flow
- app.frontends.setdefault(flow.name, frontend)
-
- # When running locally, the target will get overwritten by the dispatcher when launching the frontend servers
- # When running in the cloud, the frontend code will construct the URL based on the flow name
- return flow._layout
- if isinstance(layout, _MagicMockJsonSerializable):
- # The import was mocked, we set a dummy `Frontend` so that `is_headless` knows there is a UI
- app.frontends.setdefault(flow.name, "mock")
- return flow._layout
- if isinstance(layout, dict):
- layout = _collect_content_layout([layout], app, flow)
- elif isinstance(layout, (list, tuple)) and all(isinstance(item, dict) for item in layout):
- layout = _collect_content_layout(layout, app, flow)
- else:
- lines = _add_comment_to_literal_code(flow.configure_layout, contains="return", comment=" <------- this guy")
- raise TypeError(
- f"""
- The return value of configure_layout() in `{flow.__class__.__name__}` is an unsupported layout format:
- \n{lines}
-
- Return either an object of type {Frontend} (e.g., StreamlitFrontend, StaticWebFrontend):
- def configure_layout(self):
- return la.frontend.Frontend(...)
-
- OR a single dict:
- def configure_layout(self):
- tab1 = {{'name': 'tab name', 'content': self.a_component}}
- return tab1
-
- OR a list of dicts:
- def configure_layout(self):
- tab1 = {{'name': 'tab name 1', 'content': self.component_a}}
- tab2 = {{'name': 'tab name 2', 'content': self.component_b}}
- return [tab1, tab2]
-
- (see the docs for `LightningFlow.configure_layout`).
- """
- )
-
- return layout
-
-
-def _collect_content_layout(
- layout: List[Dict], app: "lightning.app.LightningApp", flow: "lightning.app.LightningFlow"
-) -> Union[List[Dict], Dict]:
- """Process the layout returned by the ``configure_layout()`` method if the returned format represents an
- aggregation of child layouts."""
- for entry in layout:
- if "content" not in entry:
- raise ValueError(
- f"A dictionary returned by `{flow.__class__.__name__}.configure_layout()` is missing a key 'content'."
- f" For the value, choose either a reference to a child flow or a URla."
- )
- if isinstance(entry["content"], str): # assume this is a URL
- url = entry["content"]
- if url.startswith("/"):
- # The URL isn't fully defined yet. Looks something like ``self.work.url + /something``.
- entry["target"] = ""
- else:
- entry["target"] = url
- if url.startswith("http://") and is_running_in_cloud():
- warnings.warn(
- f"You configured an http link {url[:32]}... but it won't be accessible in the cloud."
- f" Consider replacing 'http' with 'https' in the link above."
- )
-
- elif isinstance(entry["content"], lightning.app.LightningFlow):
- entry["content"] = entry["content"].name
-
- elif isinstance(entry["content"], lightning.app.LightningWork):
- work = entry["content"]
- work_layout = _collect_work_layout(work)
-
- if work_layout is None:
- entry["content"] = ""
- elif isinstance(work_layout, str):
- entry["content"] = work_layout
- entry["target"] = work_layout
- elif isinstance(work_layout, (Frontend, _MagicMockJsonSerializable)):
- if len(layout) > 1:
- lines = _add_comment_to_literal_code(
- flow.configure_layout, contains="return", comment=" <------- this guy"
- )
- m = f"""
- The return value of configure_layout() in `{flow.__class__.__name__}` is an
- unsupported format:
- \n{lines}
-
- The tab containing a `{work.__class__.__name__}` must be the only tab in the
- layout of this flow.
-
- (see the docs for `LightningWork.configure_layout`).
- """
- raise TypeError(m)
-
- if isinstance(work_layout, Frontend):
- # If the work returned a frontend, treat it as belonging to the flow.
- # NOTE: This could evolve in the future to run the Frontend directly in the work machine.
- frontend = work_layout
- frontend.flow = flow
- elif isinstance(work_layout, _MagicMockJsonSerializable):
- # The import was mocked, we set a dummy `Frontend` so that `is_headless` knows there is a UI.
- frontend = "mock"
-
- app.frontends.setdefault(flow.name, frontend)
- return flow._layout
-
- elif isinstance(entry["content"], _MagicMockJsonSerializable):
- # The import was mocked, we just record dummy content so that `is_headless` knows there is a UI
- entry["content"] = "mock"
- entry["target"] = "mock"
- else:
- m = f"""
- A dictionary returned by `{flow.__class__.__name__}.configure_layout()` contains an unsupported entry.
-
- {{'content': {repr(entry["content"])}}}
-
- Set the `content` key to a child flow or a URL, for example:
-
- class {flow.__class__.__name__}(LightningFlow):
- def configure_layout(self):
- return {{'content': childFlow OR childWork OR 'http://some/url'}}
- """
- raise ValueError(m)
- return layout
-
-
-def _collect_work_layout(work: "lightning.app.LightningWork") -> Union[None, str, Frontend, _MagicMockJsonSerializable]:
- """Check if ``configure_layout`` is overridden on the given work and return the work layout (either a string, a
- ``Frontend`` object, or an instance of a mocked import).
-
- Args:
- work: The work to collect the layout for.
-
- Raises:
- TypeError: If the value returned by ``configure_layout`` is not of a supported format.
-
- """
- work_layout = work.configure_layout() if is_overridden("configure_layout", work) else work.url
-
- if work_layout is None:
- return None
- if isinstance(work_layout, str):
- url = work_layout
- # The URL isn't fully defined yet. Looks something like ``self.work.url + /something``.
- if url and not url.startswith("/"):
- return url
- return ""
- if isinstance(work_layout, (Frontend, _MagicMockJsonSerializable)):
- return work_layout
- raise TypeError(
- f"""
- The value returned by `{work.__class__.__name__}.configure_layout()` is of an unsupported type.
-
- {repr(work_layout)}
-
- Return a `Frontend` or a URL string, for example:
-
- class {work.__class__.__name__}(LightningWork):
- def configure_layout(self):
- return MyFrontend() OR 'http://some/url'
- """
- )
diff --git a/src/lightning/app/utilities/load_app.py b/src/lightning/app/utilities/load_app.py
deleted file mode 100644
index b2016194bfe57..0000000000000
--- a/src/lightning/app/utilities/load_app.py
+++ /dev/null
@@ -1,304 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import inspect
-import os
-import sys
-import traceback
-import types
-from contextlib import contextmanager
-from copy import copy
-from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union
-
-from lightning.app.utilities.exceptions import MisconfigurationException
-
-if TYPE_CHECKING:
- from lightning.app.core import LightningApp, LightningFlow, LightningWork
- from lightning.app.plugin.plugin import LightningPlugin
-
-from lightning.app.utilities.app_helpers import Logger, _mock_missing_imports
-
-logger = Logger(__name__)
-
-
-def _prettifiy_exception(filepath: str):
- """Pretty print the exception that occurred when loading the app."""
- # we want to format the exception as if no frame was on top.
- exp, val, tb = sys.exc_info()
- listing = traceback.format_exception(exp, val, tb)
- # remove the entry for the first frame
- del listing[1]
- listing = [
- f"Found an exception when loading your application from {filepath}. Please, resolve it to run your app.\n\n"
- ] + listing
- logger.error("".join(listing))
- sys.exit(1)
-
-
-def _load_objects_from_file(
- filepath: str,
- target_type: Type,
- raise_exception: bool = False,
- mock_imports: bool = False,
- env_vars: Dict[str, str] = {},
-) -> Tuple[List[Any], types.ModuleType]:
- """Load all of the top-level objects of the given type from a file.
-
- Args:
- filepath: The file to load from.
- target_type: The type of object to load.
- raise_exception: If ``True`` exceptions will be raised, otherwise exceptions will trigger system exit.
- mock_imports: If ``True`` imports of missing packages will be replaced with a mock. This can allow the object to
- be loaded without installing dependencies.
-
- """
-
- # Taken from StreamLit: https://github.com/streamlit/streamlit/blob/develop/lib/streamlit/script_runner.py#L313
-
- # In order for imports to work in a non-package, Python normally adds the current working directory to the
- # system path, not however when running from an entry point like the `lightning` CLI command. So we do it manually:
- with _patch_sys_path(os.path.dirname(os.path.abspath(filepath))):
- code = _create_code(filepath)
- with _create_fake_main_module(filepath) as module:
- try:
- with _add_to_env(env_vars), _patch_sys_argv():
- if mock_imports:
- with _mock_missing_imports():
- exec(code, module.__dict__) # noqa: S102
- else:
- exec(code, module.__dict__) # noqa: S102
- except Exception as ex:
- if raise_exception:
- raise ex
- _prettifiy_exception(filepath)
-
- return [v for v in module.__dict__.values() if isinstance(v, target_type)], module
-
-
-def _load_plugin_from_file(filepath: str) -> "LightningPlugin":
- from lightning.app.plugin.plugin import LightningPlugin
-
- # TODO: Plugin should be run in the context of the created main module here
- plugins, _ = _load_objects_from_file(filepath, LightningPlugin, raise_exception=True, mock_imports=False)
-
- if len(plugins) > 1:
- raise RuntimeError(f"There should not be multiple plugins instantiated within the file. Found {plugins}")
- if len(plugins) == 1:
- return plugins[0]
-
- raise RuntimeError(f"The provided file {filepath} does not contain a Plugin.")
-
-
-def load_app_from_file(
- filepath: str,
- raise_exception: bool = False,
- mock_imports: bool = False,
- env_vars: Dict[str, str] = {},
-) -> "LightningApp":
- """Load a LightningApp from a file.
-
- Arguments:
- filepath: The path to the file containing the LightningApp.
- raise_exception: If True, raise an exception if the app cannot be loaded.
-
- """
- from lightning.app.core.app import LightningApp
-
- apps, main_module = _load_objects_from_file(
- filepath, LightningApp, raise_exception=raise_exception, mock_imports=mock_imports, env_vars=env_vars
- )
-
- # TODO: Remove this, downstream code shouldn't depend on side-effects here but it does
- sys.path.append(os.path.dirname(os.path.abspath(filepath)))
- sys.modules["__main__"] = main_module
-
- if len(apps) > 1:
- raise MisconfigurationException(f"There should not be multiple apps instantiated within a file. Found {apps}")
- if len(apps) == 1:
- return apps[0]
-
- raise MisconfigurationException(
- f"The provided file {filepath} does not contain a LightningApp. Instantiate your app at the module level"
- " like so: `app = LightningApp(flow, ...)`"
- )
-
-
-def _new_module(name):
- """Create a new module with the given name."""
- return types.ModuleType(name)
-
-
-def open_python_file(filename):
- """Open a read-only Python file taking proper care of its encoding.
-
- In Python 3, we would like all files to be opened with utf-8 encoding. However, some author like to specify PEP263
- headers in their source files with their own encodings. In that case, we should respect the author's encoding.
-
- """
- import tokenize
-
- if hasattr(tokenize, "open"): # Added in Python 3.2
- # Open file respecting PEP263 encoding. If no encoding header is
- # found, opens as utf-8.
- return tokenize.open(filename)
- return open(filename, encoding="utf-8") # noqa: SIM115
-
-
-def _create_code(script_path: str):
- with open_python_file(script_path) as f:
- filebody = f.read()
-
- return compile(
- filebody,
- # Pass in the file path so it can show up in exceptions.
- script_path,
- # We're compiling entire blocks of Python, so we need "exec"
- # mode (as opposed to "eval" or "single").
- mode="exec",
- # Don't inherit any flags or "future" statements.
- flags=0,
- dont_inherit=1,
- # Use the default optimization options.
- optimize=-1,
- )
-
-
-@contextmanager
-def _create_fake_main_module(script_path):
- # Create fake module. This gives us a name global namespace to
- # execute the code in.
- module = _new_module("__main__")
-
- # Install the fake module as the __main__ module. This allows
- # the pickle module to work inside the user's code, since it now
- # can know the module where the pickled objects stem from.
- # IMPORTANT: This means we can't use "if __name__ == '__main__'" in
- # our code, as it will point to the wrong module!!!
- old_main_module = sys.modules["__main__"]
- sys.modules["__main__"] = module
-
- # Add special variables to the module's globals dict.
- # Note: The following is a requirement for the CodeHasher to
- # work correctly. The CodeHasher is scoped to
- # files contained in the directory of __main__.__file__, which we
- # assume is the main script directory.
- module.__dict__["__file__"] = os.path.abspath(script_path)
-
- try:
- yield module
- finally:
- sys.modules["__main__"] = old_main_module
-
-
-@contextmanager
-def _patch_sys_path(append):
- """A context manager that appends the given value to the path once entered.
-
- Args:
- append: The value to append to the path.
-
- """
- if append in sys.path:
- yield
- return
-
- sys.path.append(append)
-
- try:
- yield
- finally:
- sys.path.remove(append)
-
-
-@contextmanager
-def _add_to_env(envs: Dict[str, str]):
- """This function adds the given environment variables to the current environment."""
- original_envs = dict(os.environ)
- os.environ.update(envs)
-
- try:
- yield
- finally:
- os.environ.clear()
- os.environ.update(original_envs)
-
-
-@contextmanager
-def _patch_sys_argv():
- """This function modifies the ``sys.argv`` by extracting the arguments after ``--app_args`` and removed everything
- else before executing the user app script.
-
- The command: ``lightning_app run app app.py --without-server --app_args --use_gpu --env ...`` will be converted into
- ``app.py --use_gpu``
-
- """
- from lightning.app.cli.lightning_cli import run_app
-
- original_argv = copy(sys.argv)
- # 1: Remove the CLI command
- if sys.argv[:3] == ["lightning", "run", "app"]:
- sys.argv = sys.argv[3:]
-
- if "--app_args" not in sys.argv:
- # 2: If app_args wasn't used, there is no arguments, so we assign the shorten arguments.
- new_argv = sys.argv[:1]
- else:
- # 3: Collect all the arguments from the CLI
- options = [p.opts[0] for p in run_app.params[1:] if p.opts[0] != "--app_args"]
- argv_slice = sys.argv
- # 4: Find the index of `app_args`
- first_index = argv_slice.index("--app_args") + 1
- # 5: Find the next argument from the CLI if any.
- matches = [
- argv_slice.index(opt) for opt in options if opt in argv_slice and argv_slice.index(opt) >= first_index
- ]
- last_index = len(argv_slice) if not matches else min(matches)
- # 6: last_index is either the fully command or the latest match from the CLI options.
- new_argv = [argv_slice[0]] + argv_slice[first_index:last_index]
-
- # 7: Patch the command
- sys.argv = new_argv
-
- try:
- yield
- finally:
- # 8: Restore the command
- sys.argv = original_argv
-
-
-def component_to_metadata(obj: Union["LightningWork", "LightningFlow"]) -> Dict:
- from lightning.app.core import LightningWork
-
- extras = {}
-
- if isinstance(obj, LightningWork):
- extras = {
- "local_build_config": obj.local_build_config.to_dict(),
- "cloud_build_config": obj.cloud_build_config.to_dict(),
- "cloud_compute": obj.cloud_compute.to_dict(),
- }
-
- return dict(
- affiliation=obj.name.split("."),
- cls_name=obj.__class__.__name__,
- module=obj.__module__,
- docstring=inspect.getdoc(obj.__init__),
- **extras,
- )
-
-
-def extract_metadata_from_app(app: "LightningApp") -> List:
- metadata = {flow.name: component_to_metadata(flow) for flow in app.flows}
- metadata.update({work.name: component_to_metadata(work) for work in app.works})
- return [metadata[key] for key in sorted(metadata.keys())]
diff --git a/src/lightning/app/utilities/log.py b/src/lightning/app/utilities/log.py
deleted file mode 100644
index 0e4f94e292517..0000000000000
--- a/src/lightning/app/utilities/log.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from pathlib import Path
-
-from lightning.app.storage.path import _storage_root_dir
-
-
-def get_logfile(filename: str = "logs.log") -> Path:
- log_dir = Path(_storage_root_dir(), "frontend")
- log_dir.mkdir(parents=True, exist_ok=True)
- return log_dir / filename
diff --git a/src/lightning/app/utilities/log_helpers.py b/src/lightning/app/utilities/log_helpers.py
deleted file mode 100644
index b7777d94e49e4..0000000000000
--- a/src/lightning/app/utilities/log_helpers.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dataclasses import dataclass
-from datetime import datetime
-from json import JSONDecodeError
-
-from websocket import WebSocketApp
-
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-# This is a superclass to inherit log entry classes from it:
-# it implements magic methods to sort logs by timestamps.
-@dataclass
-class _OrderedLogEntry:
- message: str
- timestamp: datetime
-
- def __ge__(self, other: "_OrderedLogEntry") -> bool:
- return self.timestamp >= other.timestamp
-
- def __gt__(self, other: "_OrderedLogEntry") -> bool:
- return self.timestamp > other.timestamp
-
-
-# A general error callback for log reading, prints most common types of possible errors.
-def _error_callback(ws_app: WebSocketApp, error: Exception):
- errors = {
- KeyError: "Malformed log message, missing key",
- JSONDecodeError: "Malformed log message",
- TypeError: "Malformed log format",
- ValueError: "Malformed date format",
- }
- logger.error(f"⚡ Error while reading logs ({errors.get(type(error), 'Unknown')}), {error}")
- ws_app.close()
diff --git a/src/lightning/app/utilities/login.py b/src/lightning/app/utilities/login.py
deleted file mode 100644
index eb03585eddc03..0000000000000
--- a/src/lightning/app/utilities/login.py
+++ /dev/null
@@ -1,213 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import base64
-import json
-import os
-import pathlib
-from dataclasses import dataclass
-from enum import Enum
-from time import sleep
-from typing import Optional
-from urllib.parse import urlencode
-
-import click
-import requests
-import uvicorn
-from fastapi import FastAPI, Query, Request
-from starlette.background import BackgroundTask
-from starlette.responses import RedirectResponse
-
-from lightning.app.core.constants import LIGHTNING_CREDENTIAL_PATH, get_lightning_cloud_url
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.network import find_free_network_port
-
-logger = Logger(__name__)
-
-
-class Keys(Enum):
- USERNAME = "LIGHTNING_USERNAME"
- USER_ID = "LIGHTNING_USER_ID"
- API_KEY = "LIGHTNING_API_KEY"
-
- @property
- def suffix(self):
- return self.value.lstrip("LIGHTNING_").lower()
-
-
-@dataclass
-class Auth:
- username: Optional[str] = None
- user_id: Optional[str] = None
- api_key: Optional[str] = None
-
- secrets_file = pathlib.Path(LIGHTNING_CREDENTIAL_PATH)
-
- def load(self) -> bool:
- """Load credentials from disk and update properties with credentials.
-
- Returns
- ----------
- True if credentials are available.
-
- """
- if not self.secrets_file.exists():
- logger.debug("Credentials file not found.")
- return False
- with self.secrets_file.open() as creds:
- credentials = json.load(creds)
- for key in Keys:
- setattr(self, key.suffix, credentials.get(key.suffix, None))
- return True
-
- def save(self, token: str = "", user_id: str = "", api_key: str = "", username: str = "") -> None:
- """Save credentials to disk."""
- self.secrets_file.parent.mkdir(exist_ok=True, parents=True)
- with self.secrets_file.open("w") as f:
- json.dump(
- {
- f"{Keys.USERNAME.suffix}": username,
- f"{Keys.USER_ID.suffix}": user_id,
- f"{Keys.API_KEY.suffix}": api_key,
- },
- f,
- )
-
- self.username = username
- self.user_id = user_id
- self.api_key = api_key
- logger.debug("credentials saved successfully")
-
- def clear(self) -> None:
- """Remove credentials from disk."""
- if self.secrets_file.exists():
- self.secrets_file.unlink()
- for key in Keys:
- setattr(self, key.suffix, None)
- logger.debug("credentials removed successfully")
-
- @property
- def auth_header(self) -> Optional[str]:
- """Authentication header used by lightning-cloud client."""
- if self.api_key:
- token = f"{self.user_id}:{self.api_key}"
- return f"Basic {base64.b64encode(token.encode('ascii')).decode('ascii')}" # E501
- raise AttributeError(
- "Authentication Failed, no authentication header available. "
- "This is most likely a bug in the LightningCloud Framework"
- )
-
- def _run_server(self) -> None:
- """Start a server to complete authentication."""
- AuthServer().login_with_browser(self)
-
- def authenticate(self) -> Optional[str]:
- """Performs end to end authentication flow.
-
- Returns
- ----------
- authorization header to use when authentication completes.
-
- """
- if not self.load():
- # First try to authenticate from env
- for key in Keys:
- setattr(self, key.suffix, os.environ.get(key.value, None))
-
- if self.user_id and self.api_key:
- self.save("", self.user_id, self.api_key, self.user_id)
- logger.info("Credentials loaded from environment variables")
- return self.auth_header
- if self.api_key or self.user_id:
- raise ValueError(
- "To use env vars for authentication both "
- f"{Keys.USER_ID.value} and {Keys.API_KEY.value} should be set."
- )
-
- logger.debug("failed to load credentials, opening browser to get new.")
- self._run_server()
- return self.auth_header
-
- if self.user_id and self.api_key:
- return self.auth_header
-
- raise ValueError(
- "We couldn't find any credentials linked to your account. "
- "Please try logging in using the CLI command `lightning_app login`"
- )
-
-
-class AuthServer:
- @staticmethod
- def get_auth_url(port: int) -> str:
- redirect_uri = f"http://localhost:{port}/login-complete"
- params = urlencode({"redirectTo": redirect_uri})
- return f"{get_lightning_cloud_url()}/sign-in?{params}"
-
- def login_with_browser(self, auth: Auth) -> None:
- app = FastAPI()
- port = find_free_network_port()
- url = self.get_auth_url(port)
-
- try:
- # check if server is reachable or catch any network errors
- requests.head(url)
- except requests.ConnectionError as ex:
- raise requests.ConnectionError(
- f"No internet connection available. Please connect to a stable internet connection \n{ex}" # E501
- )
- except requests.RequestException as ex:
- raise requests.RequestException(
- f"An error occurred with the request. Please report this issue to Lightning Team \n{ex}" # E501
- )
-
- logger.info(
- "\nAttempting to automatically open the login page in your default browser.\n"
- 'If the browser does not open, navigate to the "Keys" tab on your Lightning AI profile page:\n\n'
- f"{get_lightning_cloud_url()}/me/keys\n\n"
- 'Copy the "Headless CLI Login" command, and execute it in your terminal.\n'
- )
- click.launch(url)
-
- @app.get("/login-complete")
- async def save_token(request: Request, token="", key="", user_id: str = Query("", alias="userID")):
- async def stop_server_once_request_is_done():
- while not await request.is_disconnected():
- sleep(0.25)
- server.should_exit = True
-
- if not token:
- logger.warn(
- "Login Failed. This is most likely because you're using an older version of the CLI. \n" # E501
- "Please try to update the CLI or open an issue with this information \n" # E501
- f"expected token in {request.query_params.items()}"
- )
- return RedirectResponse(
- url=f"{get_lightning_cloud_url()}/cli-login-failed",
- background=BackgroundTask(stop_server_once_request_is_done),
- )
-
- auth.save(token=token, username=user_id, user_id=user_id, api_key=key)
- logger.info("Login Successful")
-
- # Include the credentials in the redirect so that UI will also be logged in
- params = urlencode({"token": token, "key": key, "userID": user_id})
-
- return RedirectResponse(
- url=f"{get_lightning_cloud_url()}/cli-login-successful?{params}",
- background=BackgroundTask(stop_server_once_request_is_done),
- )
-
- server = uvicorn.Server(config=uvicorn.Config(app, port=port, log_level="error"))
- server.run()
diff --git a/src/lightning/app/utilities/logs_socket_api.py b/src/lightning/app/utilities/logs_socket_api.py
deleted file mode 100644
index 9d25c82ba72d3..0000000000000
--- a/src/lightning/app/utilities/logs_socket_api.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Callable, Optional
-from urllib.parse import urlparse
-
-from websocket import WebSocketApp
-
-from lightning.app.utilities.auth import _AuthTokenGetter
-
-
-class _LightningLogsSocketAPI(_AuthTokenGetter):
- @staticmethod
- def _app_logs_socket_url(host: str, project_id: str, app_id: str, token: str, component: str) -> str:
- return (
- f"wss://{host}/v1/projects/{project_id}/appinstances/{app_id}/logs?"
- f"token={token}&component={component}&follow=true"
- )
-
- def create_lightning_logs_socket(
- self,
- project_id: str,
- app_id: str,
- component: str,
- on_message_callback: Callable,
- on_error_callback: Optional[Callable] = None,
- ) -> WebSocketApp:
- """Creates and returns WebSocketApp to listen to lightning app logs.
-
- .. code-block:: python
- # Synchronous reading, run_forever() is blocking
-
-
- def print_log_msg(ws_app, msg):
- print(msg)
-
-
- flow_logs_socket = client.create_lightning_logs_socket("project_id", "app_id", "flow", print_log_msg)
- flow_socket.run_forever()
-
- .. code-block:: python
- # Asynchronous reading (with Threads)
-
-
- def print_log_msg(ws_app, msg):
- print(msg)
-
-
- flow_logs_socket = client.create_lightning_logs_socket("project_id", "app_id", "flow", print_log_msg)
- work_logs_socket = client.create_lightning_logs_socket("project_id", "app_id", "work_1", print_log_msg)
-
- flow_logs_thread = Thread(target=flow_logs_socket.run_forever)
- work_logs_thread = Thread(target=work_logs_socket.run_forever)
-
- flow_logs_thread.start()
- work_logs_thread.start()
- # .......
-
- flow_logs_socket.close()
- work_logs_thread.close()
-
- Arguments:
- project_id: Project ID.
- app_id: Application ID.
- component: Component name eg flow.
- on_message_callback: Callback object which is called when received data.
- on_error_callback: Callback object which is called when we get error.
-
- Returns:
- WebSocketApp of the wanted socket
-
- """
- _token = self._get_api_token()
- clean_ws_host = urlparse(self.api_client.configuration.host).netloc
- socket_url = self._app_logs_socket_url(
- host=clean_ws_host,
- project_id=project_id,
- app_id=app_id,
- token=_token,
- component=component,
- )
-
- return WebSocketApp(socket_url, on_message=on_message_callback, on_error=on_error_callback)
diff --git a/src/lightning/app/utilities/name_generator.py b/src/lightning/app/utilities/name_generator.py
deleted file mode 100644
index c57a65f63a3d8..0000000000000
--- a/src/lightning/app/utilities/name_generator.py
+++ /dev/null
@@ -1,1359 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from random import choice, randint
-
-_adjectives = [
- # Appearance, sound, smell...
- "acrid",
- "ambrosial",
- "amorphous",
- "armored",
- "aromatic",
- "bald",
- "blazing",
- "boisterous",
- "bouncy",
- "brawny",
- "bulky",
- "camouflaged",
- "caped",
- "chubby",
- "curvy",
- "elastic",
- "ethereal",
- "fat",
- "feathered",
- "fiery",
- "flashy",
- "flat",
- "fluffy",
- "foamy",
- "fragrant",
- "furry",
- "fuzzy",
- "glaring",
- "hairy",
- "heavy",
- "hissing",
- "horned",
- "icy",
- "imaginary",
- "invisible",
- "lean",
- "loud",
- "loutish",
- "lumpy",
- "lush",
- "masked",
- "meaty",
- "messy",
- "misty",
- "nebulous",
- "noisy",
- "nondescript",
- "organic",
- "purring",
- "quiet",
- "quirky",
- "radiant",
- "roaring",
- "ruddy",
- "rustling",
- "screeching",
- "shaggy",
- "shapeless",
- "shiny",
- "silent",
- "silky",
- "singing",
- "skinny",
- "smooth",
- "soft",
- "spicy",
- "spiked",
- "statuesque",
- "sticky",
- "tacky",
- "tall",
- "tangible",
- "tentacled",
- "thick",
- "thundering",
- "venomous",
- "warm",
- "weightless",
- "whispering",
- "winged",
- "wooden",
- # Beauty & Charm",
- "adorable",
- "affable",
- "amazing",
- "amiable",
- "attractive",
- "beautiful",
- "calm",
- "charming",
- "cherubic",
- "classic",
- "classy",
- "convivial",
- "cordial",
- "cuddly",
- "curly",
- "cute",
- "debonair",
- "elegant",
- "famous",
- "fresh",
- "friendly",
- "funny",
- "gorgeous",
- "graceful",
- "gregarious",
- "grinning",
- "handsome",
- "hilarious",
- "hot",
- "interesting",
- "kind",
- "laughing",
- "lovely",
- "meek",
- "mellow",
- "merciful",
- "neat",
- "nifty",
- "notorious",
- "poetic",
- "pretty",
- "refined",
- "refreshing",
- "sexy",
- "smiling",
- "sociable",
- "spiffy",
- "stylish",
- "sweet",
- "tactful",
- "whimsical",
- "boring",
- # Character & Emotions",
- "abiding",
- "accurate",
- "adamant",
- "adaptable",
- "adventurous",
- "alluring",
- "aloof",
- "ambitious",
- "amusing",
- "annoying",
- "arrogant",
- "aspiring",
- "belligerent",
- "benign",
- "berserk",
- "benevolent",
- "bold",
- "brave",
- "cheerful",
- "chirpy",
- "cocky",
- "congenial",
- "courageous",
- "cryptic",
- "curious",
- "daft",
- "dainty",
- "daring",
- "defiant",
- "delicate",
- "delightful",
- "determined",
- "devout",
- "didactic",
- "diligent",
- "discreet",
- "dramatic",
- "dynamic",
- "eager",
- "eccentric",
- "elated",
- "encouraging",
- "enigmatic",
- "enthusiastic",
- "evasive",
- "faithful",
- "fair",
- "fanatic",
- "fearless",
- "fervent",
- "festive",
- "fierce",
- "fine",
- "free",
- "gabby",
- "garrulous",
- "gay",
- "gentle",
- "glistening",
- "greedy",
- "grumpy",
- "happy",
- "honest",
- "hopeful",
- "hospitable",
- "impetuous",
- "independent",
- "industrious",
- "innocent",
- "intrepid",
- "jolly",
- "jovial",
- "just",
- "lively",
- "loose",
- "loyal",
- "merry",
- "modest",
- "mysterious",
- "nice",
- "obedient",
- "optimistic",
- "orthodox",
- "outgoing",
- "outrageous",
- "overjoyed",
- "passionate",
- "perky",
- "placid",
- "polite",
- "positive",
- "proud",
- "prudent",
- "puzzling",
- "quixotic",
- "quizzical",
- "rebel",
- "resolute",
- "rampant",
- "righteous",
- "romantic",
- "rough",
- "rousing",
- "sassy",
- "satisfied",
- "sly",
- "sincere",
- "snobbish",
- "spirited",
- "spry",
- "stalwart",
- "stirring",
- "swinging",
- "tasteful",
- "thankful",
- "tidy",
- "tremendous",
- "truthful",
- "unselfish",
- "upbeat",
- "uppish",
- "valiant",
- "vehement",
- "vengeful",
- "vigorous",
- "vivacious",
- "zealous",
- "zippy",
- # Intelligence & Abilities",
- "able",
- "adept",
- "analytic",
- "astute",
- "attentive",
- "brainy",
- "busy",
- "calculating",
- "capable",
- "careful",
- "cautious",
- "certain",
- "clever",
- "competent",
- "conscious",
- "cooperative",
- "crafty",
- "crazy",
- "cunning",
- "daffy",
- "devious",
- "discerning",
- "efficient",
- "expert",
- "functional",
- "gifted",
- "helpful",
- "enlightened",
- "idealistic",
- "impartial",
- "industrious",
- "ingenious",
- "inquisitive",
- "intelligent",
- "inventive",
- "judicious",
- "keen",
- "knowing",
- "literate",
- "logical",
- "masterful",
- "mindful",
- "nonchalant",
- "observant",
- "omniscient",
- "poised",
- "practical",
- "pragmatic",
- "proficient",
- "provocative",
- "qualified",
- "radical",
- "rational",
- "realistic",
- "resourceful",
- "savvy",
- "sceptical",
- "sensible",
- "serious",
- "shrewd",
- "skilled",
- "slick",
- "slim",
- "sloppy",
- "smart",
- "sophisticated",
- "stoic",
- "succinct",
- "talented",
- "thoughtful",
- "tricky",
- "unbiased",
- "uptight",
- "versatile",
- "versed",
- "visionary",
- "wise",
- "witty",
- # Strength & Agility",
- "accelerated",
- "active",
- "agile",
- "athletic",
- "dashing",
- "deft",
- "dexterous",
- "energetic",
- "fast",
- "frisky",
- "hasty",
- "hypersonic",
- "meteoric",
- "mighty",
- "muscular",
- "nimble",
- "nippy",
- "powerful",
- "prompt",
- "quick",
- "rapid",
- "resilient",
- "robust",
- "rugged",
- "solid",
- "speedy",
- "steadfast",
- "steady",
- "strong",
- "sturdy",
- "tireless",
- "tough",
- "unyielding",
- # Money & Power",
- "rich",
- "wealthy",
- # Science",
- "meticulous",
- "precise",
- "rigorous",
- "scrupulous",
- "strict",
- # Movement type",
- "airborne",
- "burrowing",
- "crouching",
- "flying",
- "hidden",
- "hopping",
- "jumping",
- "lurking",
- "tunneling",
- "warping",
- # Location and Dwelling",
- "aboriginal",
- "amphibian",
- "aquatic",
- "arboreal",
- "polar",
- "terrestrial",
- "urban",
- # Awesome",
- "accomplished",
- "astonishing",
- "authentic",
- "awesome",
- "delectable",
- "excellent",
- "exotic",
- "exuberant",
- "fabulous",
- "fantastic",
- "fascinating",
- "flawless",
- "fortunate",
- "funky",
- "godlike",
- "glorious",
- "groovy",
- "honored",
- "illustrious",
- "imposing",
- "important",
- "impressive",
- "incredible",
- "invaluable",
- "kickass",
- "majestic",
- "magnificent",
- "marvellous",
- "monumental",
- "perfect",
- "phenomenal",
- "pompous",
- "precious",
- "premium",
- "private",
- "remarkable",
- "spectacular",
- "splendid",
- "successful",
- "wonderful",
- "wondrous",
- # Original",
- "offbeat",
- "original",
- "outstanding",
- "quaint",
- "unique",
- # Time",
- "ancient",
- "antique",
- "prehistoric",
- "primitive",
- # Misc",
- "abstract",
- "acoustic",
- "angelic",
- "arcane",
- "archetypal",
- "augmented",
- "auspicious",
- "axiomatic",
- "beneficial",
- "bipedal",
- "bizarre",
- "complex",
- "dancing",
- "dangerous",
- "demonic",
- "divergent",
- "economic",
- "electric",
- "elite",
- "eminent",
- "enchanted",
- "esoteric",
- "finicky",
- "fractal",
- "futuristic",
- "gainful",
- "hallowed",
- "heavenly",
- "heretic",
- "holistic",
- "hungry",
- "hypnotic",
- "hysterical",
- "illegal",
- "imperial",
- "imported",
- "impossible",
- "inescapable",
- "juicy",
- "liberal",
- "ludicrous",
- "lyrical",
- "magnetic",
- "manipulative",
- "mature",
- "military",
- "macho",
- "married",
- "melodic",
- "natural",
- "naughty",
- "nocturnal",
- "nostalgic",
- "optimal",
- "pastoral",
- "peculiar",
- "piquant",
- "pristine",
- "prophetic",
- "psychedelic",
- "quantum",
- "rare",
- "real",
- "secret",
- "simple",
- "spectral",
- "spiritual",
- "stereotyped",
- "stimulating",
- "straight",
- "strange",
- "tested",
- "therapeutic",
- "true",
- "ubiquitous",
- "uncovered",
- "unnatural",
- "utopian",
- "vagabond",
- "vague",
- "vegan",
- "victorious",
- "vigilant",
- "voracious",
- "wakeful",
- "wandering",
- "watchful",
- "wild",
- # Pseudo-colors",
- "bright",
- "brilliant",
- "colorful",
- "crystal",
- "dark",
- "dazzling",
- "fluorescent",
- "glittering",
- "glossy",
- "gleaming",
- "light",
- "mottled",
- "neon",
- "opalescent",
- "pastel",
- "smoky",
- "sparkling",
- "spotted",
- "striped",
- "translucent",
- "transparent",
- "vivid",
-]
-
-# Docker, starting from 0.7.x, generated names from notable scientists and hackers.
-# Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
-_surnames = [
- # Muhammad ibn Jabir Al-Battani was a founding father of astronomy. https://en.wikipedia.org/wiki/Al-Battani
- "albattani",
- # Frances E. Allen, became the first female IBM Fellow in 1989. In 2006, she became the first female
- # recipient of the ACM's Turing Award. https://en.wikipedia.org/wiki/Frances_E._Allen
- "allen",
- # June Almeida - Scottish virologist who took the first pictures of the rubella
- # virus - https://en.wikipedia.org/wiki/June_Almeida
- "almeida",
- # Kathleen Antonelli, American computer programmer and one of the six original
- # programmers of the ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli
- "antonelli",
- # Maria Gaetana Agnesi - Italian mathematician, philosopher, theologian and humanitarian.
- # She was the first woman to write a mathematics handbook and the first woman appointed
- # as a Mathematics Professor at a University. https://en.wikipedia.org/wiki/Maria_Gaetana_Agnesi
- "agnesi",
- # Archimedes was a physicist, engineer and mathematician who invented too many
- # things to list them here. https://en.wikipedia.org/wiki/Archimedes
- "archimedes",
- # Maria Ardinghelli - Italian translator, mathematician and physicist -
- # https://en.wikipedia.org/wiki/Maria_Ardinghelli
- "ardinghelli",
- # Aryabhata - Ancient Indian mathematician-astronomer during 476-550 CE https://en.wikipedia.org/wiki/Aryabhata
- "aryabhata",
- # Wanda Austin - Wanda Austin is the President and CEO of The Aerospace Corporation,
- # a leading architect for the US security space programs. https://en.wikipedia.org/wiki/Wanda_Austin
- "austin",
- # Charles Babbage invented the concept of a programmable computer. https://en.wikipedia.org/wiki/Charles_Babbage.
- "babbage",
- # Stefan Banach - Polish mathematician, was one of the founders of modern
- # functional analysis. https://en.wikipedia.org/wiki/Stefan_Banach
- "banach",
- # Buckaroo Banzai and his mentor Dr. Hikita perfected the "oscillation overthruster",
- # a device that allows one to pass through solid matter. -
- # https://en.wikipedia.org/wiki/The_Adventures_of_Buckaroo_Banzai_Across_the_8th_Dimension
- "banzai",
- # John Bardeen co-invented the transistor - https://en.wikipedia.org/wiki/John_Bardeen
- "bardeen",
- # Jean Bartik, born Betty Jean Jennings, was one of the original programmers
- # for the ENIAC computer. https://en.wikipedia.org/wiki/Jean_Bartik
- "bartik",
- # Laura Bassi, the world's first female professor https://en.wikipedia.org/wiki/Laura_Bassi
- "bassi",
- # Hugh Beaver, British engineer, founder of the Guinness Book of World
- # Records https://en.wikipedia.org/wiki/Hugh_Beaver
- "beaver",
- # Alexander Graham Bell - an eminent Scottish-born scientist, inventor, engineer and innovator
- # who is credited with inventing the first practical telephone - https://en.wikipedia.org/wiki/Alexander_Graham_Bell
- "bell",
- # Karl Friedrich Benz - a German automobile engineer. Inventor of the first
- # practical motorcar. https://en.wikipedia.org/wiki/Karl_Benz
- "benz",
- # Homi J Bhabha - was an Indian nuclear physicist, founding director, and professor of
- # physics at the Tata Institute of Fundamental Research. Colloquially known as "father of
- # Indian nuclear programme"- https://en.wikipedia.org/wiki/Homi_J._Bhabha
- "bhabha",
- # Bhaskara II - Ancient Indian mathematician-astronomer whose work on calculus predates
- # Newton and Leibniz by over half a millennium - https://en.wikipedia.org/wiki/Bh%C4%81skara_II#Calculus
- "bhaskara",
- # Sue Black - British computer scientist and campaigner. She has been instrumental in
- # saving Bletchley Park, the site of World War II codebreaking -
- # https://en.wikipedia.org/wiki/Sue_Black_(computer_scientist)
- "black",
- # Elizabeth Helen Blackburn - Australian-American Nobel laureate; best known
- # for co-discovering telomerase. https://en.wikipedia.org/wiki/Elizabeth_Blackburn
- "blackburn",
- # Elizabeth Blackwell - American doctor and first American woman to receive a
- # medical degree - https://en.wikipedia.org/wiki/Elizabeth_Blackwell
- "blackwell",
- # Niels Bohr is the father of quantum theory. https://en.wikipedia.org/wiki/Niels_Bohr.
- "bohr",
- # Kathleen Booth, she's credited with writing the first assembly language.
- # https://en.wikipedia.org/wiki/Kathleen_Booth
- "booth",
- # Anita Borg - Anita Borg was the founding director of the Institute for
- # Women and Technology (IWT). https://en.wikipedia.org/wiki/Anita_Borg
- "borg",
- # Satyendra Nath Bose - He provided the foundation for Bose\u2013Einstein statistics
- # and the theory of the Bose\u2013Einstein condensate. - https://en.wikipedia.org/wiki/Satyendra_Nath_Bose
- "bose",
- # Katherine Louise Bouman is an imaging scientist and Assistant Professor of Computer
- # Science at the California Institute of Technology. She researches computational methods for
- # imaging, and developed an algorithm that made possible the picture first visualization of a
- # black hole using the Event Horizon Telescope. - https://en.wikipedia.org/wiki/Katie_Bouman
- "bouman",
- # Evelyn Boyd Granville - She was one of the first African-American woman to receive a Ph.D.
- # in mathematics; she earned it in 1949 from Yale University. https://en.wikipedia.org/wiki/Evelyn_Boyd_Granville
- "boyd",
- # Brahmagupta - Ancient Indian mathematician during 598-670 CE who gave rules
- # to compute with zero - https://en.wikipedia.org/wiki/Brahmagupta#Zero
- "brahmagupta",
- # Walter Houser Brattain co-invented the transistor - https://en.wikipedia.org/wiki/Walter_Houser_Brattain
- "brattain",
- # Emmett Brown invented time travel. https://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
- "brown",
- # Linda Brown Buck - American biologist and Nobel laureate best known for her genetic and
- # molecular analyses of the mechanisms of smell. https://en.wikipedia.org/wiki/Linda_B._Buck
- "buck",
- # Dame Susan Jocelyn Bell Burnell - Northern Irish astrophysicist who discovered radio pulsars
- # and was the first to analyse them. https://en.wikipedia.org/wiki/Jocelyn_Bell_Burnell
- "burnell",
- # Annie Jump Cannon - pioneering female astronomer who classified hundreds of thousands of stars
- # and created the system we use to understand stars today. https://en.wikipedia.org/wiki/Annie_Jump_Cannon
- "cannon",
- # Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other
- # writings are credited with advancing the global environmental movement.
- # https://en.wikipedia.org/wiki/Rachel_Carson
- "carson",
- # Dame Mary Lucy Cartwright - British mathematician who was one of the first to study what is
- # now known as chaos theory. Also known for Cartwright's theorem which finds applications in
- # signal processing. https://en.wikipedia.org/wiki/Mary_Cartwright
- "cartwright",
- # George Washington Carver - American agricultural scientist and inventor. He was the most
- # prominent black scientist of the early 20th century. https://en.wikipedia.org/wiki/George_Washington_Carver
- "carver",
- # Vinton Gray Cerf - American Internet pioneer, recognised as one of "the fathers of the Internet".
- # With Robert Elliot Kahn, he designed TCP and IP, the primary data communication protocols of
- # the Internet and other computer networks. https://en.wikipedia.org/wiki/Vint_Cerf
- "cerf",
- # Subrahmanyan Chandrasekhar - Astrophysicist known for his mathematical theory on different
- # stages and evolution in structures of the stars. He has won nobel prize for physics -
- # https://en.wikipedia.org/wiki/Subrahmanyan_Chandrasekhar
- "chandrasekhar",
- # Sergey Alexeyevich Chaplygin (April 5, 1869 - October 8, 1942) was a Russian and Soviet physicist,
- # mathematician, and mechanical engineer. He is known for mathematical formulas such as Chaplygin's
- # equation and for a hypothetical substance in cosmology called Chaplygin gas,
- # named after him. https://en.wikipedia.org/wiki/Sergey_Chaplygin
- "chaplygin",
- # Emilie du Chatelet - French natural philosopher, mathematician, physicist, and author
- # during the early 1730s, known for her translation of and commentary on Isaac Newton's book
- # Principia containing basic laws of physics. https://en.wikipedia.org/wiki/%C3%89milie_du_Ch%C3%A2telet
- "chatelet",
- # Asima Chatterjee was an Indian organic chemist noted for her research on vinca alkaloids,
- # development of drugs for treatment of epilepsy and malaria - https://en.wikipedia.org/wiki/Asima_Chatterjee
- "chatterjee",
- # Pafnuty Chebyshev - Russian mathematician. He is known fo his works on probability,
- # statistics, mechanics, analytical geometry and number theory https://en.wikipedia.org/wiki/Pafnuty_Chebyshev
- "chebyshev",
- # Bram Cohen - American computer programmer and author of the BitTorrent
- # peer-to-peer protocol. https://en.wikipedia.org/wiki/Bram_Cohen
- "cohen",
- # David Lee Chaum - American computer scientist and cryptographer. Known for his
- # seminal contributions in the field of anonymous communication. https://en.wikipedia.org/wiki/David_Chaum
- "chaum",
- # Joan Clarke - Bletchley Park code breaker during the Second World War who pioneered techniques
- # that remained top secret for decades. Also an accomplished numismatist https://en.wikipedia.org/wiki/Joan_Clarke
- "clarke",
- # Jane Colden - American botanist widely considered the first female
- # American botanist - https://en.wikipedia.org/wiki/Jane_Colden
- "colden",
- # Gerty Theresa Cori - American biochemist who became the third woman and first American woman to win a
- # Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology
- # or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori
- "cori",
- # Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series
- # of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray
- "cray",
- # This entry reflects a husband and wife team who worked together:
- # Joan Curran was a Welsh scientist who developed radar and invented chaff, a radar countermeasure.
- # https://en.wikipedia.org/wiki/Joan_Curran Samuel Curran was an Irish physicist who worked
- # alongside his wife during WWII and invented the proximity fuse. https://en.wikipedia.org/wiki/Samuel_Curran
- "curran",
- # Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie.
- "curie",
- # Charles Darwin established the principles of natural evolution. https://en.wikipedia.org/wiki/Charles_Darwin.
- "darwin",
- # Leonardo Da Vinci invented too many things to list here. https://en.wikipedia.org/wiki/Leonardo_da_Vinci.
- "davinci",
- # A. K. (Alexander Keewatin) Dewdney, Canadian mathematician, computer scientist, author and filmmaker.
- # Contributor to Scientific American's "Computer Recreations" from 1984 to 1991. Author of Core War (program),
- # The Planiverse, The Armchair Universe, The Magic Machine, The New Turing Omnibus, and more.
- # https://en.wikipedia.org/wiki/Alexander_Dewdney
- "dewdney",
- # Satish Dhawan - Indian mathematician and aerospace engineer, known for leading the successful and
- # indigenous development of the Indian space programme. https://en.wikipedia.org/wiki/Satish_Dhawan
- "dhawan",
- # Bailey Whitfield Diffie - American cryptographer and one of the pioneers of
- # public-key cryptography. https://en.wikipedia.org/wiki/Whitfield_Diffie
- "diffie",
- # Edsger Wybe Dijkstra was a Dutch computer scientist and mathematical scientist.
- # https://en.wikipedia.org/wiki/Edsger_W._Dijkstra.
- "dijkstra",
- # Paul Adrien Maurice Dirac - English theoretical physicist who made fundamental contributions to the
- # early development of both quantum mechanics and quantum electrodynamics. https://en.wikipedia.org/wiki/Paul_Dirac
- "dirac",
- # Agnes Meyer Driscoll - American cryptanalyst during World Wars I and II who successfully cryptanalysed a
- # number of Japanese ciphers. She was also the co-developer of one of the cipher machines of
- # the US Navy, the CM. https://en.wikipedia.org/wiki/Agnes_Meyer_Driscoll
- "driscoll",
- # Donna Dubinsky - played an integral role in the development of personal digital assistants (PDAs)
- # serving as CEO of Palm, Inc. and co-founding Handspring. https://en.wikipedia.org/wiki/Donna_Dubinsky
- "dubinsky",
- # Annie Easley - She was a leading member of the team which developed software for the Centaur
- # rocket stage and one of the first African-Americans in her field. https://en.wikipedia.org/wiki/Annie_Easley
- "easley",
- # Thomas Alva Edison, prolific inventor https://en.wikipedia.org/wiki/Thomas_Edison
- "edison",
- # Albert Einstein invented the general theory of relativity. https://en.wikipedia.org/wiki/Albert_Einstein
- "einstein",
- # Alexandra Asanovna Elbakyan is a Kazakhstani graduate student, computer programmer, internet pirate in
- # hiding, and the creator of the site Sci-Hub. Nature has listed her in 2016 in the top ten people that
- # mattered in science, and Ars Technica has compared her to Aaron Swartz. -
- # https://en.wikipedia.org/wiki/Alexandra_Elbakyan
- "elbakyan",
- # Taher A. ElGamal - Egyptian cryptographer best known for the ElGamal discrete log cryptosystem and the
- # ElGamal digital signature scheme. https://en.wikipedia.org/wiki/Taher_Elgamal
- "elgamal",
- # Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the
- # Nobel Prize in Medicine - https://en.wikipedia.org/wiki/Gertrude_Elion
- "elion",
- # James Henry Ellis - British engineer and cryptographer employed by the GCHQ. Best known for
- # conceiving for the first time, the idea of public-key cryptography. https://en.wikipedia.org/wiki/James_H._Ellis
- "ellis",
- # Douglas Engelbart gave the mother of all demos: https://en.wikipedia.org/wiki/Douglas_Engelbart
- "engelbart",
- # Euclid invented geometry. https://en.wikipedia.org/wiki/Euclid
- "euclid",
- # Leonhard Euler invented large parts of modern mathematics. https://de.wikipedia.org/wiki/Leonhard_Euler
- "euler",
- # Michael Faraday - British scientist who contributed to the study of electromagnetism and
- # electrochemistry. https://en.wikipedia.org/wiki/Michael_Faraday
- "faraday",
- # Horst Feistel - German-born American cryptographer who was one of the earliest non-government
- # researchers to study the design and theory of block ciphers. Co-developer of DES and Lucifer.
- # Feistel networks, a symmetric structure used in the construction of block ciphers are named after him.
- # https://en.wikipedia.org/wiki/Horst_Feistel
- "feistel",
- # Pierre de Fermat pioneered several aspects of modern mathematics. https://en.wikipedia.org/wiki/Pierre_de_Fermat
- "fermat",
- # Enrico Fermi invented the first nuclear reactor. https://en.wikipedia.org/wiki/Enrico_Fermi.
- "fermi",
- # Richard Feynman was a key contributor to quantum mechanics and particle physics.
- # https://en.wikipedia.org/wiki/Richard_Feynman
- "feynman",
- # Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
- "franklin",
- # Yuri Alekseyevich Gagarin - Soviet pilot and cosmonaut, best known as the first human to
- # journey into outer space. https://en.wikipedia.org/wiki/Yuri_Gagarin
- "gagarin",
- # Galileo was a founding father of modern astronomy, and faced politics and obscurantism to
- # establish scientific truth. https://en.wikipedia.org/wiki/Galileo_Galilei
- "galileo",
- # Evariste Galois - French mathematician whose work laid the foundations of Galois theory and group theory,
- # two major branches of abstract algebra, and the subfield of Galois connections, all while still in
- # his late teens. https://en.wikipedia.org/wiki/%C3%89variste_Galois
- "galois",
- # Kadambini Ganguly - Indian physician, known for being the first South Asian female physician,
- # trained in western medicine, to graduate in South Asia. https://en.wikipedia.org/wiki/Kadambini_Ganguly
- "ganguly",
- # William Henry "Bill" Gates III is an American business magnate, philanthropist, investor,
- # computer programmer, and inventor. https://en.wikipedia.org/wiki/Bill_Gates
- "gates",
- # Johann Carl Friedrich Gauss - German mathematician who made significant contributions to many fields,
- # including number theory, algebra, statistics, analysis, differential geometry, geodesy, geophysics, mechanics,
- # electrostatics, magnetic fields, astronomy, matrix theory, and optics.
- # https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss
- "gauss",
- # Marie-Sophie Germain - French mathematician, physicist and philosopher. Known for her work o
- # n elasticity theory, number theory and philosophy. https://en.wikipedia.org/wiki/Sophie_Germain
- "germain",
- # Adele Goldberg, was one of the designers and developers of the Smalltalk language.
- # https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist)
- "goldberg",
- # Adele Goldstine, born Adele Katz, wrote the complete technical description for the first electronic
- # digital computer, ENIAC. https://en.wikipedia.org/wiki/Adele_Goldstine
- "goldstine",
- # Shafi Goldwasser is a computer scientist known for creating theoretical foundations of modern
- # cryptography. Winner of 2012 ACM Turing Award. https://en.wikipedia.org/wiki/Shafi_Goldwasser
- "goldwasser",
- # James Golick, all around gangster.
- "golick",
- # Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the
- # world's foremost expert on chimpanzees - https://en.wikipedia.org/wiki/Jane_Goodall
- "goodall",
- # Stephen Jay Gould was was an American paleontologist, evolutionary biologist, and historian of science.
- # He is most famous for the theory of punctuated equilibrium - https://en.wikipedia.org/wiki/Stephen_Jay_Gould
- "gould",
- # Carolyn Widney Greider - American molecular biologist and joint winner of the 2009 Nobel Prize for
- # Physiology or Medicine for the discovery of telomerase. https://en.wikipedia.org/wiki/Carol_W._Greider
- "greider",
- # Alexander Grothendieck - German-born French mathematician who became a leading figure in the creation
- # of modern algebraic geometry. https://en.wikipedia.org/wiki/Alexander_Grothendieck
- "grothendieck",
- # Lois Haibt - American computer scientist, part of the team at IBM that developed FORTRAN -
- # https://en.wikipedia.org/wiki/Lois_Haibt
- "haibt",
- # Margaret Hamilton - Director of the Software Engineering Division of the MIT Instrumentation Laboratory,
- # which developed on-board flight software for the Apollo space program.
- # https://en.wikipedia.org/wiki/Margaret_Hamilton_(scientist)
- "hamilton",
- # Caroline Harriet Haslett - English electrical engineer, electricity industry administrator and champion of
- # women's rights. Co-author of British Standard 1363 that specifies AC power plugs and sockets used across
- # the United Kingdom (which is widely considered as one of the safest designs).
- # https://en.wikipedia.org/wiki/Caroline_Haslett
- "haslett",
- # Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics.
- # https://en.wikipedia.org/wiki/Stephen_Hawking
- "hawking",
- # Martin Edward Hellman - American cryptologist, best known for his invention of public-key cryptography
- # in co-operation with Whitfield Diffie and Ralph Merkle. https://en.wikipedia.org/wiki/Martin_Hellman
- "hellman",
- # Werner Heisenberg was a founding father of quantum mechanics. https://en.wikipedia.org/wiki/Werner_Heisenberg
- "heisenberg",
- # Grete Hermann was a German philosopher noted for her philosophical work on the foundations of quantum mechanics.
- # https://en.wikipedia.org/wiki/Grete_Hermann
- "hermann",
- # Caroline Lucretia Herschel - German astronomer and discoverer of several comets.
- # https://en.wikipedia.org/wiki/Caroline_Herschel
- "herschel",
- # Heinrich Rudolf Hertz - German physicist who first conclusively proved the existence of the electromagnetic waves.
- # https://en.wikipedia.org/wiki/Heinrich_Hertz
- "hertz",
- # Jaroslav Heyrovsky was the inventor of the polarographic method, father of the electroanalytical method, and
- # recipient of the Nobel Prize in 1959. His main field of work was polarography.
- # https://en.wikipedia.org/wiki/Jaroslav_Heyrovsk%C3%BD
- "heyrovsky",
- # Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was
- # awarded the Nobel Prize in Chemistry in 1964. https://en.wikipedia.org/wiki/Dorothy_Hodgkin
- "hodgkin",
- # Douglas R. Hofstadter is an American professor of cognitive science and author of the Pulitzer Prize and American
- # Book Award-winning work Goedel, Escher, Bach: An Eternal Golden Braid in 1979. A mind-bending work which coined
- # Hofstadter's Law: "It always takes longer than you expect, even when you take into account Hofstadter's Law."
- # https://en.wikipedia.org/wiki/Douglas_Hofstadter
- "hofstadter",
- # Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephone switching method.
- # https://en.wikipedia.org/wiki/Erna_Schneider_Hoover
- "hoover",
- # Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing
- # the term "debugging" for fixing computer glitches. https://en.wikipedia.org/wiki/Grace_Hopper
- "hopper",
- # Frances Hugle, she was an American scientist, engineer, and inventor who contributed to the understanding of
- # semiconductors, integrated circuitry, and the unique electrical principles of microscopic materials.
- # https://en.wikipedia.org/wiki/Frances_Hugle
- "hugle",
- # Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics -
- # https://en.wikipedia.org/wiki/Hypatia
- "hypatia",
- # Teruko Ishizaka - Japanese scientist and immunologist who co-discovered the antibody class Immunoglobulin E.
- # https://en.wikipedia.org/wiki/Teruko_Ishizaka
- "ishizaka",
- # Mary Jackson, American mathematician and aerospace engineer who earned the highest title within NASA's engineering
- #
- # department - https://en.wikipedia.org/wiki/Mary_Jackson_(engineer)
- "jackson",
- # Yeong-Sil Jang was a Korean scientist and astronomer during the Joseon Dynasty; he invented the first metal
- # printing press and water gauge. https://en.wikipedia.org/wiki/Jang_Yeong-sil
- "jang",
- # Mae Carol Jemison - is an American engineer, physician, and former NASA astronaut. She became the first black
- # woman to travel in space when she served as a mission specialist aboard the Space Shuttle Endeavour -
- # https://en.wikipedia.org/wiki/Mae_Jemison
- "jemison",
- # Betty Jennings - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC -
- # https://en.wikipedia.org/wiki/Jean_Bartik
- "jennings",
- # Mary Lou Jepsen, was the founder and chief technology officer of One Laptop Per Child (OLPC), and the founder of
- # Pixel Qi. https://en.wikipedia.org/wiki/Mary_Lou_Jepsen
- "jepsen",
- # Katherine Coleman Goble Johnson - American physicist and mathematician contributed to the NASA.
- # https://en.wikipedia.org/wiki/Katherine_Johnson
- "johnson",
- # Irene Joliot-Curie - French scientist who was awarded the Nobel Prize for Chemistry in 1935. Daughter of Marie
- # and Pierre Curie. https://en.wikipedia.org/wiki/Ir%C3%A8ne_Joliot-Curie
- "joliot",
- # Karen Sparck Jones came up with the concept of inverse document frequency, which is used in most search engines
- # today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones
- "jones",
- # A. P. J. Abdul Kalam - is an Indian scientist aka Missile Man of India for his work on the development of
- # ballistic missile and launch vehicle technology - https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam
- "kalam",
- # Sergey Petrovich Kapitsa (14 February 1928 - 14 August 2012) was a Russian physicist and demographer. He was best
- # known as host of the popular and long-running Russian scientific TV show, Evident, but Incredible. His father was
- # the Nobel laureate Soviet-era physicist Pyotr Kapitsa, and his brother was the geographer and Antarctic explorer
- # Andrey Kapitsa. - https://en.wikipedia.org/wiki/Sergey_Kapitsa
- "kapitsa",
- # Susan Kare, created the icons and many of the interface elements for the original Apple Macintosh in the 1980s,
- # and was an original employee of NeXT, working as the Creative Director. https://en.wikipedia.org/wiki/Susan_Kare
- "kare",
- # Mstislav Keldysh - a Soviet scientist in the field of mathematics and mechanics, academician of the USSR Academy
- # of Sciences (1946), President of the USSR Academy of Sciences (1961-1975),
- # three times Hero of Socialist Labor (1956, 1961, 1971), fellow of the Royal Society of Edinburgh (1968).
- # https://en.wikipedia.org/wiki/Mstislav_Keldysh
- "keldysh",
- # Mary Kenneth Keller, Sister Mary Kenneth Keller became the first American woman to earn a
- # PhD in Computer Science in 1965. https://en.wikipedia.org/wiki/Mary_Kenneth_Keller
- "keller",
- # Johannes Kepler, German astronomer known for his three laws of planetary motion -
- # https://en.wikipedia.org/wiki/Johannes_Kepler
- "kepler",
- # Omar Khayyam - Persian mathematician, astronomer and poet. Known for his work on the classification and solution
- # of cubic equations, for his contribution to the understanding of Euclid's fifth postulate and for computing the
- # length of a year very accurately. https://en.wikipedia.org/wiki/Omar_Khayyam
- "khayyam",
- # Har Gobind Khorana - Indian-American biochemist who shared the 1968 Nobel Prize for Physiology -
- # https://en.wikipedia.org/wiki/Har_Gobind_Khorana
- "khorana",
- # Jack Kilby invented silicon integrated circuits and gave Silicon Valley its name. -
- # https://en.wikipedia.org/wiki/Jack_Kilby
- "kilby",
- # Maria Kirch - German astronomer and first woman to discover a comet -
- # https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch
- "kirch",
- # Donald Knuth - American computer scientist, author of "The Art of Computer Programming" and creator of the TeX
- # typesetting system. https://en.wikipedia.org/wiki/Donald_Knuth
- "knuth",
- # Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis,
- # differential equations and mechanics - https://en.wikipedia.org/wiki/Sofia_Kovalevskaya
- "kowalevski",
- # Marie-Jeanne de Lalande - French astronomer, mathematician and cataloguer of stars -
- # https://en.wikipedia.org/wiki/Marie-Jeanne_de_Lalande
- "lalande",
- # Hedy Lamarr - Actress and inventor. The principles of her work are now incorporated into modern Wi-Fi, CDMA
- # and Bluetooth technology. https://en.wikipedia.org/wiki/Hedy_Lamarr
- "lamarr",
- # Leslie B. Lamport - American computer scientist. Lamport is best known for his seminal work in distributed
- # systems and was the winner of the 2013 Turing Award. https://en.wikipedia.org/wiki/Leslie_Lamport
- "lamport",
- # Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull -
- # https://en.wikipedia.org/wiki/Mary_Leakey
- "leakey",
- # Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and
- # the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt
- "leavitt",
- # Esther Miriam Zimmer Lederberg - American microbiologist and a pioneer of bacterial genetics.
- # https://en.wikipedia.org/wiki/Esther_Lederberg
- "lederberg",
- # Inge Lehmann - Danish seismologist and geophysicist. Known for discovering in 1936 that the Earth has a solid
- # inner core inside a molten outer core. https://en.wikipedia.org/wiki/Inge_Lehmann
- "lehmann",
- # Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for
- # routing traffic on the internet. Died attempting to stop the 9-11 hijackers.
- # https://en.wikipedia.org/wiki/Daniel_Lewin
- "lewin",
- # Ruth Lichterman - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC -
- # https://en.wikipedia.org/wiki/Ruth_Teitelbaum
- "lichterman",
- # Barbara Liskov - co-developed the Liskov substitution principle. Liskov was also the winner of the Turing
- # Prize in 2008. - https://en.wikipedia.org/wiki/Barbara_Liskov
- "liskov",
- # Ada Lovelace invented the first algorithm. https://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
- "lovelace",
- # Auguste and Louis Lumiere - the first filmmakers in history -
- # https://en.wikipedia.org/wiki/Auguste_and_Louis_Lumi%C3%A8re
- "lumiere",
- # Mahavira - Ancient Indian mathematician during 9th century AD who discovered basic algebraic identities -
- # https://en.wikipedia.org/wiki/Mah%C4%81v%C4%ABra_(mathematician)
- "mahavira",
- # Lynn Margulis (b. Lynn Petra Alexander) - an American evolutionary theorist and biologist, science author,
- # educator, and popularizer, and was the primary modern proponent for the significance of symbiosis in evolution. -
- # https://en.wikipedia.org/wiki/Lynn_Margulis
- "margulis",
- # Yukihiro Matsumoto - Japanese computer scientist and software programmer best known as the chief designer of
- # the Ruby programming language. https://en.wikipedia.org/wiki/Yukihiro_Matsumoto
- "matsumoto",
- # James Clerk Maxwell - Scottish physicist, best known for his formulation of electromagnetic theory.
- # https://en.wikipedia.org/wiki/James_Clerk_Maxwell
- "maxwell",
- # Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model
- # of the atomic nucleus - https://en.wikipedia.org/wiki/Maria_Mayer
- "mayer",
- # John McCarthy invented LISP: https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
- "mccarthy",
- # Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for
- # discovering transposons. https://en.wikipedia.org/wiki/Barbara_McClintock
- "mcclintock",
- # Anne Laura Dorinthea McLaren - British developmental biologist whose work helped lead to human
- # in-vitro fertilisation. https://en.wikipedia.org/wiki/Anne_McLaren
- "mclaren",
- # Malcolm McLean invented the modern shipping container: https://en.wikipedia.org/wiki/Malcom_McLean
- "mclean",
- # Kay McNulty - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC -
- # https://en.wikipedia.org/wiki/Kathleen_Antonelli
- "mcnulty",
- # Gregor Johann Mendel - Czech scientist and founder of genetics. https://en.wikipedia.org/wiki/Gregor_Mendel
- "mendel",
- # Dmitri Mendeleev - a chemist and inventor. He formulated the Periodic Law, created a farsighted version of the
- # periodic table of elements, and used it to correct the properties of some already discovered elements and also
- # to predict the properties of eight elements yet to be discovered. https://en.wikipedia.org/wiki/Dmitri_Mendeleev
- "mendeleev",
- # Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element
- # meitnerium is named after her - https://en.wikipedia.org/wiki/Lise_Meitner
- "meitner",
- # Carla Meninsky, was the game designer and programmer for Atari 2600 games Dodge 'Em and Warlords.
- # https://en.wikipedia.org/wiki/Carla_Meninsky
- "meninsky",
- # Ralph C. Merkle - American computer scientist, known for devising Merkle's puzzles - one of the very first
- # schemes for public-key cryptography. Also, inventor of Merkle trees and co-inventor of the Merkle-Damgard
- # construction for building collision-resistant cryptographic hash functions and the Merkle-Hellman knapsack
- # cryptosystem. https://en.wikipedia.org/wiki/Ralph_Merkle
- "merkle",
- # Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany -
- # https://en.wikipedia.org/wiki/Johanna_Mestorf
- "mestorf",
- # Maryam Mirzakhani - an Iranian mathematician and the first woman to win the Fields Medal.
- # https://en.wikipedia.org/wiki/Maryam_Mirzakhani
- "mirzakhani",
- # Rita Levi-Montalcini - Won Nobel Prize in Physiology or Medicine jointly with colleague Stanley Cohen for the
- # discovery of nerve growth factor (https://en.wikipedia.org/wiki/Rita_Levi-Montalcini)
- "montalcini",
- # Gordon Earle Moore - American engineer, Silicon Valley founding father, author of Moore's law.
- # https://en.wikipedia.org/wiki/Gordon_Moore
- "moore",
- # Samuel Morse - contributed to the invention of a single-wire telegraph system based on European telegraphs
- # and was a co-developer of the Morse code - https://en.wikipedia.org/wiki/Samuel_Morse
- "morse",
- # Ian Murdock - founder of the Debian project - https://en.wikipedia.org/wiki/Ian_Murdock
- "murdock",
- # May-Britt Moser - Nobel prize winner neuroscientist who contributed to the discovery of grid cells in the brain.
- # https://en.wikipedia.org/wiki/May-Britt_Moser
- "moser",
- # John Napier of Merchiston - Scottish landowner known as an astronomer, mathematician and physicist.
- # Best known for his discovery of logarithms. https://en.wikipedia.org/wiki/John_Napier
- "napier",
- # John Forbes Nash, Jr. - American mathematician who made fundamental contributions to game theory, differential
- # geometry, and the study of partial differential equations. https://en.wikipedia.org/wiki/John_Forbes_Nash_Jr.
- "nash",
- # John von Neumann - todays computer architectures are based on the von Neumann architecture.
- # https://en.wikipedia.org/wiki/Von_Neumann_architecture
- "neumann",
- # Isaac Newton invented classic mechanics and modern optics. https://en.wikipedia.org/wiki/Isaac_Newton
- "newton",
- # Florence Nightingale, more prominently known as a nurse, was also the first female member of the Royal Statistical
- # Society and a pioneer in statistical graphics
- # https://en.wikipedia.org/wiki/Florence_Nightingale#Statistics_and_sanitary_reform
- "nightingale",
- # Alfred Nobel - a Swedish chemist, engineer, innovator, and armaments manufacturer (inventor of dynamite) -
- # https://en.wikipedia.org/wiki/Alfred_Nobel
- "nobel",
- # Emmy Noether, German mathematician. Noether's Theorem is named after her.
- # https://en.wikipedia.org/wiki/Emmy_Noether
- "noether",
- # Poppy Northcutt. Poppy Northcutt was the first woman to work as part of NASA's Mission Control.
- # http://www.businessinsider.com/poppy-northcutt-helped-apollo-astronauts-2014-12?op=1
- "northcutt",
- # Robert Noyce invented silicon integrated circuits and gave Silicon Valley its name. -
- # https://en.wikipedia.org/wiki/Robert_Noyce
- "noyce",
- # Panini - Ancient Indian linguist and grammarian from 4th century CE who worked on the world's first formal system
- # - https://en.wikipedia.org/wiki/P%C4%81%E1%B9%87ini#Comparison_with_modern_formal_systems
- "panini",
- # Ambroise Pare invented modern surgery. https://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
- "pare",
- # Blaise Pascal, French mathematician, physicist, and inventor - https://en.wikipedia.org/wiki/Blaise_Pascal
- "pascal",
- # Louis Pasteur discovered vaccination, fermentation and pasteurization.
- # https://en.wikipedia.org/wiki/Louis_Pasteur.
- "pasteur",
- # Cecilia Payne-Gaposchkin was an astronomer and astrophysicist who, in 1925, proposed in her Ph.D. thesis an
- # explanation for the composition of stars in terms of the relative abundances of hydrogen and helium.
- # https://en.wikipedia.org/wiki/Cecilia_Payne-Gaposchkin
- "payne",
- # Radia Perlman is a software designer and network engineer and most famous for her invention of the
- # spanning-tree protocol (STP). https://en.wikipedia.org/wiki/Radia_Perlman
- "perlman",
- # Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language.
- # https://en.wikipedia.org/wiki/Rob_Pike
- "pike",
- # Henri Poincare made fundamental contributions in several fields of mathematics.
- # https://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
- "poincare",
- # Laura Poitras is a director and producer whose work, made possible by open source crypto tools, advances the
- # causes of truth and freedom of information by reporting disclosures by whistleblowers such as Edward Snowden.
- # https://en.wikipedia.org/wiki/Laura_Poitras
- "poitras",
- # Tat'yana Avenirovna Proskuriakova (January 23 [O.S. January 10] 1909 - August 30, 1985) was a Russian-American
- # Mayanist scholar and archaeologist who contributed significantly to the deciphering of Maya hieroglyphs, the
- # writing system of the pre-Columbian Maya civilization of Mesoamerica.
- # https://en.wikipedia.org/wiki/Tatiana_Proskouriakoff
- "proskuriakova",
- # Claudius Ptolemy - a Greco-Egyptian writer of Alexandria, known as a mathematician, astronomer, geographer,
- # astrologer, and poet of a single epigram in the Greek Anthology - https://en.wikipedia.org/wiki/Ptolemy
- "ptolemy",
- # C. V. Raman - Indian physicist who won the Nobel Prize in 1930 for proposing the Raman effect. -
- # https://en.wikipedia.org/wiki/C._V._Raman
- "raman",
- # Srinivasa Ramanujan - Indian mathematician and autodidact who made extraordinary contributions to mathematical
- # analysis, number theory, infinite series, and continued fractions. -
- # https://en.wikipedia.org/wiki/Srinivasa_Ramanujan
- "ramanujan",
- # Sally Kristen Ride was an American physicist and astronaut. She was the first American woman in space, and the
- # youngest American astronaut. https://en.wikipedia.org/wiki/Sally_Ride
- "ride",
- # Dennis Ritchie - co-creator of UNIX and the C programming language. - https://en.wikipedia.org/wiki/Dennis_Ritchie
- "ritchie",
- # Ida Rhodes - American pioneer in computer programming, designed the first computer used for Social Security.
- # https://en.wikipedia.org/wiki/Ida_Rhodes
- "rhodes",
- # Julia Hall Bowman Robinson - American mathematician renowned for her contributions to the fields of computability
- # theory and computational complexity theory. https://en.wikipedia.org/wiki/Julia_Robinson
- "robinson",
- # Wilhelm Conrad Rontgen - German physicist who was awarded the first Nobel Prize in Physics in 1901 for the
- # discovery of X-rays (Rontgen rays). https://en.wikipedia.org/wiki/Wilhelm_R%C3%B6ntgen
- "roentgen",
- # Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the
- # understanding of DNA - https://en.wikipedia.org/wiki/Rosalind_Franklin
- "rosalind",
- # Vera Rubin - American astronomer who pioneered work on galaxy rotation rates.
- # https://en.wikipedia.org/wiki/Vera_Rubin
- "rubin",
- # Meghnad Saha - Indian astrophysicist best known for his development of the Saha equation, used to describe
- # chemical and physical conditions in stars - https://en.wikipedia.org/wiki/Meghnad_Saha
- "saha",
- # Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of
- # mathematical formulas. https://en.wikipedia.org/wiki/Jean_E._Sammet
- "sammet",
- # Mildred Sanderson - American mathematician best known for Sanderson's theorem concerning modular invariants.
- # https://en.wikipedia.org/wiki/Mildred_Sanderson
- "sanderson",
- # Satoshi Nakamoto is the name used by the unknown person or group of people who developed bitcoin, authored the
- # bitcoin white paper, and created and deployed bitcoin's original reference implementation.
- # https://en.wikipedia.org/wiki/Satoshi_Nakamoto
- "satoshi",
- # Adi Shamir - Israeli cryptographer whose numerous inventions and contributions to cryptography include the Ferge
- # Fiat Shamir identification scheme, the Rivest Shamir Adleman (RSA) public-key cryptosystem, the Shamir's secret
- # sharing scheme, the breaking of the Merkle-Hellman cryptosystem, the TWINKLE and TWIRL factoring devices and the
- # discovery of differential cryptanalysis (with Eli Biham). https://en.wikipedia.org/wiki/Adi_Shamir
- "shamir",
- # Claude Shannon - The father of information theory and founder of digital circuit design theory.
- # (https://en.wikipedia.org/wiki/Claude_Shannon)
- "shannon",
- # Carol Shaw - Originally an Atari employee, Carol Shaw is said to be the first female video game designer.
- # https://en.wikipedia.org/wiki/Carol_Shaw_(video_game_designer)
- "shaw",
- # Dame Stephanie "Steve" Shirley - Founded a software company in 1962 employing women working from home.
- # https://en.wikipedia.org/wiki/Steve_Shirley
- "shirley",
- # William Shockley co-invented the transistor - https://en.wikipedia.org/wiki/William_Shockley
- "shockley",
- # Lina Solomonovna Stern (or Shtern; 26 August 1878 - 7 March 1968) was a Soviet biochemist, physiologist and
- # humanist whose medical discoveries saved thousands of lives at the fronts of World War II. She is best known
- # for her pioneering work on blood\u2013brain barrier, which she described as hemato-encephalic barrier in 1921.
- # https://en.wikipedia.org/wiki/Lina_Stern
- "shtern",
- # Francoise Barre-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was
- # fundamental in identifying HIV as the cause of AIDS.
- # https://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi
- "sinoussi",
- # Betty Snyder - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC -
- # https://en.wikipedia.org/wiki/Betty_Holberton
- "snyder",
- # Cynthia Solomon - Pioneer in the fields of artificial intelligence, computer science and educational computing.
- # Known for creation of Logo, an educational programming language. https://en.wikipedia.org/wiki/Cynthia_Solomon
- "solomon",
- # Frances Spence - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC -
- # https://en.wikipedia.org/wiki/Frances_Spence
- "spence",
- # Michael Stonebraker is a database research pioneer and architect of Ingres, Postgres, VoltDB and SciDB.
- # Winner of 2014 ACM Turing Award. https://en.wikipedia.org/wiki/Michael_Stonebraker
- "stonebraker",
- # Ivan Edward Sutherland - American computer scientist and Internet pioneer, widely regarded as the father of
- # computer graphics. https://en.wikipedia.org/wiki/Ivan_Sutherland
- "sutherland",
- # Janese Swanson (with others) developed the first of the Carmen Sandiego games. She went on to found Girl Tech.
- # https://en.wikipedia.org/wiki/Janese_Swanson
- "swanson",
- # Aaron Swartz was influential in creating RSS, Markdown, Creative Commons, Reddit, and much of the internet as we
- # know it today. He was devoted to freedom of information on the web. https://en.wikiquote.org/wiki/Aaron_Swartz
- "swartz",
- # Bertha Swirles was a theoretical physicist who made a number of contributions to early quantum theory.
- # https://en.wikipedia.org/wiki/Bertha_Swirles
- "swirles",
- # Helen Brooke Taussig - American cardiologist and founder of the field of paediatric cardiology.
- # https://en.wikipedia.org/wiki/Helen_B._Taussig
- "taussig",
- # Valentina Tereshkova is a Russian engineer, cosmonaut and politician. She was the first woman to fly to space in
- # 1963. In 2013, at the age of 76, she offered to go on a one-way mission to Mars.
- # https://en.wikipedia.org/wiki/Valentina_Tereshkova
- "tereshkova",
- # Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain.
- # https://en.wikipedia.org/wiki/Nikola_Tesla
- "tesla",
- # Marie Tharp - American geologist and oceanic cartographer who co-created the first scientific map of the Atlantic
- # Ocean floor. Her work led to the acceptance of the theories of plate tectonics and continental drift.
- # https://en.wikipedia.org/wiki/Marie_Tharp
- "tharp",
- # Ken Thompson - co-creator of UNIX and the C programming language - https://en.wikipedia.org/wiki/Ken_Thompson
- "thompson",
- # Linus Torvalds invented Linux and Git. https://en.wikipedia.org/wiki/Linus_Torvalds
- "torvalds",
- # Youyou Tu - Chinese pharmaceutical chemist and educator known for discovering artemisinin and dihydroartemisinin,
- # used to treat malaria, which has saved millions of lives. Joint winner of the 2015 Nobel Prize in Physiology or
- # Medicine. https://en.wikipedia.org/wiki/Tu_Youyou
- "tu",
- # Alan Turing was a founding father of computer science. https://en.wikipedia.org/wiki/Alan_Turing.
- "turing",
- # Varahamihira - Ancient Indian mathematician who discovered trigonometric formulae during 505-587 CE -
- # https://en.wikipedia.org/wiki/Var%C4%81hamihira#Contributions
- "varahamihira",
- # Dorothy Vaughan was a NASA mathematician and computer programmer on the SCOUT launch vehicle program that put
- # America's first satellites into space - https://en.wikipedia.org/wiki/Dorothy_Vaughan
- "vaughan",
- # Sir Mokshagundam Visvesvaraya - is a notable Indian engineer. He is a recipient of the Indian Republic's highest
- # honour, the Bharat Ratna, in 1955. On his birthday, 15 September is celebrated as Engineer's Day in India in his
- # memory - https://en.wikipedia.org/wiki/Visvesvaraya
- "visvesvaraya",
- # Christiane Nusslein-Volhard - German biologist, won Nobel Prize in Physiology or Medicine in 1995 for research on
- # the genetic control of embryonic development. https://en.wikipedia.org/wiki/Christiane_N%C3%BCsslein-Volhard
- "volhard",
- # Cedric Villani - French mathematician, won Fields Medal, Fermat Prize and Poincare Price for his work in
- # differential geometry and statistical mechanics. https://en.wikipedia.org/wiki/C%C3%A9dric_Villani
- "villani",
- # Marlyn Wescoff - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC -
- # https://en.wikipedia.org/wiki/Marlyn_Meltzer
- "wescoff",
- # Sylvia B. Wilbur - British computer scientist who helped develop the ARPANET, was one of the first to exchange
- # email in the UK and a leading researcher in computer-supported collaborative work.
- # https://en.wikipedia.org/wiki/Sylvia_Wilbur
- "wilbur",
- # Andrew Wiles - Notable British mathematician who proved the enigmatic Fermat's Last Theorem -
- # https://en.wikipedia.org/wiki/Andrew_Wiles
- "wiles",
- # Roberta Williams, did pioneering work in graphical adventure games for personal computers, particularly the King's
- # Quest series. https://en.wikipedia.org/wiki/Roberta_Williams
- "williams",
- # Malcolm John Williamson - British mathematician and cryptographer employed by the GCHQ. Developed in 1974 what
- # is now known as Diffie-Hellman key exchange (Diffie and Hellman first published the scheme in 1976).
- # https://en.wikipedia.org/wiki/Malcolm_J._Williamson
- "williamson",
- # Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors.
- # https://en.wikipedia.org/wiki/Sophie_Wilson
- "wilson",
- # Jeannette Wing - co-developed the Liskov substitution principle. - https://en.wikipedia.org/wiki/Jeannette_Wing
- "wing",
- # Steve Wozniak invented the Apple I and Apple II. https://en.wikipedia.org/wiki/Steve_Wozniak
- "wozniak",
- # The Wright brothers, Orville and Wilbur - credited with inventing and building the world's first successful
- # airplane and making the first controlled, powered and sustained heavier-than-air human flight -
- # https://en.wikipedia.org/wiki/Wright_brothers
- "wright",
- # Chien-Shiung Wu - Chinese-American experimental physicist who made significant contributions to nuclear physics.
- # https://en.wikipedia.org/wiki/Chien-Shiung_Wu
- "wu",
- # Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977
- # Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique.
- # https://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow
- "yalow",
- # Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the
- # sciences. https://en.wikipedia.org/wiki/Ada_Yonath
- "yonath",
- # Nikolay Yegorovich Zhukovsky (January 17 1847 - March 17, 1921) was a Russian scientist, mathematician and
- # engineer, and a founding father of modern aero- and hydrodynamics. Whereas contemporary scientists scoffed at the
- # idea of human flight, Zhukovsky was the first to undertake the study of airflow. He is often called the Father
- # of Russian Aviation. https://en.wikipedia.org/wiki/Nikolay_Yegorovich_Zhukovsky
- "zhukovsky",
-]
-
-
-def get_unique_name():
- """Generates a random name in the style of "docker containers".
-
- This is generated from the list of adjectives and surnames in this package,
- formatted as "adjective_surname" with a random integer between 0 and 10000
- added to the end.
-
- A python port of docker's random container name generator.
- Original source:
- https://raw.githubusercontent.com/moby/moby/master/pkg/namesgenerator/names-generator.go
-
- Examples:
-
- >>> import random ; random.seed(42)
- >>> get_unique_name()
- 'meek-ardinghelli-4506'
- >>> get_unique_name()
- 'truthful-dijkstra-2286'
-
- """
- adjective, surname, i = choice(_adjectives), choice(_surnames), randint(0, 9999) # noqa: S311
- return f"{adjective}-{surname}-{i}"
diff --git a/src/lightning/app/utilities/network.py b/src/lightning/app/utilities/network.py
deleted file mode 100644
index a7cc00fde52b7..0000000000000
--- a/src/lightning/app/utilities/network.py
+++ /dev/null
@@ -1,215 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import socket
-from functools import wraps
-from typing import Any, Callable, Dict, Optional
-from urllib.parse import urljoin
-
-import requests
-
-# for backwards compatibility
-from lightning_cloud.rest_client import GridRestClient, LightningClient, create_swagger_client # noqa: F401
-from requests import Session
-from requests.adapters import HTTPAdapter
-from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
-from urllib3.util.retry import Retry
-
-from lightning.app.core import constants
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-
-
-# Global record to track ports that have been allocated in this session.
-_reserved_ports = set()
-
-
-def find_free_network_port() -> int:
- """Finds a free port on localhost."""
- if constants.LIGHTNING_CLOUDSPACE_HOST is not None:
- return _find_free_network_port_cloudspace()
-
- port = None
-
- for _ in range(10):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.bind(("", 0))
- port = sock.getsockname()[1]
- sock.close()
-
- if port not in _reserved_ports:
- break
-
- if port in _reserved_ports:
- # Prevent an infinite loop, if we tried 10 times and didn't get a free port then something is wrong
- raise RuntimeError(
- "Couldn't find a free port. Please open an issue at `https://github.com/Lightning-AI/lightning/issues`."
- )
-
- _reserved_ports.add(port)
- return port
-
-
-def _find_free_network_port_cloudspace():
- """Finds a free port in the exposed range when running in a cloudspace."""
- for port in range(
- constants.APP_SERVER_PORT + 1, # constants.APP_SERVER_PORT is reserved for the app server
- constants.APP_SERVER_PORT + constants.LIGHTNING_CLOUDSPACE_EXPOSED_PORT_COUNT,
- ):
- if port in _reserved_ports:
- continue
-
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.bind(("", port))
- sock.close()
- _reserved_ports.add(port)
- return port
- except OSError:
- continue
-
- # This error should never happen. An app using this many ports would probably fail on a single machine anyway.
- raise RuntimeError(f"All {constants.LIGHTNING_CLOUDSPACE_EXPOSED_PORT_COUNT} ports are already in use.")
-
-
-_CONNECTION_RETRY_TOTAL = 2880
-_CONNECTION_RETRY_BACKOFF_FACTOR = 0.5
-_DEFAULT_REQUEST_TIMEOUT = 30 # seconds
-
-
-def create_retry_strategy():
- return Retry(
- # wait time between retries increases exponentially according to: backoff_factor * (2 ** (retry - 1))
- # but the the maximum wait time is 120 secs. By setting a large value (2880), we'll make sure clients
- # are going to be alive for a very long time (~ 4 days) but retries every 120 seconds
- total=_CONNECTION_RETRY_TOTAL,
- backoff_factor=_CONNECTION_RETRY_BACKOFF_FACTOR,
- # Any 4xx and 5xx statuses except
- # 400 Bad Request
- # 401 Unauthorized
- # 403 Forbidden
- # 404 Not Found
- status_forcelist={
- 402,
- *range(405, 600),
- },
- allowed_methods={
- "POST", # Default methods are idempotent, add POST here
- *Retry.DEFAULT_ALLOWED_METHODS,
- },
- )
-
-
-def _configure_session() -> Session:
- """Configures the session for GET and POST requests.
-
- It enables a generous retrial strategy that waits for the application server to connect.
-
- """
- retry_strategy = create_retry_strategy()
- adapter = HTTPAdapter(max_retries=retry_strategy)
- http = requests.Session()
- http.mount("https://", adapter)
- http.mount("http://", adapter)
- return http
-
-
-def _check_service_url_is_ready(url: str, timeout: float = 5, metadata="") -> bool:
- try:
- response = requests.get(url, timeout=timeout)
- return response.status_code in (200, 404)
- except (ConnectionError, ConnectTimeout, ReadTimeout):
- logger.debug(f"The url {url} is not ready. {metadata}")
- return False
-
-
-class CustomRetryAdapter(HTTPAdapter):
- def __init__(self, *args: Any, **kwargs: Any):
- self.timeout = kwargs.pop("timeout", _DEFAULT_REQUEST_TIMEOUT)
- super().__init__(*args, **kwargs)
-
- def send(self, request, **kwargs: Any):
- kwargs["timeout"] = kwargs.get("timeout", self.timeout)
- return super().send(request, **kwargs)
-
-
-def _http_method_logger_wrapper(func: Callable) -> Callable:
- """Returns the function decorated by a wrapper that logs the message using the `log_function` hook."""
-
- @wraps(func)
- def wrapped(self: "HTTPClient", *args: Any, **kwargs: Any) -> Any:
- message = f"HTTPClient: Method: {func.__name__.upper()}, Path: {args[0]}\n"
- message += f" Base URL: {self.base_url}\n"
- params = kwargs.get("query_params", {})
- if params:
- message += f" Params: {params}\n"
- resp: requests.Response = func(self, *args, **kwargs)
- message += f" Response: {resp.status_code} {resp.reason}"
- self.log_function(message)
- return resp
-
- return wrapped
-
-
-def _response(r, *args: Any, **kwargs: Any):
- return r.raise_for_status()
-
-
-class HTTPClient:
- """A wrapper class around the requests library which handles chores like logging, retries, and timeouts
- automatically."""
-
- def __init__(
- self, base_url: str, auth_token: Optional[str] = None, log_callback: Optional[Callable] = None
- ) -> None:
- self.base_url = base_url
- retry_strategy = create_retry_strategy()
- adapter = CustomRetryAdapter(max_retries=retry_strategy, timeout=_DEFAULT_REQUEST_TIMEOUT)
- self.session = requests.Session()
-
- self.session.hooks = {"response": _response}
-
- self.session.mount("http://", adapter)
- self.session.mount("https://", adapter)
-
- if auth_token:
- self.session.headers.update({"Authorization": f"Bearer {auth_token}"})
-
- self.log_function = log_callback or self.log_function
-
- @_http_method_logger_wrapper
- def get(self, path: str):
- url = urljoin(self.base_url, path)
- return self.session.get(url)
-
- @_http_method_logger_wrapper
- def post(self, path: str, *, query_params: Optional[Dict] = None, data: Optional[bytes] = None, json: Any = None):
- url = urljoin(self.base_url, path)
- return self.session.post(url, data=data, params=query_params, json=json)
-
- @_http_method_logger_wrapper
- def delete(self, path: str):
- url = urljoin(self.base_url, path)
- return self.session.delete(url)
-
- def log_function(self, message: str, *args, **kwargs: Any):
- """This function is used to log the messages in the client, it can be overridden by caller to customise the
- logging logic.
-
- We enabled customisation here instead of just using `logger.debug` because HTTP logging can be very noisy, but
- it is crucial for finding bugs when we have them
-
- """
- pass
diff --git a/src/lightning/app/utilities/openapi.py b/src/lightning/app/utilities/openapi.py
deleted file mode 100644
index f210c3cd47b04..0000000000000
--- a/src/lightning/app/utilities/openapi.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import json
-from typing import Any, Dict
-
-
-def _duplicate_checker(js):
- """_duplicate_checker verifies that your JSON object doesn't contain duplicate keys."""
- result = {}
- for name, value in js:
- if name in result:
- raise ValueError(
- f"Unable to load JSON. A duplicate key {name} was detected. JSON objects must have unique keys."
- )
- result[name] = value
- return result
-
-
-def string2dict(text):
- """String2dict parses a JSON string into a dictionary, ensuring no keys are duplicated by accident."""
- if not isinstance(text, str):
- text = text.decode("utf-8")
- try:
- js = json.loads(text, object_pairs_hook=_duplicate_checker)
- return js
- except ValueError as ex:
- raise ValueError(f"Unable to load JSON: {str(ex)}.")
-
-
-def is_openapi(obj):
- """is_openopi checks if an object was generated by OpenAPI."""
- return hasattr(obj, "swagger_types")
-
-
-def create_openapi_object(json_obj: Dict, target: Any):
- """Create the OpenAPI object from the given JSON dict and based on the target object.
-
- Lightning AI uses the target object to make new objects from the given JSON spec so the target must be a valid
- object.
-
- """
- if not isinstance(json_obj, dict):
- raise TypeError("json_obj must be a dictionary")
- if not is_openapi(target):
- raise TypeError("target must be an openapi object")
-
- target_attribs = {}
- for key, value in json_obj.items():
- try:
- # user provided key is not a valid key on openapi object
- sub_target = getattr(target, key)
- except AttributeError:
- raise ValueError(f"Field {key} not found in the target object")
-
- if is_openapi(sub_target): # it's an openapi object
- target_attribs[key] = create_openapi_object(value, sub_target)
- else:
- target_attribs[key] = value
-
- # TODO(sherin) - specifically process list and dict and do the validation. Also do the
- # verification for enum types
-
- return target.__class__(**target_attribs)
diff --git a/src/lightning/app/utilities/packaging/__init__.py b/src/lightning/app/utilities/packaging/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning/app/utilities/packaging/app_config.py b/src/lightning/app/utilities/packaging/app_config.py
deleted file mode 100644
index 57177344a8fe0..0000000000000
--- a/src/lightning/app/utilities/packaging/app_config.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import pathlib
-from dataclasses import asdict, dataclass, field
-from typing import Union
-
-import yaml
-
-from lightning.app.utilities.name_generator import get_unique_name
-
-_APP_CONFIG_FILENAME = ".lightning"
-
-
-@dataclass
-class AppConfig:
- """The AppConfig holds configuration metadata for the application.
-
- Args:
- name: Optional name of the application. If not provided, auto-generates a new name.
-
- """
-
- name: str = field(default_factory=get_unique_name)
-
- def save_to_file(self, path: Union[str, pathlib.Path]) -> None:
- """Save the configuration to the given file in YAML format."""
- path = pathlib.Path(path)
- with open(path, "w") as file:
- yaml.dump(asdict(self), file)
-
- def save_to_dir(self, directory: Union[str, pathlib.Path]) -> None:
- """Save the configuration to a file '.lightning' to the given folder in YAML format."""
- self.save_to_file(_get_config_file(directory))
-
- @classmethod
- def load_from_file(cls, path: Union[str, pathlib.Path]) -> "AppConfig":
- """Load the configuration from the given file."""
- with open(path) as file:
- config = yaml.safe_load(file)
- return cls(**config)
-
- @classmethod
- def load_from_dir(cls, directory: Union[str, pathlib.Path]) -> "AppConfig":
- """Load the configuration from the given folder.
-
- Args:
- directory: Path to a folder which contains the '.lightning' config file to load.
-
- """
- return cls.load_from_file(pathlib.Path(directory, _APP_CONFIG_FILENAME))
-
-
-def _get_config_file(source_path: Union[str, pathlib.Path]) -> pathlib.Path:
- """Get the Lightning app config file '.lightning' at the given source path.
-
- Args:
- source_path: A path to a folder or a file.
-
- """
- source_path = pathlib.Path(source_path).absolute()
- if source_path.is_file():
- source_path = source_path.parent
-
- return pathlib.Path(source_path / _APP_CONFIG_FILENAME)
diff --git a/src/lightning/app/utilities/packaging/build_config.py b/src/lightning/app/utilities/packaging/build_config.py
deleted file mode 100644
index 05099fa1b4c77..0000000000000
--- a/src/lightning/app/utilities/packaging/build_config.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import inspect
-import os
-import re
-from dataclasses import asdict, dataclass, field
-from pathlib import Path
-from typing import TYPE_CHECKING, Dict, List, Optional, Union
-
-from typing_extensions import Self
-
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-if TYPE_CHECKING:
- from lightning.app.core.work import LightningWork
-
-logger = Logger(__name__)
-
-
-def load_requirements(
- path_dir: str, file_name: str = "base.txt", comment_char: str = "#", unfreeze: bool = True
-) -> List[str]:
- """Load requirements from a file."""
- path = os.path.join(path_dir, file_name)
- if not os.path.isfile(path):
- return []
-
- with open(path) as file:
- lines = [ln.strip() for ln in file.readlines()]
- reqs = []
- for ln in lines:
- # filer all comments
- comment = ""
- if comment_char in ln:
- comment = ln[ln.index(comment_char) :]
- ln = ln[: ln.index(comment_char)]
- req = ln.strip()
- # skip directly installed dependencies
- if not req or req.startswith("http") or "@http" in req:
- continue
- # remove version restrictions unless they are strict
- if unfreeze and "<" in req and "strict" not in comment:
- req = re.sub(r",? *<=? *[\d\.\*]+", "", req).strip()
- reqs.append(req)
- return reqs
-
-
-@dataclass
-class _Dockerfile:
- path: str
- data: List[str]
-
-
-@dataclass
-class BuildConfig:
- """The Build Configuration describes how the environment a LightningWork runs in should be set up.
-
- Arguments:
- requirements: List of requirements or list of paths to requirement files. If not passed, they will be
- automatically extracted from a `requirements.txt` if it exists.
- dockerfile: The path to a dockerfile to be used to build your container.
- You need to add those lines to ensure your container works in the cloud.
-
- .. warning:: This feature isn't supported yet, but coming soon.
-
- Example::
-
- WORKDIR /gridai/project
- COPY . .
- image: The base image that the work runs on. This should be a publicly accessible image from a registry that
- doesn't enforce rate limits (such as DockerHub) to pull this image, otherwise your application will not
- start.
-
- """
-
- requirements: List[str] = field(default_factory=list)
- dockerfile: Optional[Union[str, Path, _Dockerfile]] = None
- image: Optional[str] = None
-
- def __post_init__(self) -> None:
- current_frame = inspect.currentframe()
- co_filename = current_frame.f_back.f_back.f_code.co_filename # type: ignore[union-attr]
- self._call_dir = os.path.dirname(co_filename)
- self._prepare_requirements()
- self._prepare_dockerfile()
-
- def build_commands(self) -> List[str]:
- """Override to run some commands before your requirements are installed.
-
- .. note:: If you provide your own dockerfile, this would be ignored.
-
- Example:
-
- from dataclasses import dataclass
- from lightning.app import BuildConfig
-
- @dataclass
- class MyOwnBuildConfig(BuildConfig):
-
- def build_commands(self):
- return ["apt-get install libsparsehash-dev"]
-
- BuildConfig(requirements=["git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0"])
-
- """
- return []
-
- def on_work_init(self, work: "LightningWork", cloud_compute: Optional["CloudCompute"] = None) -> None:
- """Override with your own logic to load the requirements or dockerfile."""
- found_requirements = self._find_requirements(work)
- if self.requirements:
- if found_requirements and self.requirements != found_requirements:
- # notify the user of this silent behaviour
- logger.info(
- f"A 'requirements.txt' exists with {found_requirements} but {self.requirements} was passed to"
- f" the `{type(self).__name__}` in {work.name!r}. The `requirements.txt` file will be ignored."
- )
- else:
- self.requirements = found_requirements
- self._prepare_requirements()
-
- found_dockerfile = self._find_dockerfile(work)
- if self.dockerfile:
- if found_dockerfile and self.dockerfile != found_dockerfile:
- # notify the user of this silent behaviour
- logger.info(
- f"A Dockerfile exists at {found_dockerfile!r} but {self.dockerfile!r} was passed to"
- f" the `{type(self).__name__}` in {work.name!r}. {found_dockerfile!r}` will be ignored."
- )
- else:
- self.dockerfile = found_dockerfile
- self._prepare_dockerfile()
-
- def _find_requirements(self, work: "LightningWork", filename: str = "requirements.txt") -> List[str]:
- # 1. Get work file
- file = _get_work_file(work)
- if file is None:
- return []
- # 2. Try to find a requirement file associated the file.
- dirname = os.path.dirname(file)
- try:
- requirements = load_requirements(dirname, filename)
- except NotADirectoryError:
- return []
- return [r for r in requirements if r != "lightning"]
-
- def _find_dockerfile(self, work: "LightningWork", filename: str = "Dockerfile") -> Optional[str]:
- # 1. Get work file
- file = _get_work_file(work)
- if file is None:
- return None
- # 2. Check for Dockerfile.
- dirname = os.path.dirname(file)
- dockerfile = os.path.join(dirname, filename)
- if os.path.isfile(dockerfile):
- return dockerfile
- return None
-
- def _prepare_requirements(self) -> None:
- requirements = []
- for req in self.requirements:
- # 1. Check for relative path
- path = os.path.join(self._call_dir, req)
- if os.path.isfile(path):
- try:
- new_requirements = load_requirements(self._call_dir, req)
- except NotADirectoryError:
- continue
- requirements.extend(new_requirements)
- else:
- requirements.append(req)
- self.requirements = requirements
-
- def _prepare_dockerfile(self) -> None:
- if isinstance(self.dockerfile, (str, Path)):
- path = os.path.join(self._call_dir, self.dockerfile)
- if os.path.exists(path):
- with open(path) as f:
- self.dockerfile = _Dockerfile(path, f.readlines())
-
- def to_dict(self) -> Dict:
- return {"__build_config__": asdict(self)}
-
- @classmethod
- def from_dict(cls, d: Dict) -> Self:
- return cls(**d["__build_config__"])
-
-
-def _get_work_file(work: "LightningWork") -> Optional[str]:
- cls = work.__class__
- try:
- return inspect.getfile(cls)
- except TypeError:
- logger.debug(f"The {cls.__name__} file couldn't be found.")
- return None
diff --git a/src/lightning/app/utilities/packaging/cloud_compute.py b/src/lightning/app/utilities/packaging/cloud_compute.py
deleted file mode 100644
index e4f30aee14a63..0000000000000
--- a/src/lightning/app/utilities/packaging/cloud_compute.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dataclasses import asdict, dataclass
-from typing import Dict, List, Optional, Tuple, Union
-from uuid import uuid4
-
-from lightning.app.core.constants import ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER, enable_interruptible_works
-from lightning.app.storage.mount import Mount
-
-__CLOUD_COMPUTE_IDENTIFIER__ = "__cloud_compute__"
-
-
-@dataclass
-class _CloudComputeStore:
- id: str
- component_names: List[str]
-
- def add_component_name(self, new_component_name: str) -> None:
- found_index = None
- # When the work is being named by the flow, pop its previous names
- for index, component_name in enumerate(self.component_names):
- if new_component_name.endswith(component_name.replace("root.", "")):
- found_index = index
-
- if found_index is not None:
- self.component_names[found_index] = new_component_name
- else:
- if (
- len(self.component_names) == 1
- and not ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER
- and self.id != "default"
- ):
- raise Exception(
- f"A Cloud Compute can be assigned only to a single Work. Attached to {self.component_names[0]}"
- )
- self.component_names.append(new_component_name)
-
- def remove(self, new_component_name: str) -> None:
- found_index = None
- for index, component_name in enumerate(self.component_names):
- if new_component_name == component_name:
- found_index = index
-
- if found_index is not None:
- del self.component_names[found_index]
-
-
-_CLOUD_COMPUTE_STORE = {}
-
-
-@dataclass
-class CloudCompute:
- """Configure the cloud runtime for a lightning work or flow.
-
- Arguments:
- name: The name of the hardware to use. A full list of supported options can be found in
- :doc:`/core_api/lightning_work/compute`. If you have a request for more hardware options, please contact
- `onprem@lightning.ai `_.
-
- disk_size: The disk size in Gigabytes.
- The value you set here will be allocated to the /home folder.
-
- idle_timeout: The number of seconds to wait before pausing the compute when the work is running and idle.
- This timeout starts whenever your run() method succeeds (or fails).
- If the timeout is reached, the instance pauses until the next run() call happens.
-
- shm_size: Shared memory size in MiB, backed by RAM. min 512, max 8192, it will auto update in steps of 512.
- For example 1100 will become 1024. If set to zero (the default) will get the default 64MiB inside docker.
-
- mounts: External data sources which should be mounted into a work as a filesystem at runtime.
-
- colocation_group_id: Identifier for groups of works to be colocated in the same datacenter.
- Set this to a string of max. 64 characters and all works with this group id will run in the same datacenter.
- If not set, the works are not guaranteed to be colocated.
-
- interruptible: Whether to run on a interruptible machine e.g the machine can be stopped
- at any time by the providers. This is also known as spot or preemptible machines.
- Compared to on-demand machines, they tend to be cheaper.
-
- """
-
- name: str = "default"
- disk_size: int = 0
- idle_timeout: Optional[int] = None
- shm_size: Optional[int] = None
- mounts: Optional[Union[Mount, List[Mount]]] = None
- colocation_group_id: Optional[str] = None
- interruptible: bool = False
- _internal_id: Optional[str] = None
-
- def __post_init__(self) -> None:
- _verify_mount_root_dirs_are_unique(self.mounts)
-
- self.name = self.name.lower()
-
- if self.shm_size is None:
- if "gpu" in self.name:
- self.shm_size = 1024
- else:
- self.shm_size = 0
-
- if self.interruptible:
- if not enable_interruptible_works():
- raise ValueError("CloudCompute with `interruptible=True` isn't supported yet.")
- if "gpu" not in self.name:
- raise ValueError("CloudCompute `interruptible=True` is supported only with GPU.")
-
- # FIXME: Clean the mess on the platform side
- if self.name == "default" or self.name == "cpu":
- self.name = "cpu-small"
- self._internal_id = "default"
-
- # TODO: Remove from the platform first.
- self.preemptible = self.interruptible
-
- # All `default` CloudCompute are identified in the same way.
- if self._internal_id is None:
- self._internal_id = self._generate_id()
-
- if self.colocation_group_id is not None and (
- not isinstance(self.colocation_group_id, str)
- or (isinstance(self.colocation_group_id, str) and len(self.colocation_group_id) > 64)
- ):
- raise ValueError("colocation_group_id can only be a string of maximum 64 characters.")
-
- def to_dict(self) -> dict:
- _verify_mount_root_dirs_are_unique(self.mounts)
- return {"type": __CLOUD_COMPUTE_IDENTIFIER__, **asdict(self)}
-
- @classmethod
- def from_dict(cls, d: dict) -> "CloudCompute":
- assert d.pop("type") == __CLOUD_COMPUTE_IDENTIFIER__
- mounts = d.pop("mounts", None)
- if mounts is None:
- pass
- elif isinstance(mounts, dict):
- d["mounts"] = Mount(**mounts)
- elif isinstance(mounts, (list)):
- d["mounts"] = []
- for mount in mounts:
- d["mounts"].append(Mount(**mount))
- else:
- raise TypeError(
- f"mounts argument must be one of [None, Mount, List[Mount]], "
- f"received {mounts} of type {type(mounts)}"
- )
- _verify_mount_root_dirs_are_unique(d.get("mounts"))
- return cls(**d)
-
- @property
- def id(self) -> Optional[str]:
- return self._internal_id
-
- def is_default(self) -> bool:
- return self.name in ("default", "cpu-small")
-
- def _generate_id(self):
- return "default" if self.name == "default" else uuid4().hex[:7]
-
- def clone(self):
- new_dict = self.to_dict()
- new_dict["_internal_id"] = self._generate_id()
- return self.from_dict(new_dict)
-
-
-def _verify_mount_root_dirs_are_unique(mounts: Union[None, Mount, List[Mount], Tuple[Mount]]) -> None:
- if isinstance(mounts, (list, tuple, set)):
- mount_paths = [mount.mount_path for mount in mounts]
- if len(set(mount_paths)) != len(mount_paths):
- raise ValueError("Every Mount attached to a work must have a unique 'mount_path' argument.")
-
-
-def _maybe_create_cloud_compute(state: Dict) -> Union[CloudCompute, Dict]:
- if state and state.get("type") == __CLOUD_COMPUTE_IDENTIFIER__:
- return CloudCompute.from_dict(state)
- return state
diff --git a/src/lightning/app/utilities/packaging/docker.py b/src/lightning/app/utilities/packaging/docker.py
deleted file mode 100644
index 8a59fec288c7c..0000000000000
--- a/src/lightning/app/utilities/packaging/docker.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import pickle
-import shutil
-import sys
-from datetime import datetime
-from typing import Optional
-
-from lightning.app import _PROJECT_ROOT, LightningWork
-from lightning.app.storage.path import _shared_local_mount_path
-from lightning.app.utilities.imports import _is_docker_available, _is_jinja2_available, requires
-
-if _is_docker_available():
- import docker
- from docker.models.containers import Container
-
-if _is_jinja2_available():
- import jinja2
-
-
-class DockerRunner:
- @requires("docker")
- def __init__(self, file: str, work: LightningWork, queue_id: str, create_base: bool = False):
- self.file = file
- self.work = work
- self.queue_id = queue_id
- self.image: Optional[str] = None
- if create_base:
- self._create_base_container()
- self._create_work_container()
-
- def _create_base_container(self) -> None:
- # 1. Get base container
- container_base = f"{_PROJECT_ROOT}/dockers/Dockerfile.base.cpu"
- destination_path = os.path.join(_PROJECT_ROOT, "Dockerfile")
-
- # 2. Copy the base Dockerfile within the Lightning project
- shutil.copy(container_base, destination_path)
-
- # 3. Build the docker image.
- os.system("docker build . --tag thomaschaton/base")
-
- # 4. Clean the copied base Dockerfile.
- os.remove(destination_path)
-
- def _create_work_container(self) -> None:
- # 1. Get work container.
- source_path = os.path.join(_PROJECT_ROOT, "dockers/Dockerfile.jinja")
- destination_path = os.path.join(_PROJECT_ROOT, "Dockerfile")
- work_destination_path = os.path.join(_PROJECT_ROOT, "work.p")
-
- # 2. Pickle the work.
- with open(work_destination_path, "wb") as f:
- pickle.dump(self.work, f)
-
- # 3. Load Lightning requirements.
- with open(source_path) as f:
- template = jinja2.Template(f.read())
-
- # Get the work local build spec.
- requirements = self.work.local_build_config.requirements
-
- # Render template with the requirements.
- dockerfile_str = template.render(
- requirements=" ".join(requirements),
- redis_host="host.docker.internal" if sys.platform == "darwin" else "127.0.0.1",
- )
-
- with open(destination_path, "w") as f:
- f.write(dockerfile_str)
-
- # 4. Build the container.
- self.image = f"work-{self.work.__class__.__qualname__.lower()}"
- os.system(f"docker build . --tag {self.image}")
-
- # 5. Clean the leftover files.
- os.remove(destination_path)
- os.remove(work_destination_path)
-
- def run(self) -> None:
- assert self.image
-
- # 1. Run the work container and launch the work.
- client = docker.DockerClient(base_url="unix://var/run/docker.sock")
- self.container: Container = client.containers.run(
- image=self.image,
- shm_size="10G",
- stderr=True,
- stdout=True,
- stdin_open=True,
- detach=True,
- ports=[url.split(":")[-1] for url in self.work._urls if url],
- volumes=[f"{str(_shared_local_mount_path())}:/home/.shared"],
- command=f"python -m lightning run work {self.file} --work_name={self.work.name} --queue_id {self.queue_id}",
- environment={"SHARED_MOUNT_DIRECTORY": "/home/.shared"},
- network_mode="host",
- )
-
- # 2. Check the log and exit when ``Starting WorkRunner`` is found.
- for line in self.container.logs(stream=True):
- line = str(line.strip())
- print(line)
- if "command not found" in line:
- raise RuntimeError("The container wasn't properly executed.")
- if "Starting WorkRunner" in line:
- break
-
- def get_container_logs(self) -> str:
- """Returns the logs of the container produced until now."""
- return "".join([chr(c) for c in self.container.logs(until=datetime.now())])
-
- def kill(self) -> None:
- """Kill the container."""
- self.container.kill()
diff --git a/src/lightning/app/utilities/packaging/lightning_utils.py b/src/lightning/app/utilities/packaging/lightning_utils.py
deleted file mode 100644
index 4dbba499625eb..0000000000000
--- a/src/lightning/app/utilities/packaging/lightning_utils.py
+++ /dev/null
@@ -1,219 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import functools
-import logging
-import os
-import pathlib
-import shutil
-import subprocess
-import sys
-import tarfile
-import tempfile
-import urllib.request
-from pathlib import Path
-from typing import Any, Callable, Optional
-
-from packaging.version import Version
-
-from lightning.app import _PROJECT_ROOT, _logger, _root_logger
-from lightning.app import __version__ as version
-from lightning.app.core.constants import FRONTEND_DIR, PACKAGE_LIGHTNING
-from lightning.app.utilities.app_helpers import Logger
-from lightning.app.utilities.git import check_github_repository, get_dir_name
-
-logger = Logger(__name__)
-
-
-# FIXME(alecmerdler): Use GitHub release artifacts once the `lightning-ui` repo is public
-LIGHTNING_FRONTEND_RELEASE_URL = "https://storage.googleapis.com/grid-packages/lightning-ui/v0.0.0/build.tar.gz"
-
-
-def download_frontend(root: str = _PROJECT_ROOT):
- """Downloads an archive file for a specific release of the Lightning frontend and extracts it to the correct
- directory."""
- build_dir = "build"
- frontend_dir = pathlib.Path(FRONTEND_DIR)
- download_dir = tempfile.mkdtemp()
-
- shutil.rmtree(frontend_dir, ignore_errors=True)
-
- response = urllib.request.urlopen(LIGHTNING_FRONTEND_RELEASE_URL) # noqa: S310
-
- file = tarfile.open(fileobj=response, mode="r|gz")
- file.extractall(path=download_dir) # noqa: S202
-
- shutil.move(os.path.join(download_dir, build_dir), frontend_dir)
- print("The Lightning UI has successfully been downloaded!")
-
-
-def _cleanup(*tar_files: str):
- for tar_file in tar_files:
- shutil.rmtree(os.path.join(_PROJECT_ROOT, "dist"), ignore_errors=True)
- os.remove(tar_file)
-
-
-def _prepare_wheel(path):
- with open("log.txt", "w") as logfile:
- with subprocess.Popen(
- ["rm", "-r", "dist"], stdout=logfile, stderr=logfile, bufsize=0, close_fds=True, cwd=path
- ) as proc:
- proc.wait()
-
- with subprocess.Popen(
- ["python", "setup.py", "sdist"],
- stdout=logfile,
- stderr=logfile,
- bufsize=0,
- close_fds=True,
- cwd=path,
- ) as proc:
- proc.wait()
-
- os.remove("log.txt")
-
-
-def _copy_tar(project_root, dest: Path) -> str:
- dist_dir = os.path.join(project_root, "dist")
- tar_files = os.listdir(dist_dir)
- assert len(tar_files) == 1
- tar_name = tar_files[0]
- tar_path = os.path.join(dist_dir, tar_name)
- shutil.copy(tar_path, dest)
- return tar_name
-
-
-def get_dist_path_if_editable_install(project_name) -> str:
- """Is distribution an editable install - modified version from pip that
- fetches egg-info instead of egg-link"""
- for path_item in sys.path:
- if not os.path.isdir(path_item):
- continue
-
- egg_info = os.path.join(path_item, project_name + ".egg-info")
- if os.path.isdir(egg_info):
- return path_item
- return ""
-
-
-def _prepare_lightning_wheels_and_requirements(root: Path, package_name: str = "lightning") -> Optional[Callable]:
- """This function determines if lightning is installed in editable mode (for developers) and packages the current
- lightning source along with the app.
-
- For normal users who install via PyPi or Conda, then this function does not do anything.
-
- """
- if not get_dist_path_if_editable_install(package_name):
- return None
-
- os.environ["PACKAGE_NAME"] = "app" if package_name == "lightning" + "_app" else "lightning"
-
- # Packaging the Lightning codebase happens only inside the `lightning` repo.
- git_dir_name = get_dir_name() if check_github_repository() else None
-
- is_lightning = git_dir_name and git_dir_name == package_name
-
- if (PACKAGE_LIGHTNING is None and not is_lightning) or PACKAGE_LIGHTNING == "0":
- return None
-
- download_frontend(_PROJECT_ROOT)
- _prepare_wheel(_PROJECT_ROOT)
-
- # todo: check why logging.info is missing in outputs
- print(f"Packaged Lightning with your application. Version: {version}")
-
- tar_name = _copy_tar(_PROJECT_ROOT, root)
-
- tar_files = [os.path.join(root, tar_name)]
-
- # Don't skip by default
- if (PACKAGE_LIGHTNING or is_lightning) and not bool(int(os.getenv("SKIP_LIGHTING_UTILITY_WHEELS_BUILD", "0"))):
- # building and copying lightning-cloud wheel if installed in editable mode
- lightning_cloud_project_path = get_dist_path_if_editable_install("lightning_cloud")
- if lightning_cloud_project_path:
- from lightning_cloud.__version__ import __version__ as cloud_version
-
- # todo: check why logging.info is missing in outputs
- print(f"Packaged Lightning Cloud with your application. Version: {cloud_version}")
- _prepare_wheel(lightning_cloud_project_path)
- tar_name = _copy_tar(lightning_cloud_project_path, root)
- tar_files.append(os.path.join(root, tar_name))
-
- lightning_launcher_project_path = get_dist_path_if_editable_install("lightning_launcher")
- if lightning_launcher_project_path:
- from lightning_launcher.__version__ import __version__ as cloud_version
-
- # todo: check why logging.info is missing in outputs
- print(f"Packaged Lightning Launcher with your application. Version: {cloud_version}")
- _prepare_wheel(lightning_launcher_project_path)
- tar_name = _copy_tar(lightning_launcher_project_path, root)
- tar_files.append(os.path.join(root, tar_name))
-
- return functools.partial(_cleanup, *tar_files)
-
-
-def _enable_debugging():
- tar_file = os.path.join(os.getcwd(), f"lightning-{version}.tar.gz")
-
- if not os.path.exists(tar_file):
- return
-
- _root_logger.propagate = True
- _logger.propagate = True
- _root_logger.setLevel(logging.DEBUG)
- _root_logger.debug("Setting debugging mode.")
-
-
-def enable_debugging(func: Callable) -> Callable:
- """This function is used to transform any print into logger.info calls, so it gets tracked in the cloud."""
-
- @functools.wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> Any:
- _enable_debugging()
- res = func(*args, **kwargs)
- _logger.setLevel(logging.INFO)
- return res
-
- return wrapper
-
-
-def _fetch_latest_version(package_name: str) -> str:
- args = [
- sys.executable,
- "-m",
- "pip",
- "install",
- f"{package_name}==1000",
- ]
-
- proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0, close_fds=True)
- if proc.stdout:
- logs = " ".join([line.decode("utf-8") for line in iter(proc.stdout.readline, b"")])
- return logs.split(")\n")[0].split(",")[-1].replace(" ", "")
- return version
-
-
-def _verify_lightning_version():
- """This function verifies that users are running the latest lightning version for the cloud."""
- # TODO (tchaton) Add support for windows
- if sys.platform == "win32":
- return
-
- lightning_latest_version = _fetch_latest_version("lightning")
-
- if Version(lightning_latest_version) > Version(version):
- raise Exception(
- f"You need to use the latest version of Lightning ({lightning_latest_version}) to run in the cloud. "
- "Please, run `pip install -U lightning`"
- )
diff --git a/src/lightning/app/utilities/packaging/tarfile.py b/src/lightning/app/utilities/packaging/tarfile.py
deleted file mode 100644
index d19c25e78403e..0000000000000
--- a/src/lightning/app/utilities/packaging/tarfile.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import shutil
-import tarfile
-
-
-def clean_tarfile(file_path: str, mode: str) -> None:
- """This utility removes all files extracted from a tarfile."""
- if not os.path.exists(file_path):
- return
-
- with tarfile.open(file_path, mode=mode) as tar_ref:
- for member in tar_ref.getmembers():
- p = member.path
- if p == "." or not os.path.exists(p):
- continue
- try:
- if os.path.isfile(p):
- os.remove(p)
- else:
- shutil.rmtree(p)
- except (FileNotFoundError, OSError, PermissionError):
- pass
-
- if os.path.exists(file_path):
- os.remove(file_path)
-
-
-def extract_tarfile(file_path: str, extract_path: str, mode: str) -> None:
- """This utility extracts all files from a tarfile."""
- if not os.path.exists(file_path):
- return
-
- with tarfile.open(file_path, mode=mode) as tar_ref:
- for member in tar_ref.getmembers():
- try:
- tar_ref.extract(member, path=extract_path, set_attrs=False)
- except PermissionError:
- raise PermissionError(f"Could not extract tar file {file_path}")
diff --git a/src/lightning/app/utilities/port.py b/src/lightning/app/utilities/port.py
deleted file mode 100644
index d26036794f2ca..0000000000000
--- a/src/lightning/app/utilities/port.py
+++ /dev/null
@@ -1,168 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import socket
-from typing import Optional
-
-from lightning_cloud.openapi import AppinstancesIdBody, Externalv1LightningappInstance, V1NetworkConfig
-
-from lightning.app.utilities.network import LightningClient, find_free_network_port
-
-
-def is_port_in_use(port: int) -> bool:
- """Checks if the given port is in use."""
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- return s.connect_ex(("localhost", port)) == 0
-
-
-def _find_lit_app_port(default_port: int) -> int:
- """Make a request to the cloud controlplane to find a disabled port of the flow, enable it and return it."""
- app_id = os.getenv("LIGHTNING_CLOUD_APP_ID", None)
- project_id = os.getenv("LIGHTNING_CLOUD_PROJECT_ID", None)
- enable_multiple_works_in_default_container = bool(int(os.getenv("ENABLE_MULTIPLE_WORKS_IN_DEFAULT_CONTAINER", "0")))
-
- if not app_id or not project_id or not enable_multiple_works_in_default_container:
- app_port = default_port
-
- # If the default port is not available, picks any other available one
- if is_port_in_use(default_port):
- app_port = find_free_network_port()
-
- return app_port
-
- client = LightningClient()
- list_apps_resp = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id)
- lit_app: Optional[Externalv1LightningappInstance] = None
-
- for lapp in list_apps_resp.lightningapps:
- if lapp.id == app_id:
- lit_app = lapp
-
- if not lit_app:
- raise RuntimeError(
- "App was not found. Please open an issue at https://github.com/lightning-AI/lightning/issues."
- )
-
- found_nc = None
-
- for nc in lit_app.spec.network_config:
- if not nc.enable:
- found_nc = nc
- nc.enable = True
- break
-
- client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=lit_app.id,
- body=AppinstancesIdBody(name=lit_app.name, spec=lit_app.spec),
- )
-
- if not found_nc:
- raise RuntimeError(
- "No available port was found. Please open an issue at https://github.com/lightning-AI/lightning/issues."
- )
-
- # Note: This is required for the framework to know we need to use the CloudMultiProcessRuntime.
- os.environ["APP_SERVER_HOST"] = f"https://{found_nc.host}"
-
- return found_nc.port
-
-
-def enable_port() -> V1NetworkConfig:
- """Make a request to the cloud controlplane to open a port of the flow."""
- app_id = os.getenv("LIGHTNING_CLOUD_APP_ID", None)
- project_id = os.getenv("LIGHTNING_CLOUD_PROJECT_ID", None)
-
- if not app_id or not project_id:
- raise Exception("The app_id and project_id should be defined.")
-
- client = LightningClient()
- list_apps_resp = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id)
- lit_app: Optional[Externalv1LightningappInstance] = None
-
- for lapp in list_apps_resp.lightningapps:
- if lapp.id == app_id:
- lit_app = lapp
-
- if not lit_app:
- raise RuntimeError(
- "App was not found. Please open an issue at https://github.com/lightning-AI/lightning/issues."
- )
-
- found_nc = None
-
- for nc in lit_app.spec.network_config:
- if not nc.enable:
- found_nc = nc
- nc.enable = True
- break
-
- client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=lit_app.id,
- body=AppinstancesIdBody(name=lit_app.name, spec=lit_app.spec),
- )
-
- if not found_nc:
- raise RuntimeError(
- "No available port was found. Please open an issue at https://github.com/lightning-AI/lightning/issues."
- )
-
- return found_nc
-
-
-def disable_port(port: int, ignore_disabled: bool = True) -> None:
- """Make a request to the cloud controlplane to close a port of the flow."""
- app_id = os.getenv("LIGHTNING_CLOUD_APP_ID", None)
- project_id = os.getenv("LIGHTNING_CLOUD_PROJECT_ID", None)
-
- if not app_id or not project_id:
- raise Exception("The app_id and project_id should be defined.")
-
- client = LightningClient()
- list_apps_resp = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id)
- lit_app: Optional[Externalv1LightningappInstance] = None
-
- for lapp in list_apps_resp.lightningapps:
- if lapp.id == app_id:
- lit_app = lapp
-
- if not lit_app:
- raise RuntimeError(
- "App was not found. Please open an issue at https://github.com/lightning-AI/lightning/issues."
- )
-
- found_nc = None
-
- for nc in lit_app.spec.network_config:
- if nc.port == port:
- if not nc.enable and not ignore_disabled:
- raise RuntimeError(f"The port {port} was already disabled.")
-
- nc.enable = False
- found_nc = nc
- break
-
- client.lightningapp_instance_service_update_lightningapp_instance(
- project_id=project_id,
- id=lit_app.id,
- body=AppinstancesIdBody(name=lit_app.name, spec=lit_app.spec),
- )
-
- if not found_nc:
- ports = [nc.port for nc in lit_app.spec.network_config]
- raise ValueError(f"The provided port doesn't exists. Available ports are {ports}.")
-
- assert found_nc
diff --git a/src/lightning/app/utilities/proxies.py b/src/lightning/app/utilities/proxies.py
deleted file mode 100644
index f16cf9086c97a..0000000000000
--- a/src/lightning/app/utilities/proxies.py
+++ /dev/null
@@ -1,766 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import pathlib
-import queue
-import signal
-import sys
-import threading
-import time
-import traceback
-import warnings
-from contextlib import contextmanager
-from copy import deepcopy
-from dataclasses import dataclass, field
-from functools import partial
-from threading import Event, Thread
-from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, Optional, Set, Tuple, Type, Union
-
-from deepdiff import DeepDiff, Delta
-from lightning_utilities.core.apply_func import apply_to_collection
-
-from lightning.app.core import constants
-from lightning.app.core.queues import MultiProcessQueue
-from lightning.app.storage.copier import _Copier, _copy_files
-from lightning.app.storage.drive import Drive, _maybe_create_drive
-from lightning.app.storage.path import Path, _path_to_work_artifact
-from lightning.app.storage.payload import Payload
-from lightning.app.utilities.app_helpers import affiliation
-from lightning.app.utilities.component import _set_work_context
-from lightning.app.utilities.enum import (
- CacheCallsKeys,
- WorkFailureReasons,
- WorkStageStatus,
- WorkStopReasons,
- make_status,
-)
-from lightning.app.utilities.exceptions import CacheMissException, LightningSigtermStateException
-
-if TYPE_CHECKING:
- from lightning.app.core import LightningWork
- from lightning.app.core.queues import BaseQueue
-
-from lightning.app.utilities.app_helpers import Logger
-
-logger = Logger(__name__)
-_state_observer_lock = threading.Lock()
-
-
-@dataclass
-class Action:
- method: str = "run"
- args: Tuple = field(default_factory=lambda: ())
- kwargs: Dict = field(default_factory=lambda: {})
-
-
-def unwrap(fn):
- if isinstance(fn, partial):
- fn = fn.keywords["work_run"]
- if isinstance(fn, ProxyWorkRun):
- fn = fn.work_run
- while hasattr(fn, "__wrapped__"):
- fn = fn.__wrapped__
- return fn
-
-
-def _send_data_to_caller_queue(
- proxy, work: "LightningWork", caller_queue: "BaseQueue", data: Dict, call_hash: str
-) -> Dict:
- proxy.has_sent = True
-
- if work._calls[CacheCallsKeys.LATEST_CALL_HASH] is None:
- work._calls[CacheCallsKeys.LATEST_CALL_HASH] = call_hash
-
- if call_hash not in work._calls:
- work._calls[call_hash] = {"statuses": []}
- else:
- # remove ret when relaunching the work.
- work._calls[call_hash].pop("ret", None)
-
- work._calls[call_hash]["statuses"].append(make_status(WorkStageStatus.PENDING))
-
- work_state = work.state
-
- # There is no need to send all call hashes to the work.
- calls = deepcopy(work_state["calls"])
- work_state["calls"] = {
- k: v for k, v in work_state["calls"].items() if k in (call_hash, CacheCallsKeys.LATEST_CALL_HASH)
- }
-
- data.update({"state": work_state})
- logger.debug(f"Sending to {work.name}: {data}")
- caller_queue.put(deepcopy(data))
-
- # Reset the calls entry.
- work_state["calls"] = calls
- work._restarting = False
- return work_state
-
-
-@dataclass
-class ProxyWorkRun:
- work_run: Callable
- work_name: str # TODO: remove this argument and get the name from work.name directly
- work: "LightningWork"
- caller_queue: "BaseQueue"
-
- def __post_init__(self):
- self.work_state = None
-
- def __call__(self, *args: Any, **kwargs: Any):
- self.has_sent = False
-
- self._validate_call_args(args, kwargs)
- args, kwargs = self._process_call_args(args, kwargs)
-
- call_hash = self.work._call_hash(self.work_run, *self._convert_hashable(args, kwargs))
- entered = call_hash in self.work._calls
- returned = entered and "ret" in self.work._calls[call_hash]
- # TODO (tchaton): Handle spot instance retrieval differently from stopped work.
- stopped_on_sigterm = self.work._restarting and self.work.status.reason == WorkStopReasons.SIGTERM_SIGNAL_HANDLER
-
- data = {"args": args, "kwargs": kwargs, "call_hash": call_hash}
-
- # The if/else conditions are left un-compressed to simplify readability for the readers.
- if not entered or stopped_on_sigterm:
- _send_data_to_caller_queue(self, self.work, self.caller_queue, data, call_hash)
- else:
- if self.work.cache_calls and returned:
- return
- if returned or stopped_on_sigterm:
- # the previous task has completed and we can re-queue the next one.
- # overriding the return value for next loop iteration.
- _send_data_to_caller_queue(self, self.work, self.caller_queue, data, call_hash)
- if not self.work.parallel:
- raise CacheMissException("Task never called before. Triggered now")
-
- def _validate_call_args(self, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> None:
- """Validate the call args before they get passed to the run method of the Work.
-
- Currently, this performs a check against strings that look like filesystem paths and may need to be wrapped with
- a Lightning Path by the user.
-
- """
-
- def warn_if_pathlike(obj: Union[os.PathLike, str]):
- if isinstance(obj, Path):
- return
- if os.sep in str(obj) and os.path.exists(obj):
- # NOTE: The existence check is wrong in general, as the file will never exist on the disk
- # where the flow is running unless we are running locally
- warnings.warn(
- f"You passed a the value {obj!r} as an argument to the `run()` method of {self.work_name} and"
- f" it looks like this is a path to a file or a folder. Consider wrapping this path in a"
- f" `lightning.app.storage.Path` object to be able to access these files in your Work.",
- UserWarning,
- )
-
- apply_to_collection(args, dtype=(os.PathLike, str), function=warn_if_pathlike)
- apply_to_collection(kwargs, dtype=(os.PathLike, str), function=warn_if_pathlike)
-
- @staticmethod
- def _process_call_args(args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
- """Processes all positional and keyword arguments before they get passed to the caller queue and sent to the
- LightningWork.
-
- Currently, this method only applies sanitization to Lightning Path objects.
-
- Args:
- args: The tuple of positional arguments passed to the run method.
- kwargs: The dictionary of named arguments passed to the run method.
-
- Returns:
- The positional and keyword arguments in the same order they were passed in.
-
- """
-
- def sanitize(obj: Union[Path, Drive]) -> Union[Path, Dict]:
- if isinstance(obj, Path):
- # create a copy of the Path and erase the consumer
- # the LightningWork on the receiving end of the caller queue will become the new consumer
- # this is necessary to make the Path deepdiff-hashable
- path_copy = Path(obj)
- path_copy._sanitize()
- path_copy._consumer = None
- return path_copy
- return obj.to_dict()
-
- return apply_to_collection((args, kwargs), dtype=(Path, Drive), function=sanitize)
-
- @staticmethod
- def _convert_hashable(args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
- """Processes all positional and keyword arguments before they get passed to the caller queue and sent to the
- LightningWork.
-
- Currently, this method only applies sanitization to Hashable Objects.
-
- Args:
- args: The tuple of positional arguments passed to the run method.
- kwargs: The dictionary of named arguments passed to the run method.
-
- Returns:
- The positional and keyword arguments in the same order they were passed in.
-
- """
- from lightning.app.utilities.types import Hashable
-
- def sanitize(obj: Hashable) -> Union[Path, Dict]:
- return obj.to_dict()
-
- return apply_to_collection((args, kwargs), dtype=Hashable, function=sanitize)
-
-
-class WorkStateObserver(Thread):
- """This thread runs alongside LightningWork and periodically checks for state changes. If the state changed from
- one interval to the next, it will compute the delta and add it to the queue which is connected to the Flow. This
- enables state changes to be captured that are not triggered through a setattr call.
-
- Args:
- work: The LightningWork for which the state should be monitored
- delta_queue: The queue to send deltas to when state changes occur
- interval: The interval at which to check for state changes.
-
- Example:
-
- class Work(LightningWork):
- ...
-
- def run(self):
- # This update gets sent to the Flow once the thread compares the new state with the previous one
- self.list.append(1)
-
- """
-
- def __init__(
- self,
- work: "LightningWork",
- delta_queue: "BaseQueue",
- flow_to_work_delta_queue: Optional["BaseQueue"] = None,
- error_queue: Optional["BaseQueue"] = None,
- interval: float = 1,
- ) -> None:
- super().__init__(daemon=True)
- self.started = False
- self._work = work
- self._delta_queue = delta_queue
- self._flow_to_work_delta_queue = flow_to_work_delta_queue
- self._error_queue = error_queue
- self._interval = interval
- self._exit_event = Event()
- self._delta_memory = []
- self._last_state = deepcopy(self._work.state)
-
- def run(self) -> None:
- self.started = True
- while not self._exit_event.is_set():
- time.sleep(self._interval)
- # Run the thread only if active
- self.run_once()
-
- @staticmethod
- def get_state_changed_from_queue(q: "BaseQueue", timeout: Optional[int] = None):
- try:
- delta = q.get(timeout=timeout or q.default_timeout)
- return delta
- except queue.Empty:
- return None
-
- def run_once(self) -> None:
- with _state_observer_lock:
- # Add all deltas the LightningWorkSetAttrProxy has processed and sent to the Flow already while
- # the WorkStateObserver was sleeping
- for delta in self._delta_memory:
- self._last_state += delta
- self._delta_memory.clear()
-
- # The remaining delta is the result of state updates triggered outside the setattr, e.g, by a list append
- delta = Delta(DeepDiff(self._last_state, self._work.state, verbose_level=2))
- if not delta.to_dict():
- return
- self._last_state = deepcopy(self._work.state)
- self._delta_queue.put(ComponentDelta(id=self._work.name, delta=delta))
-
- if self._flow_to_work_delta_queue:
- while True:
- deep_diff = self.get_state_changed_from_queue(self._flow_to_work_delta_queue)
- if not isinstance(deep_diff, dict):
- break
- try:
- with _state_observer_lock:
- self._work.apply_flow_delta(Delta(deep_diff, raise_errors=True))
- except Exception as ex:
- print(traceback.print_exc())
- self._error_queue.put(ex)
- raise ex
-
- def join(self, timeout: Optional[float] = None) -> None:
- self._exit_event.set()
- super().join(timeout)
-
-
-@dataclass
-class LightningWorkSetAttrProxy:
- """This wrapper around the ``LightningWork.__setattr__`` ensures that state changes get sent to the delta queue to
- be reflected in the Flow.
-
- Example:
-
- class Work(LightningWork):
- ...
-
- def run(self):
- self.var += 1 # This update gets sent to the Flow immediately
-
- """
-
- work_name: str
- work: "LightningWork"
- delta_queue: "BaseQueue"
- state_observer: Optional["WorkStateObserver"]
-
- def __call__(self, name: str, value: Any) -> None:
- logger.debug(f"Setting {name}: {value}")
- with _state_observer_lock:
- state = deepcopy(self.work.state)
- self.work._default_setattr(name, value)
- delta = Delta(DeepDiff(state, self.work.state, verbose_level=2))
- if not delta.to_dict():
- return
-
- # push the delta only if there is any
- self.delta_queue.put(ComponentDelta(id=self.work_name, delta=delta))
-
- # add the delta to the buffer to let WorkStateObserver know we already sent this one to the Flow
- if self.state_observer:
- self.state_observer._delta_memory.append(delta)
-
-
-@dataclass
-class ComponentDelta:
- id: str
- delta: Delta
-
-
-@dataclass
-class WorkRunExecutor:
- work: "LightningWork"
- work_run: Callable
- delta_queue: "BaseQueue"
- enable_start_observer: bool = True
-
- def __call__(self, *args, **kwargs):
- return self.work_run(*args, **kwargs)
-
- @contextmanager
- def enable_spawn(self) -> Generator:
- self.work._setattr_replacement = None
- self.work._backend = None
- self._clean_queues()
- yield
-
- def _clean_queues(self):
- if not isinstance(self.work._request_queue, MultiProcessQueue):
- self.work._request_queue = self.work._request_queue.to_dict()
- self.work._response_queue = self.work._response_queue.to_dict()
-
- @staticmethod
- def process_queue(queue):
- from lightning.app.core.queues import HTTPQueue, RedisQueue
-
- if isinstance(queue, dict):
- queue_type = queue.pop("type")
- if queue_type == "redis":
- return RedisQueue.from_dict(queue)
- return HTTPQueue.from_dict(queue)
- return queue
-
-
-@dataclass
-class WorkRunner:
- work: "LightningWork"
- work_name: str
- caller_queue: "BaseQueue"
- delta_queue: "BaseQueue"
- readiness_queue: "BaseQueue"
- error_queue: "BaseQueue"
- request_queue: "BaseQueue"
- response_queue: "BaseQueue"
- copy_request_queue: "BaseQueue"
- copy_response_queue: "BaseQueue"
- flow_to_work_delta_queue: Optional["BaseQueue"] = None
- run_executor_cls: Type[WorkRunExecutor] = WorkRunExecutor
- enable_copier: bool = constants.ENABLE_ORCHESTRATOR
-
- def __post_init__(self):
- self.parallel = self.work.parallel
- self.copier: Optional[_Copier] = None
- self.state_observer: Optional[WorkStateObserver] = None
-
- def __call__(self):
- self.setup()
- while True:
- try:
- self.run_once()
- except KeyboardInterrupt:
- if self.state_observer:
- if self.state_observer.started:
- self.state_observer.join(0)
- self.state_observer = None
- if self.copier:
- self.copier.join(0)
- except LightningSigtermStateException as ex:
- logger.debug("Exiting")
- os._exit(ex.exit_code)
- except Exception as ex:
- # Inform the flow the work failed. This would fail the entire application.
- self.error_queue.put(ex)
- # Terminate the threads
- if self.state_observer:
- if self.state_observer.started:
- self.state_observer.join(0)
- self.state_observer = None
- if self.copier:
- self.copier.join(0)
- raise ex
-
- def setup(self):
- from lightning.app.utilities.state import AppState
-
- _set_work_context()
-
- # 1. Make the AppState aware of the affiliation of the work.
- # hacky: attach affiliation to be know from the AppState object
- AppState._MY_AFFILIATION = affiliation(self.work)
-
- # 2. Attach the queues to the work.
- # Each work gets their own request- and response storage queue for communicating with the storage orchestrator
- self.work._request_queue = self.request_queue
- self.work._response_queue = self.response_queue
-
- # 3. Starts the Copier thread. This thread enables transfering files using
- # the Path object between works.
- if self.enable_copier:
- self.copier = _Copier(self.work, self.copy_request_queue, self.copy_response_queue)
- self.copier.setDaemon(True)
- self.copier.start()
-
- # 4. If the work is restarting, reload the latest state.
- # TODO (tchaton) Add support for capturing the latest state.
- if self.work._restarting:
- self.work.load_state_dict(self.work.state)
-
- # 7. Deepcopy the work state and send the first `RUNNING` status delta to the flow.
- reference_state = deepcopy(self.work.state)
-
- # Set the internal IP address.
- # Set this here after the state observer is initialized, since it needs to record it as a change and send
- # it back to the flow
- default_internal_ip = "127.0.0.1" if constants.LIGHTNING_CLOUDSPACE_HOST is None else "0.0.0.0" # noqa: S104
- self.work._internal_ip = os.environ.get("LIGHTNING_NODE_PRIVATE_IP", default_internal_ip)
- self.work._public_ip = os.environ.get("LIGHTNING_NODE_IP", "")
-
- self.work.on_start()
-
- delta = Delta(DeepDiff(reference_state, self.work.state))
- logger.debug(f"Sending delta_queue {delta}")
- self.delta_queue.put(ComponentDelta(id=self.work_name, delta=delta))
-
- # # 8. Inform the flow that the work is ready to receive data through the caller queue.
- # self.readiness_queue.put(True)
-
- def run_once(self):
- # 1. Wait for the caller queue data.
- called: Dict[str, Any] = self.caller_queue.get()
- logger.debug(f"Work {self.work_name} {called}")
-
- # 2. Extract the info from the caller queue data and process the input arguments. Arguments can contain
- # Lightning Path objects; if they don't have a consumer, the current Work will become one.
- call_hash = called["call_hash"]
- args, kwargs = self._process_call_args(called["args"], called["kwargs"])
-
- # 3. Register the signal handler for spot instances.
- # `SIGUSR1` signal isn't supported on windows.
- # TODO (tchaton) Add support for windows
- if sys.platform != "win32":
- signal.signal(signal.SIGTERM, partial(self._sigterm_signal_handler, call_hash=call_hash))
-
- # 4. Set the received state to the work.
- self.work.set_state(called["state"])
-
- # 5. Transfer all paths in the state automatically if they have an origin and exist
- self._transfer_path_attributes()
-
- # 6. Create the state observer thread.
- if self.run_executor_cls.enable_start_observer:
- self.state_observer = WorkStateObserver(
- self.work,
- delta_queue=self.delta_queue,
- flow_to_work_delta_queue=self.flow_to_work_delta_queue,
- error_queue=self.error_queue,
- )
-
- # 7. Deepcopy the work state and send the first `RUNNING` status delta to the flow.
- reference_state = deepcopy(self.work.state)
-
- # Set the internal IP address.
- # Set this here after the state observer is initialized, since it needs to record it as a change and send
- # it back to the flow
- default_internal_ip = "127.0.0.1" if constants.LIGHTNING_CLOUDSPACE_HOST is None else "0.0.0.0" # noqa: S104
- self.work._internal_ip = os.environ.get("LIGHTNING_NODE_PRIVATE_IP", default_internal_ip)
- self.work._public_ip = os.environ.get("LIGHTNING_NODE_IP", "")
-
- # 8. Patch the setattr method of the work. This needs to be done after step 4, so we don't
- # send delta while calling `set_state`.
- self._proxy_setattr()
-
- if self._is_starting(called, reference_state, call_hash):
- return
-
- # 9. Inform the flow the work is running and add the delta to the deepcopy.
- self.work._calls[CacheCallsKeys.LATEST_CALL_HASH] = call_hash
- self.work._calls[call_hash]["statuses"].append(make_status(WorkStageStatus.RUNNING))
- delta = Delta(DeepDiff(reference_state, self.work.state))
- self.delta_queue.put(ComponentDelta(id=self.work_name, delta=delta))
-
- # 10. Unwrap the run method if wrapped.
- work_run = self.work.run
- if hasattr(work_run, "__wrapped__"):
- work_run = work_run.__wrapped__
-
- # 11. Start the state observer thread. It will look for state changes and send them back to the Flow
- # The observer has to be initialized here, after the set_state call above so that the thread can start with
- # the proper initial state of the work
- if self.run_executor_cls.enable_start_observer:
- self.state_observer.start()
-
- # 12. Run the `work_run` method.
- # If an exception is raised, send a `FAILED` status delta to the flow and call the `on_exception` hook.
- try:
- ret = self.run_executor_cls(self.work, work_run, self.delta_queue)(*args, **kwargs)
- except LightningSigtermStateException as ex:
- raise ex
- except BaseException as ex:
- # 10.2 Send failed delta to the flow.
- reference_state = deepcopy(self.work.state)
- exp, val, tb = sys.exc_info()
- listing = traceback.format_exception(exp, val, tb)
- user_exception = False
- used_runpy = False
- trace = []
- for p in listing:
- if "runpy.py" in p:
- trace = []
- used_runpy = True
- if user_exception:
- trace.append(p)
- if "ret = self.run_executor_cls(" in p:
- user_exception = True
-
- if used_runpy:
- trace = trace[1:]
-
- self.work._calls[call_hash]["statuses"].append(
- make_status(
- WorkStageStatus.FAILED,
- message=str("\n".join(trace)),
- reason=WorkFailureReasons.USER_EXCEPTION,
- )
- )
- self.delta_queue.put(
- ComponentDelta(
- id=self.work_name, delta=Delta(DeepDiff(reference_state, self.work.state, verbose_level=2))
- )
- )
- self.work.on_exception(ex)
- print("########## CAPTURED EXCEPTION ###########")
- print(traceback.print_exc())
- print("########## CAPTURED EXCEPTION ###########")
- return
-
- # 13. Destroy the state observer.
- if self.run_executor_cls.enable_start_observer and self.state_observer.started:
- self.state_observer.join(0)
- self.state_observer = None
-
- # 14. Copy all artifacts to the shared storage so other Works can access them while this Work gets scaled down
- persist_artifacts(work=self.work)
-
- # 15. An asynchronous work shouldn't return a return value.
- if ret is not None:
- raise RuntimeError(
- f"Your work {self.work} shouldn't have a return value. Found {ret}."
- "HINT: Use the Payload API instead."
- )
-
- # 17. DeepCopy the state and send the latest delta to the flow.
- # use the latest state as we have already sent delta
- # during its execution.
- # inform the task has completed
- reference_state = deepcopy(self.work.state)
- self.work._calls[call_hash]["statuses"].append(make_status(WorkStageStatus.SUCCEEDED))
- self.work._calls[call_hash]["ret"] = ret
- self.delta_queue.put(
- ComponentDelta(id=self.work_name, delta=Delta(DeepDiff(reference_state, self.work.state, verbose_level=2)))
- )
-
- # 18. Update the work for the next delta if any.
- self._proxy_setattr(cleanup=True)
-
- def _sigterm_signal_handler(self, signum, frame, call_hash: str) -> None:
- """Signal handler used to react when spot instances are being retrived."""
- logger.info(f"Received SIGTERM signal. Gracefully terminating {self.work.name.replace('root.', '')}...")
- persist_artifacts(work=self.work)
- with _state_observer_lock:
- self.work.on_exit()
- self.work._calls[call_hash]["statuses"] = []
- state = deepcopy(self.work.state)
- self.work._calls[call_hash]["statuses"].append(
- make_status(WorkStageStatus.STOPPED, reason=WorkStopReasons.SIGTERM_SIGNAL_HANDLER)
- )
-
- # kill the thread as the job is going to be terminated.
- if self.state_observer:
- if self.state_observer.started:
- self.state_observer.join(0)
- self.state_observer = None
- delta = Delta(DeepDiff(state, deepcopy(self.work.state), verbose_level=2))
- self.delta_queue.put(ComponentDelta(id=self.work_name, delta=delta))
-
- if self.copier:
- self.copier.join(0)
- raise LightningSigtermStateException(0)
-
- def _proxy_setattr(self, cleanup: bool = False):
- _proxy_setattr(self.work, self.delta_queue, self.state_observer, cleanup=cleanup)
-
- def _process_call_args(
- self, args: Tuple[Any, ...], kwargs: Dict[str, Any]
- ) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
- """Process the arguments that were passed in to the ``run()`` method of the
- :class:`lightning.app.core.work.LightningWork`.
-
- This method currently only implements special treatments for the :class:`lightning.app.storage.path.Path`
- objects. Any Path objects that get passed into the run method get attached to the Work automatically, i.e.,
- the Work becomes the `origin` or the `consumer` if they were not already before. Additionally,
- if the file or folder under the Path exists, we transfer it.
-
- Args:
- args: The tuple of positional arguments passed to the run method.
- kwargs: The dictionary of named arguments passed to the run method.
-
- Returns:
- The positional and keyword arguments in the same order they were passed in.
-
- """
-
- def _attach_work_and_get(transporter: Union[Path, Payload, dict]) -> Union[Path, Drive, dict, Any]:
- if not transporter.origin_name:
- # If path/payload is not attached to an origin, there is no need to attach or transfer anything
- return transporter
-
- transporter._attach_work(self.work)
- transporter._attach_queues(self.work._request_queue, self.work._response_queue)
- if transporter.exists_remote():
- # All paths/payloads passed to the `run` method under a Lightning obj need to be copied (if they exist)
- if isinstance(transporter, Payload):
- transporter.get()
- else:
- transporter.get(overwrite=True)
- return transporter
-
- def _handle_drive(dict):
- return _maybe_create_drive(self.work_name, dict)
-
- args, kwargs = apply_to_collection((args, kwargs), dtype=(Path, Payload), function=_attach_work_and_get)
- return apply_to_collection((args, kwargs), dtype=dict, function=_handle_drive)
-
- def _transfer_path_attributes(self) -> None:
- """Transfer all Path attributes in the Work if they have an origin and exist."""
- for name in self.work._paths:
- path = getattr(self.work, name)
- if isinstance(path, str):
- path = Path(path)
- path._attach_work(self.work)
- if path.origin_name and path.origin_name != self.work.name and path.exists_remote():
- path.get(overwrite=True)
-
- def _is_starting(self, called, reference_state, call_hash) -> bool:
- if len(called["args"]) == 1 and isinstance(called["args"][0], Action):
- action = called["args"][0]
- if action.method == "start":
- # 9. Inform the flow the work is running and add the delta to the deepcopy.
- self.work._calls[CacheCallsKeys.LATEST_CALL_HASH] = call_hash
- self.work._calls[call_hash]["statuses"].append(make_status(WorkStageStatus.STARTED))
- delta = Delta(DeepDiff(reference_state, self.work.state))
- self.delta_queue.put(ComponentDelta(id=self.work_name, delta=delta))
- self._proxy_setattr(cleanup=True)
- return True
- raise Exception("Only the `start` action is supported right now !")
- return False
-
-
-def persist_artifacts(work: "LightningWork") -> None:
- """Copies all :class:`~lightning.app.storage.path.Path` referenced by the given LightningWork to the shared
- storage.
-
- Files that don't exist or do not originate from the given Work will be skipped.
-
- """
- artifact_paths = [getattr(work, name) for name in work._paths]
- # only copy files that belong to this Work, i.e., when the path's origin refers to the current Work
- artifact_paths = [path for path in artifact_paths if isinstance(path, Path) and path.origin_name == work.name]
-
- for name in work._state:
- if isinstance(getattr(work, name), Payload):
- artifact_path = pathlib.Path(name).resolve()
- payload = getattr(work, name)
- payload.save(payload.value, artifact_path)
- artifact_paths.append(artifact_path)
-
- missing_artifacts: Set[str] = set()
- destination_paths = []
- for artifact_path in artifact_paths:
- artifact_path = pathlib.Path(artifact_path).absolute()
- if not artifact_path.exists():
- missing_artifacts.add(str(artifact_path))
- continue
- destination_path = _path_to_work_artifact(artifact_path, work)
- _copy_files(artifact_path, destination_path)
- destination_paths.append(destination_path)
-
- if missing_artifacts:
- warnings.warn(
- f"{len(missing_artifacts)} artifacts could not be saved because they don't exist:"
- f" {','.join(missing_artifacts)}.",
- UserWarning,
- )
- else:
- logger.debug(
- f"All {destination_paths} artifacts from Work {work.name} successfully "
- "stored at {artifacts_path(work.name)}."
- )
-
-
-def _proxy_setattr(work, delta_queue, state_observer: Optional[WorkStateObserver], cleanup: bool = False):
- if cleanup:
- setattr_proxy = None
- else:
- setattr_proxy = LightningWorkSetAttrProxy(
- work.name,
- work,
- delta_queue=delta_queue,
- state_observer=state_observer,
- )
- work._setattr_replacement = setattr_proxy
diff --git a/src/lightning/app/utilities/redis.py b/src/lightning/app/utilities/redis.py
deleted file mode 100644
index 5461d5d302ae3..0000000000000
--- a/src/lightning/app/utilities/redis.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Optional
-
-from lightning.app.core.constants import REDIS_HOST, REDIS_PASSWORD, REDIS_PORT
-from lightning.app.utilities.imports import _is_redis_available
-
-
-def check_if_redis_running(
- host: Optional[str] = "", port: Optional[int] = 6379, password: Optional[str] = None
-) -> bool:
- if not _is_redis_available():
- return False
- import redis
-
- try:
- host = host or REDIS_HOST
- port = port or REDIS_PORT
- password = password or REDIS_PASSWORD
- return redis.Redis(host=host, port=port, password=password).ping()
- except redis.exceptions.ConnectionError:
- return False
diff --git a/src/lightning/app/utilities/safe_pickle.py b/src/lightning/app/utilities/safe_pickle.py
deleted file mode 100644
index ddd77ddcc6509..0000000000000
--- a/src/lightning/app/utilities/safe_pickle.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import contextlib
-import pickle
-import sys
-import types
-import typing
-from copy import deepcopy
-from pathlib import Path
-
-from lightning.app.core.work import LightningWork
-from lightning.app.utilities.app_helpers import _LightningAppRef
-
-NON_PICKLABLE_WORK_ATTRIBUTES = ["_request_queue", "_response_queue", "_backend", "_setattr_replacement"]
-
-
-@contextlib.contextmanager
-def _trimmed_work(work: LightningWork, to_trim: typing.List[str]) -> typing.Iterator[None]:
- """Context manager to trim the work object to remove attributes that are not picklable."""
- holder = {}
- for arg in to_trim:
- holder[arg] = getattr(work, arg)
- setattr(work, arg, None)
- yield
- for arg in to_trim:
- setattr(work, arg, holder[arg])
-
-
-def get_picklable_work(work: LightningWork) -> LightningWork:
- """Pickling a LightningWork instance fails if done from the work process
- itself. This function is safe to call from the work process within both MultiprocessRuntime
- and Cloud.
- Note: This function modifies the module information of the work object. Specifically, it injects
- the relative module path into the __module__ attribute of the work object. If the object is not
- importable from the CWD, then the pickle load will fail.
-
- Example:
- for a directory structure like below and the work class is defined in the app.py where
- the app.py is the entrypoint for the app, it will inject `foo.bar.app` into the
- __module__ attribute
-
- └── foo
- ├── __init__.py
- └── bar
- └── app.py
- """
- # If the work object not taken from the app ref, there is a thread lock reference
- # somewhere thats preventing it from being pickled. Investigate it later. We
- # shouldn't be fetching the work object from the app ref. TODO @sherin
- app_ref = _LightningAppRef.get_current()
- if app_ref is None:
- raise RuntimeError("Cannot pickle LightningWork outside of a LightningApp")
- for w in app_ref.works:
- if work.name == w.name:
- # deep-copying the work object to avoid modifying the original work object
- with _trimmed_work(w, to_trim=NON_PICKLABLE_WORK_ATTRIBUTES):
- copied_work = deepcopy(w)
- break
- else:
- raise ValueError(f"Work with name {work.name} not found in the app references")
-
- # if work is defined in the __main__ or __mp__main__ (the entrypoint file for `lightning run app` command),
- # pickling/unpickling will fail, hence we need patch the module information
- if "_main__" in copied_work.__class__.__module__:
- work_class_module = sys.modules[copied_work.__class__.__module__]
- work_class_file = work_class_module.__file__
- if not work_class_file:
- raise ValueError(
- f"Cannot pickle work class {copied_work.__class__.__name__} because we "
- f"couldn't identify the module file"
- )
- relative_path = Path(work_class_module.__file__).relative_to(Path.cwd()) # type: ignore
- expected_module_name = relative_path.as_posix().replace(".py", "").replace("/", ".")
- # TODO @sherin: also check if the module is importable from the CWD
- fake_module = types.ModuleType(expected_module_name)
- fake_module.__dict__.update(work_class_module.__dict__)
- fake_module.__dict__["__name__"] = expected_module_name
- sys.modules[expected_module_name] = fake_module
- for k, v in fake_module.__dict__.items():
- if not k.startswith("__") and hasattr(v, "__module__") and "_main__" in v.__module__:
- v.__module__ = expected_module_name
- return copied_work
-
-
-def dump(work: LightningWork, f: typing.BinaryIO) -> None:
- picklable_work = get_picklable_work(work)
- pickle.dump(picklable_work, f)
-
-
-def load(f: typing.BinaryIO) -> typing.Any:
- # inject current working directory to sys.path
- sys.path.insert(1, str(Path.cwd()))
- work = pickle.load(f)
- sys.path.pop(1)
- return work
diff --git a/src/lightning/app/utilities/scheduler.py b/src/lightning/app/utilities/scheduler.py
deleted file mode 100644
index 67b081fb56000..0000000000000
--- a/src/lightning/app/utilities/scheduler.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import threading
-from datetime import datetime
-from typing import Optional
-
-from croniter import croniter
-from deepdiff import Delta
-
-from lightning.app.utilities.proxies import ComponentDelta
-
-
-class SchedulerThread(threading.Thread):
- # TODO (tchaton) Abstract this logic to a generic scheduling service.
-
- def __init__(self, app) -> None:
- super().__init__(daemon=True)
- self._exit_event = threading.Event()
- self._sleep_time = 1.0
- self._app = app
-
- def run(self) -> None:
- while not self._exit_event.is_set():
- self._exit_event.wait(self._sleep_time)
- self.run_once()
-
- def run_once(self):
- for call_hash in list(self._app._schedules.keys()):
- metadata = self._app._schedules[call_hash]
- start_time = datetime.fromisoformat(metadata["start_time"])
- current_date = datetime.now()
- next_event = croniter(metadata["cron_pattern"], start_time).get_next(datetime)
- # When the event is reached, send a delta to activate scheduling.
- if current_date > next_event:
- component_delta = ComponentDelta(
- id=metadata["name"],
- delta=Delta({
- "values_changed": {
- f"root['calls']['scheduling']['{call_hash}']['running']": {"new_value": True}
- }
- }),
- )
- self._app.delta_queue.put(component_delta)
- metadata["start_time"] = next_event.isoformat()
-
- def join(self, timeout: Optional[float] = None) -> None:
- self._exit_event.set()
- super().join(timeout)
diff --git a/src/lightning/app/utilities/secrets.py b/src/lightning/app/utilities/secrets.py
deleted file mode 100644
index dee96c5d163a9..0000000000000
--- a/src/lightning/app/utilities/secrets.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Dict, Iterable
-
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.network import LightningClient
-
-
-def _names_to_ids(secret_names: Iterable[str]) -> Dict[str, str]:
- """Returns the name/ID pair for each given Secret name.
-
- Raises a `ValueError` if any of the given Secret names do not exist.
-
- """
- lightning_client = LightningClient()
-
- project = _get_project(lightning_client)
- secrets = lightning_client.secret_service_list_secrets(project_id=project.project_id)
-
- secret_names_to_ids: Dict[str, str] = {}
- for secret in secrets.secrets:
- if secret.name in secret_names:
- secret_names_to_ids[secret.name] = secret.id
-
- for secret_name in secret_names:
- if secret_name not in secret_names_to_ids:
- raise ValueError(f"Secret with name '{secret_name}' not found")
-
- return secret_names_to_ids
diff --git a/src/lightning/app/utilities/state.py b/src/lightning/app/utilities/state.py
deleted file mode 100644
index b366142948102..0000000000000
--- a/src/lightning/app/utilities/state.py
+++ /dev/null
@@ -1,323 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import enum
-import json
-import os
-from copy import deepcopy
-from time import sleep
-from typing import Any, Dict, List, Optional, Tuple, Union
-
-from deepdiff import DeepDiff
-from requests import Session
-from requests.exceptions import ConnectionError
-
-from lightning.app.core.constants import APP_SERVER_HOST, APP_SERVER_PORT
-from lightning.app.storage.drive import _maybe_create_drive
-from lightning.app.utilities.app_helpers import AppStatePlugin, BaseStatePlugin, Logger
-from lightning.app.utilities.network import LightningClient, _configure_session
-
-logger = Logger(__name__)
-
-# GLOBAL APP STATE
-_LAST_STATE = None
-_STATE = None
-
-
-class AppStateType(enum.Enum):
- STREAMLIT = enum.auto()
- DEFAULT = enum.auto()
-
-
-def headers_for(context: Dict[str, str]) -> Dict[str, str]:
- return {
- "X-Lightning-Session-UUID": context.get("token", ""),
- "X-Lightning-Session-ID": context.get("session_id", ""),
- "X-Lightning-Type": context.get("type", ""),
- }
-
-
-class AppState:
- _APP_PRIVATE_KEYS: Tuple[str, ...] = (
- "_use_localhost",
- "_host",
- "_session_id",
- "_state",
- "_last_state",
- "_url",
- "_port",
- "_request_state",
- "_store_state",
- "_send_state",
- "_my_affiliation",
- "_find_state_under_affiliation",
- "_plugin",
- "_attach_plugin",
- "_authorized",
- "is_authorized",
- "_debug",
- "_session",
- )
- _MY_AFFILIATION: Tuple[str, ...] = ()
-
- def __init__(
- self,
- host: Optional[str] = None,
- port: Optional[int] = None,
- last_state: Optional[Dict] = None,
- state: Optional[Dict] = None,
- my_affiliation: Tuple[str, ...] = None,
- plugin: Optional[BaseStatePlugin] = None,
- ) -> None:
- """The AppState class enables Frontend users to interact with their application state.
-
- When the state isn't defined, it would be pulled from the app REST API Server.
- If the state gets modified by the user, the new state would be sent to the API Server.
-
- Arguments:
- host: Rest API Server current host
- port: Rest API Server current port
- last_state: The state pulled on first access.
- state: The state modified by the user.
- my_affiliation: A tuple describing the affiliation this app state represents. When storing a state dict
- on this AppState, this affiliation will be used to reduce the scope of the given state.
- plugin: A plugin to handle authorization.
-
- """
- self._use_localhost = "LIGHTNING_APP_STATE_URL" not in os.environ
- self._host = host or ("http://127.0.0.1" if self._use_localhost else None)
- self._port = port or (APP_SERVER_PORT if self._use_localhost else None)
- self._last_state = last_state
- self._state = state
- self._session_id = "1234"
- self._my_affiliation = my_affiliation if my_affiliation is not None else AppState._MY_AFFILIATION
- self._authorized = None
- self._attach_plugin(plugin)
- self._session = self._configure_session()
-
- @property
- def _url(self) -> str:
- if self._host is None:
- app_ip = ""
-
- if "LIGHTNING_CLOUD_PROJECT_ID" in os.environ and "LIGHTNING_CLOUD_APP_ID" in os.environ:
- client = LightningClient()
- app_instance = client.lightningapp_instance_service_get_lightningapp_instance(
- os.environ.get("LIGHTNING_CLOUD_PROJECT_ID"),
- os.environ.get("LIGHTNING_CLOUD_APP_ID"),
- )
- app_ip = app_instance.status.ip_address
-
- # TODO: Don't hard code port 8080 here
- self._host = f"http://{app_ip}:8080" if app_ip else APP_SERVER_HOST
- return f"{self._host}:{self._port}" if self._use_localhost else self._host
-
- def _attach_plugin(self, plugin: Optional[BaseStatePlugin]) -> None:
- plugin = plugin if plugin is not None else AppStatePlugin()
- self._plugin = plugin
-
- @staticmethod
- def _find_state_under_affiliation(state, my_affiliation: Tuple[str, ...]) -> Dict[str, Any]:
- """This method is used to extract the subset of the app state associated with the given affiliation.
-
- For example, if the affiliation is ``("root", "subflow")``, then the returned state will be
- ``state["flows"]["subflow"]``.
-
- """
- children_state = state
- for name in my_affiliation:
- if name in children_state["flows"]:
- children_state = children_state["flows"][name]
- elif name in children_state["works"]:
- children_state = children_state["works"][name]
- else:
- raise ValueError(f"Failed to extract the state under the affiliation '{my_affiliation}'.")
- return children_state
-
- def _store_state(self, state: Dict[str, Any]) -> None:
- # Relying on the global variable to ensure the
- # deep_diff is done on the entire state.
- global _LAST_STATE
- global _STATE
- _LAST_STATE = deepcopy(state)
- _STATE = state
- # If the affiliation is passed, the AppState was created in a LightningFlow context.
- # The state should be only the one of this LightningFlow and its children.
- self._last_state = self._find_state_under_affiliation(_LAST_STATE, self._my_affiliation)
- self._state = self._find_state_under_affiliation(_STATE, self._my_affiliation)
-
- def send_delta(self) -> None:
- app_url = f"{self._url}/api/v1/delta"
- deep_diff = DeepDiff(_LAST_STATE, _STATE, verbose_level=2)
- assert self._plugin is not None
- # TODO: Find how to prevent the infinite loop on refresh without storing the DeepDiff
- if self._plugin.should_update_app(deep_diff):
- data = {"delta": json.loads(deep_diff.to_json())}
- headers = headers_for(self._plugin.get_context())
- try:
- # TODO: Send the delta directly to the REST API.
- response = self._session.post(app_url, json=data, headers=headers)
- except ConnectionError as ex:
- raise AttributeError("Failed to connect and send the app state. Is the app running?") from ex
-
- if response and response.status_code != 200:
- raise Exception(f"The response from the server was {response.status_code}. Your inputs were rejected.")
-
- def _request_state(self) -> None:
- if self._state is not None:
- return
- app_url = f"{self._url}/api/v1/state"
- headers = headers_for(self._plugin.get_context()) if self._plugin else {}
-
- response_json = {}
-
- # Sometimes the state URL can return an empty JSON when things are being set-up,
- # so we wait for it to be ready here.
- while response_json == {}:
- sleep(0.5)
- try:
- response = self._session.get(app_url, headers=headers, timeout=1)
- except ConnectionError as ex:
- raise AttributeError("Failed to connect and fetch the app state. Is the app running?") from ex
-
- self._authorized = response.status_code
- if self._authorized != 200:
- return
-
- response_json = response.json()
-
- logger.debug(f"GET STATE {response} {response_json}")
- self._store_state(response_json)
-
- def __getattr__(self, name: str) -> Union[Any, "AppState"]:
- if name in self._APP_PRIVATE_KEYS:
- return object.__getattr__(self, name)
-
- # The state needs to be fetched on access if it doesn't exist.
- self._request_state()
-
- if name in self._state.get("vars", {}):
- value = self._state["vars"][name]
- if isinstance(value, dict):
- return _maybe_create_drive("root." + ".".join(self._my_affiliation), value)
- return value
-
- if name in self._state.get("works", {}):
- return AppState(
- self._host, self._port, last_state=self._last_state["works"][name], state=self._state["works"][name]
- )
-
- if name in self._state.get("flows", {}):
- return AppState(
- self._host,
- self._port,
- last_state=self._last_state["flows"][name],
- state=self._state["flows"][name],
- )
-
- if name in self._state.get("structures", {}):
- return AppState(
- self._host,
- self._port,
- last_state=self._last_state["structures"][name],
- state=self._state["structures"][name],
- )
-
- raise AttributeError(
- f"Failed to access '{name}' through `AppState`. The state provides:"
- f" Variables: {list(self._state['vars'].keys())},"
- f" Components: {list(self._state.get('flows', {}).keys()) + list(self._state.get('works', {}).keys())}",
- )
-
- def __getitem__(self, key: str):
- return self.__getattr__(key)
-
- def __setattr__(self, name: str, value: Any) -> None:
- if name in self._APP_PRIVATE_KEYS:
- object.__setattr__(self, name, value)
- return
-
- # The state needs to be fetched on access if it doesn't exist.
- self._request_state()
-
- # TODO: Find a way to aggregate deltas to avoid making
- # request for each attribute change.
- if name in self._state["vars"]:
- self._state["vars"][name] = value
- self.send_delta()
-
- elif name in self._state["flows"]:
- raise AttributeError("You shouldn't set the flows directly onto the state. Use its attributes instead.")
-
- elif name in self._state["works"]:
- raise AttributeError("You shouldn't set the works directly onto the state. Use its attributes instead.")
-
- else:
- raise AttributeError(
- f"Failed to access '{name}' through `AppState`. The state provides:"
- f" Variables: {list(self._state['vars'].keys())},"
- f" Components: {list(self._state['flows'].keys()) + list(self._state['works'].keys())}",
- )
-
- def __repr__(self) -> str:
- return str(self._state)
-
- def __bool__(self) -> bool:
- return bool(self._state)
-
- def __len__(self) -> int:
- # The state needs to be fetched on access if it doesn't exist.
- self._request_state()
-
- keys = []
- for component in ["flows", "works", "structures"]:
- keys.extend(list(self._state.get(component, {})))
- return len(keys)
-
- def items(self) -> List[Dict[str, Any]]:
- # The state needs to be fetched on access if it doesn't exist.
- self._request_state()
-
- items = []
- for component in ["flows", "works"]:
- state = self._state.get(component, {})
- last_state = self._last_state.get(component, {})
- for name, state_value in state.items():
- v = AppState(
- self._host,
- self._port,
- last_state=last_state[name],
- state=state_value,
- )
- items.append((name, v))
-
- structures = self._state.get("structures", {})
- last_structures = self._last_state.get("structures", {})
- if structures:
- for component in ["flows", "works"]:
- state = structures.get(component, {})
- last_state = last_structures.get(component, {})
- for name, state_value in state.items():
- v = AppState(
- self._host,
- self._port,
- last_state=last_state[name],
- state=state_value,
- )
- items.append((name, v))
- return items
-
- @staticmethod
- def _configure_session() -> Session:
- return _configure_session()
diff --git a/src/lightning/app/utilities/tracer.py b/src/lightning/app/utilities/tracer.py
deleted file mode 100644
index fe44f91947305..0000000000000
--- a/src/lightning/app/utilities/tracer.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import functools
-import inspect
-import runpy
-import sys
-import time
-from pathlib import Path
-from typing import Any, Optional
-
-
-def get_default_args(func):
- signature = inspect.signature(func)
- return {k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty}
-
-
-def wrap_fn(fn, cls, method_name, trace, stack_level=1, pre_fn=None, post_fn=None, is_class_method=None):
- """Wrap a function so that its execution can be traced and its args and return values modified."""
- class_name = cls.__qualname__
-
- @functools.wraps(fn)
- def fn_with_tracing(self, *args: Any, **kwargs: Any):
- if class_name not in trace:
- trace[class_name] = {}
-
- self_id = id(self)
- stack = inspect.stack()
- frame = stack[stack_level]
- frame_id = id(frame)
- stack_len = len(stack) - 1
-
- if self_id not in trace[class_name]:
- trace[class_name][self_id] = {}
-
- if method_name not in trace[class_name][self_id]:
- trace[class_name][self_id][method_name] = {}
-
- if frame_id not in trace[class_name][self_id][method_name]:
- trace[class_name][self_id][method_name][frame_id] = {}
-
- trace_entry = trace[class_name][self_id][method_name][frame_id]
-
- if pre_fn:
- # If a pre_fn is specified, it can both record information
- # in a trace, as well as return modified args and kwargs
- # that will be provided to the actual fn being wrappped
- pre_trace, args, kwargs = pre_fn(self, *args, **kwargs)
- trace_entry["pre"] = pre_trace
-
- # We record the invocation and the calling location in the trace
- trace_entry["frame"] = {
- "filename": frame.filename,
- "lineno": frame.lineno,
- "function": frame.function,
- "depth": stack_len,
- }
-
- # we cache the dfeault parameters used during the function call
- trace_entry["default_args"] = get_default_args(fn)
-
- # we cache also the parameters used during the function call
- trace_entry["call_args"] = kwargs
-
- trace_entry["call"] = {"start": time.time_ns()}
-
- ret = fn(self, *args, **kwargs) if not is_class_method else fn(*args, **kwargs)
-
- trace_entry["call"]["end"] = time.time_ns()
-
- if post_fn:
- # If a post_fn is specified, it can both record information
- # in a trace, as well as modify the value returned from fn
- post_trace, ret = post_fn(self, ret)
- trace_entry["post"] = post_trace
-
- return ret
-
- return fn_with_tracing
-
-
-class Tracer:
- def __init__(self):
- self.methods = []
- self.orig = {}
- self.res = {}
-
- def add_traced(self, cls, method_name, stack_level=1, pre_fn=None, post_fn=None):
- """Record the fact that we will want to trace method_name in class cls.
-
- Optionally provide two functions that will execute prior to and after the method. The functions also have a
- chance to modify the input arguments and the return values of the methods.
-
- """
- self.methods.append((cls, method_name, stack_level, pre_fn, post_fn))
-
- def _instrument(self):
- """Modify classes by wrapping methods that need to be traced.
-
- Initialize the output trace dict.
-
- """
- self.res = {}
- for cls, method, stack_level, pre_fn, post_fn in self.methods:
- fn = getattr(cls, method)
- # this checks if the passed function is a class method
- fn_is_class_method: bool = hasattr(fn, "__self__")
-
- if cls not in self.orig:
- self.orig[cls] = {}
- self.orig[cls][method] = fn
- wrapped_fn = wrap_fn(
- fn,
- cls,
- method,
- self.res,
- stack_level=stack_level,
- pre_fn=pre_fn,
- post_fn=post_fn,
- is_class_method=fn_is_class_method,
- )
-
- # this is needed to wrap class methods
- if fn_is_class_method:
- wrapped_fn = classmethod(wrapped_fn)
-
- setattr(cls, method, wrapped_fn)
-
- def _restore(self):
- """Restore original methods so classes go back to their initial state."""
- for cls in self.orig:
- for method in self.orig[cls]:
- setattr(cls, method, self.orig[cls][method])
-
- def _cleanup(self):
- """Cleanup trace by converting trace[class_name][instance_id][method_name][frame_id] to
- trace[class_name][][method_name][] thereby removing references to instance ids."""
- out = {}
- for class_name in self.res:
- out[class_name] = []
- for self_id in self.res[class_name]:
- instance = self.res[class_name][self_id]
- out_instance = {"id": self_id}
- for method_name, method in instance.items():
- frames = []
- for frame_id, frame in method.items():
- frame["id"] = frame_id
- frames.append(frame)
- out_instance[method_name] = frames
- out[class_name].append(out_instance)
- self.res = out
-
- def trace(self, *args: Any, init_globals=None) -> Optional[dict]:
- """Execute the command-line arguments in args after instrumenting for tracing.
-
- Restore the classes to their initial state after tracing.
-
- """
- args = list(args)
- script = args[0]
- script_dir = Path(script).parent.absolute()
-
- sys_path = sys.path[:]
- sys_argv = sys.argv[:]
-
- sys.path.append(str(script_dir))
-
- sys.argv = args
-
- self._instrument()
-
- res = runpy.run_path(script, run_name="__main__", init_globals=init_globals or globals())
-
- self._restore()
- self._cleanup()
-
- sys.path = sys_path[:]
- sys.argv = sys_argv[:]
-
- res["tracer_res"] = self.res
-
- return res
diff --git a/src/lightning/app/utilities/tree.py b/src/lightning/app/utilities/tree.py
deleted file mode 100644
index b60d8a85dc30b..0000000000000
--- a/src/lightning/app/utilities/tree.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Utilities for traversing the tree of components in an app."""
-
-from typing import TYPE_CHECKING, Type
-
-import lightning.app
-
-if TYPE_CHECKING:
- from lightning.app.utilities.types import Component, ComponentTuple
-
-
-def breadth_first(root: "Component", types: Type["ComponentTuple"] = None):
- """Returns a generator that walks through the tree of components breadth-first.
-
- Arguments:
- root: The root component of the tree
- types: If provided, only the component types in this list will be visited.
-
- """
- yield from _BreadthFirstVisitor(root, types)
-
-
-class _BreadthFirstVisitor:
- def __init__(self, root: "Component", types: Type["ComponentTuple"] = None) -> None:
- self.queue = [root]
- self.types = types
-
- def __iter__(self):
- return self
-
- def __next__(self):
- from lightning.app.structures import Dict
-
- while self.queue:
- component = self.queue.pop(0)
-
- if isinstance(component, lightning.app.LightningFlow):
- components = [getattr(component, el) for el in sorted(component._flows)]
- for struct_name in sorted(component._structures):
- structure = getattr(component, struct_name)
- if isinstance(structure, Dict):
- values = sorted(structure.items(), key=lambda x: x[0])
- else:
- values = sorted(((v.name, v) for v in structure), key=lambda x: x[0])
- for _, value in values:
- if isinstance(value, lightning.app.LightningFlow):
- components.append(value)
- self.queue += components
- self.queue += component.works(recurse=False)
-
- if any(isinstance(component, t) for t in self.types):
- return component
-
- raise StopIteration
-
-
-class _DepthFirstVisitor:
- def __init__(self, root: "Component", types: Type["ComponentTuple"] = None) -> None:
- self.stack = [root]
- self.types = types
-
- def __iter__(self):
- return self
-
- def __next__(self):
- while self.stack:
- component = self.stack.pop()
-
- if isinstance(component, lightning.app.LightningFlow):
- self.stack += list(component.flows.values())
- self.stack += component.works(recurse=False)
-
- if any(isinstance(component, t) for t in self.types):
- return component
-
- raise StopIteration
diff --git a/src/lightning/app/utilities/types.py b/src/lightning/app/utilities/types.py
deleted file mode 100644
index d14a025e179b3..0000000000000
--- a/src/lightning/app/utilities/types.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import typing as t
-from typing import Protocol, runtime_checkable
-
-from lightning.app.core import LightningFlow, LightningWork
-from lightning.app.structures import Dict, List
-
-Component = t.Union[LightningFlow, LightningWork, Dict, List]
-ComponentTuple = (LightningFlow, LightningWork, Dict, List)
-
-
-@runtime_checkable
-class Hashable(Protocol):
- def to_dict(self) -> t.Dict[str, t.Any]:
- """Convert to dictionaty."""
diff --git a/src/lightning/app/utilities/warnings.py b/src/lightning/app/utilities/warnings.py
deleted file mode 100644
index f0787103ea54d..0000000000000
--- a/src/lightning/app/utilities/warnings.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-class LightningFlowWarning(UserWarning):
- """Warning used to inform users of misuse with Lightning Flow."""
diff --git a/src/lightning/fabric/cli.py b/src/lightning/fabric/cli.py
index 0af94fb3ac922..5ca46ba331622 100644
--- a/src/lightning/fabric/cli.py
+++ b/src/lightning/fabric/cli.py
@@ -57,18 +57,12 @@ def _legacy_main() -> None:
"""
hparams = sys.argv[1:]
- if len(hparams) >= 2 and hparams[0] == "run":
- if hparams[1] == "model":
- print(
- "`lightning run model` is deprecated and will be removed in future versions."
- " Please call `fabric run` instead."
- )
- _main()
- return
-
- from lightning.app.cli.lightning_cli import main as main_cli
-
- main_cli()
+ if len(hparams) >= 2 and hparams[0] == "run" and hparams[1] == "model":
+ print(
+ "`lightning run model` is deprecated and will be removed in future versions."
+ " Please call `fabric run` instead."
+ )
+ _main()
return
if _LIGHTNING_SDK_AVAILABLE:
diff --git a/src/lightning/store/README.md b/src/lightning/store/README.md
deleted file mode 100644
index 0f2fdf61f76de..0000000000000
--- a/src/lightning/store/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-## Getting Started
-
-- Login to lightning.ai (_optional_) \<-- takes less than a minute. ⏩
-- Store your models on the cloud \<-- simple call: `upload_model(...)`. 🗳️
-- Share it with your friends \<-- just share the "username/model_name" (and version if required) format. :handshake:
-- They download using a simple call: `download_model("username/model_name", version="your_version")`. :wink:
-- Lightning :zap: fast, isn't it?. :heart:
-
-## Usage
-
-**Storing to the cloud**
-
-```python
-import lightning as L
-
-# Upload a checkpoint:
-L.store.upload_model("mnist_model", "mnist_model.ckpt")
-
-# Optionally provide a version:
-L.store.upload_model("mnist_model", "mnist_model.ckpt", version="1.0.0")
-```
-
-**List your models**
-
-```python
-import lightning as L
-
-models = L.store.list_models()
-
-print([model.name for model in models])
-# ['username/mnist_model']
-```
-
-**Downloading from the cloud**
-
-```python
-import lightning as L
-
-# Download a checkpoint
-L.store.download_model("username/mnist_model", "any_path.ckpt")
-```
diff --git a/src/lightning/store/__init__.py b/src/lightning/store/__init__.py
deleted file mode 100644
index 951bd7262e698..0000000000000
--- a/src/lightning/store/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from lightning.store.store import download_model, list_models, upload_model
-
-__all__ = ["download_model", "upload_model", "list_models"]
diff --git a/src/lightning/store/store.py b/src/lightning/store/store.py
deleted file mode 100644
index f2389f8b8aa63..0000000000000
--- a/src/lightning/store/store.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import os
-from typing import List
-
-from lightning_cloud.openapi import V1Model, V1UploadModelRequest
-
-from lightning.app.utilities.cloud import _get_project
-from lightning.store.utils import _Client, _download_file_from_url, _upload_file_to_url
-
-
-def upload_model(
- name: str,
- path: str,
- version: str = "latest",
- progress_bar: bool = True,
-) -> None:
- """Upload a model to the lightning cloud.
-
- Args:
- name:
- The model name.
- path:
- The path to the checkpoint to be uploaded.
- version:
- The version of the model to be uploaded. If not provided, default will be latest (not overridden).
- progress_bar:
- A progress bar to show the uploading status. Disable this if not needed, by setting to `False`.
-
- """
- client = _Client()
- user = client.auth_service_get_user()
- # TODO: Allow passing this
- project_id = _get_project(client).project_id
-
- # TODO: Post model parts if the file size is over threshold
- body = V1UploadModelRequest(
- name=f"{user.username}/{name}",
- version=version,
- project_id=project_id,
- )
- model = client.models_store_upload_model(body)
-
- _upload_file_to_url(model.upload_url, path, progress_bar=progress_bar)
-
-
-def download_model(
- name: str,
- path: str,
- version: str = "latest",
- progress_bar: bool = True,
-) -> None:
- """Download a model from the lightning cloud.
-
- Args:
- name:
- The unique name of the model to be downloaded. Format: `/`.
- path:
- The path to download the model to.
- version:
- The version of the model to be uploaded. If not provided, default will be latest (not overridden).
- progress_bar:
- Show progress on download.
-
- """
- client = _Client()
- download_url = client.models_store_download_model(name=name, version=version).download_url
- _download_file_from_url(download_url, os.path.abspath(path), progress_bar=progress_bar)
-
-
-def list_models() -> List[V1Model]:
- """List your models in the lightning cloud.
-
- Returns:
- A list of model objects.
-
- """
- client = _Client()
- # TODO: Allow passing this
- project_id = _get_project(client).project_id
- return client.models_store_list_models(project_id=project_id).models
diff --git a/src/lightning/store/utils.py b/src/lightning/store/utils.py
deleted file mode 100644
index f3b8b9b7ae883..0000000000000
--- a/src/lightning/store/utils.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import os
-
-import requests
-from lightning_cloud.openapi import AuthServiceApi, ModelsStoreApi, ProjectsServiceApi
-from lightning_cloud.rest_client import create_swagger_client
-from tqdm import tqdm
-from tqdm.utils import CallbackIOWrapper
-
-
-def _upload_file_to_url(url: str, path: str, progress_bar: bool) -> None:
- if progress_bar:
- file_size = os.path.getsize(path)
- with open(path, "rb") as fd, tqdm(
- desc="Uploading",
- total=file_size,
- unit="B",
- unit_scale=True,
- unit_divisor=1000,
- ) as t:
- reader_wrapper = CallbackIOWrapper(t.update, fd, "read")
- response = requests.put(url, data=reader_wrapper)
- response.raise_for_status()
- else:
- with open(path, "rb") as fo:
- requests.put(url, data=fo)
-
-
-def _download_file_from_url(url: str, path: str, progress_bar: bool) -> None:
- with requests.get(url, stream=True) as req_stream:
- total_size_in_bytes = int(req_stream.headers.get("content-length", 0))
- block_size = 1000 * 1000 # 1 MB
-
- download_progress_bar = None
- if progress_bar:
- download_progress_bar = tqdm(
- desc="Downloading",
- total=total_size_in_bytes,
- unit="B",
- unit_scale=True,
- unit_divisor=1000,
- )
- with open(path, "wb") as f:
- for chunk in req_stream.iter_content(chunk_size=block_size):
- if download_progress_bar:
- download_progress_bar.update(len(chunk))
- f.write(chunk)
- if download_progress_bar:
- download_progress_bar.close()
-
-
-class _Client(AuthServiceApi, ModelsStoreApi, ProjectsServiceApi):
- def __init__(self):
- api_client = create_swagger_client()
- super().__init__(api_client)
diff --git a/src/lightning_app/MANIFEST.in b/src/lightning_app/MANIFEST.in
deleted file mode 100644
index a8e251508baf5..0000000000000
--- a/src/lightning_app/MANIFEST.in
+++ /dev/null
@@ -1,11 +0,0 @@
-include src/version.info
-include src/lightning_app/version.info
-include src/lightning_app/CHANGELOG.md
-include src/lightning_app/README.md
-recursive-include requirements/app *.txt
-include .actions/assistant.py
-recursive-include src/lightning_app/cli/*-template *
-# TODO: remove this once lightning-ui package is ready as a dependency
-recursive-include src/lightning_app/ui *
-include src/lightning_app/components/serve/catimage.png
-include src/lightning_app/py.typed # marker file for PEP 561
diff --git a/src/lightning_app/README.md b/src/lightning_app/README.md
deleted file mode 100644
index bf28077071b36..0000000000000
--- a/src/lightning_app/README.md
+++ /dev/null
@@ -1,146 +0,0 @@
-
-
-
-
-**With Lightning Apps, you build exactly what you need: from production-ready, multi-cloud ML systems to simple research demos.**
-
-______________________________________________________________________
-
-
- Website •
- Docs •
- Getting started •
- Help •
- Slack
-
-
-[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lightning_app)](https://pypi.org/project/lightning_app/)
-[![PyPI Status](https://badge.fury.io/py/lightning_app.svg)](https://badge.fury.io/py/lightning_app)
-[![PyPI - Downloads](https://img.shields.io/pypi/dm/lightning-app)](https://pepy.tech/project/lightning-app)
-[![Conda](https://img.shields.io/conda/v/conda-forge/lightning_app?label=conda&color=success)](https://anaconda.org/conda-forge/lightning_app)
-
-![readme-gif](https://pl-bolts-doc-images.s3.us-east-2.amazonaws.com/lightning-gif-888777nslpiijdbcvctyvwhe.gif)
-
-
-
-## From production-ready, multi-cloud ML systems to simple research demos.
-
-Lightning Apps enable researchers, data scientists, and software engineers to build, share and iterate on highly scalable, complex AI workflows using the tools and technologies of their choice without any of the cloud boilerplate.
-
-With Lightning Apps, your favorite components can work together on any machine at any scale.
-
-# Getting started
-
-## Install Lightning
-
-
-
-Prerequisites
-
-> TIP: We strongly recommend creating a virtual environment first.
-> Don’t know what this is? Follow our [beginner guide here](https://lightning.ai/docs/stable/install/installation.html).
-
-- Python 3.8.x or later (3.8.x, 3.9.x, 3.10.x, ...)
-- Git
-- Set up an alias for python=python3
-- Add the root folder of Lightning to the Environment Variables to PATH
-- (quick-start app requirement) Install Z shell (zsh)
-
-
-
-```bash
-pip install -U lightning
-```
-
-## Run your first Lightning App
-
-1. Install a simple training and deployment app by typing:
-
-```bash
-lightning install app lightning/quick-start
-```
-
-2. If everything was successful, move into the new directory:
-
-```bash
-cd lightning-quick-start
-```
-
-3. Run the app locally
-
-```bash
-lightning run app app.py
-```
-
-4. Alternatively, run it on the public Lightning Cloud to share your app!
-
-```bash
-lightning run app app.py --cloud
-```
-
-[Read this guide](https://lightning.ai/docs/stable/levels/basic/) to learn the basics of Lightning Apps in 15 minutes.
-
-# Features
-
-Lightning Apps consist of a root [LightningFlow](https://lightning.ai/docs/stable/glossary/app_tree.html) component, that optionally contains a tree of 2 types of components: [LightningFlow](https://lightning.ai/lightning-docs/core_api/lightning_flow.html) 🌊 and [LightningWork](https://lightning.ai/lightning-docs/core_api/lightning_work/) ⚒️. Key functionality includes:
-
-- A shared state between components.
-- A constantly running event loop for reactivity.
-- Dynamic attachment of components at runtime.
-- Start and stop functionality of your works.
-
-Lightning Apps can run [locally](https://lightning.ai/lightning-docs/workflows/run_on_private_cloud.html) 💻 or [on the cloud](https://lightning.ai/lightning-docs/core_api/lightning_work/compute.html) 🌩️.
-
-Easy communication 🛰️ between components is supported with:
-
-- [Directional state updates](https://lightning.ai/lightning-docs/core_api/lightning_app/communication.html?highlight=directional%20state) from the Works to the Flow creating an event: When creating interactive apps, you will likely want your components to share information with each other. You might to rely on that information to control their execution, share progress in the UI, trigger a sequence of operations, or more.
-- [Storage](https://lightning.ai/lightning-docs/api_reference/storage.html): The Lightning Storage system makes it easy to share files between LightningWork so you can run your app both locally and in the cloud without changing the code.
- - [Path](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.storage.path.Path.html#lightning_app.storage.path.Path): The Path object is a reference to a specific file or directory from a LightningWork and can be used to transfer those files to another LightningWork (one way, from source to destination).
- - [Payload](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.storage.payload.Payload.html#lightning_app.storage.payload.Payload): The Payload object enables transferring of Python objects from one work to another in a similar fashion as Path.
- - [Drive](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.storage.drive.Drive.html#lightning_app.storage.drive.Drive): The Drive object provides a central place for your components to share data. The drive acts as an isolated folder and any component can access it by knowing its name.
-
-Lightning Apps have built-in support for [adding UIs](https://lightning.ai/lightning-docs/workflows/add_web_ui/) 🎨:
-
-- [StaticWebFrontEnd](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.frontend.web.StaticWebFrontend.html#lightning_app.frontend.web.StaticWebFrontend): A frontend that serves static files from a directory using FastAPI.
-- [StreamlitFrontend](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.frontend.stream_lit.StreamlitFrontend.html#lightning_app.frontend.stream_lit.StreamlitFrontend): A frontend for wrapping Streamlit code in your LightingFlow.
-- [ServeGradio](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.components.serve.gradio_server.ServeGradio.html#servegradio): This class enables you to quickly create a `gradio` based UI for your Lightning App.
-
-[Scheduling](https://lightning.ai/lightning-docs/glossary/scheduling.html) ⏲️: The Lightning Scheduling system makes it easy to schedule your components execution with any arbitrary conditions.
-
-Advanced users who need full control over the environment a LightningWork runs in can [specify a custom Docker image](https://lightning.ai/lightning-docs/glossary/build_config/build_config_advanced.html?highlight=docker) 🐋 that will be deployed in the cloud.
-
-[Environment variables](https://lightning.ai/lightning-docs/glossary/environment_variables.html?highlight=environment%20variables) 💬: If your app is using secrets or values, such as API keys or access tokens, use environment variables to avoid sticking them in the source code.
-
-Ready to use [built-in components](https://lightning.ai/lightning-docs/api_reference/components.html?highlight=built%20components) 🧱:
-
-- [PopenPythonScript](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.components.python.popen.PopenPythonScript.html#lightning_app.components.python.popen.PopenPythonScript): This class enables you to easily run a Python Script.
-- [ModelInferenceAPI](https://lightning.ai/docs/app/stable/api_reference/generated/lightning.app.components.serve.serve.ModelInferenceAPI.html#lightning_app.components.serve.serve.ModelInferenceAPI): This class enables you to easily get your model served.
-
-# App gallery
-
-The [Lightning AI website](https://lightning.ai/) features a curated gallery of Lightning Apps and components that makes it easy to get started. A few highlights:
-
-| App | Description |
-| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| Train & Demo PyTorch Lightning | Train a model using PyTorch Lightning and deploy it to an interactive demo. Use this Lightning App as a starting point for building more complex apps around your models. |
-| Lightning Sweeper | Run a hyperparameter sweep over any model script across hundreds of cloud machines at once. This Lightning App uses Optuna to provide advanced tuning algorithms (from grid and random search to Hyperband). |
-| Flashy | Flashy, the auto-AI Lightning App, selects the best deep learning model for your image or text datasets. It automatically uses state-of-the-art models from Torchision, TIMM and Hugging Face. |
-
-## Current limitations
-
-- Lightning requires Python 3.8.x or later (3.8.x, 3.9.x, 3.10.x).
-- For now, you can only run a single app locally at a time.
-- You are required to install the Lightning App requirements locally, even when starting the app on the cloud.
-- Multiple works cannot share the same machine.
-- To run on the cloud, you will need access to a browser.
-- Frontends only support the HTTP protocol. TCP support is coming in the future.
-- App Flow Frontends cannot be changed after startup, but you the layout can be updated reactively.
-- Authentication is not supported.
-
-## Asking for help
-
-If you have any questions please:
-
-1. [Read the docs](https://lightning.ai/lightning-docs/).
-1. [Search through existing Discussions](https://github.com/Lightning-ai/lightning/discussions), or [add a new question](https://github.com/Lightning-ai/lightning/discussions/new)
-1. [Join our Discord community ](https://discord.gg/VptPCZkGNa).
diff --git a/src/lightning_app/__about__.py b/src/lightning_app/__about__.py
deleted file mode 100644
index 3ffbe2420a905..0000000000000
--- a/src/lightning_app/__about__.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python
-# Copyright The Lightning AI team.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import time
-
-# __version__ = "0.5.1"
-__author__ = "Lightning-AI et al."
-__author_email__ = "name@pytorchlightning.ai"
-__license__ = "Apache-2.0"
-__copyright__ = f"Copyright (c) 2021-{time.strftime('%Y')}, {__author__}."
-__homepage__ = "https://github.com/Lightning-AI/lightning"
-__docs__ = (
- "Use Lightning Apps to build everything from production-ready, multi-cloud ML systems to simple research demos."
-)
-
-__all__ = [
- "__author__",
- "__author_email__",
- "__copyright__",
- "__docs__",
- "__homepage__",
- "__license__",
-]
diff --git a/src/lightning_app/__main__.py b/src/lightning_app/__main__.py
deleted file mode 100644
index 57b27ab968c82..0000000000000
--- a/src/lightning_app/__main__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from lightning.app.cli.lightning_cli import main
-
-if __name__ == "__main__":
- main()
diff --git a/src/lightning_app/__setup__.py b/src/lightning_app/__setup__.py
deleted file mode 100644
index 080eb6c3a6399..0000000000000
--- a/src/lightning_app/__setup__.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import glob
-import os
-from importlib.util import module_from_spec, spec_from_file_location
-from pathlib import Path
-from types import ModuleType
-from typing import Any, Dict
-
-from setuptools import find_packages
-
-_PROJECT_ROOT = "."
-_SOURCE_ROOT = os.path.join(_PROJECT_ROOT, "src")
-_PACKAGE_ROOT = os.path.join(_SOURCE_ROOT, "lightning_app")
-_PATH_REQUIREMENTS = os.path.join("requirements", "app")
-_FREEZE_REQUIREMENTS = os.environ.get("FREEZE_REQUIREMENTS", "0").lower() in ("1", "true")
-
-
-def _load_py_module(name: str, location: str) -> ModuleType:
- spec = spec_from_file_location(name, location)
- assert spec, f"Failed to load module {name} from {location}"
- py = module_from_spec(spec)
- assert spec.loader, f"ModuleSpec.loader is None for {name} from {location}"
- spec.loader.exec_module(py)
- return py
-
-
-def _load_assistant() -> ModuleType:
- location = os.path.join(_PROJECT_ROOT, ".actions", "assistant.py")
- return _load_py_module("assistant", location)
-
-
-def _prepare_extras() -> Dict[str, Any]:
- assistant = _load_assistant()
- # https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras
- # Define package extras. These are only installed if you specify them.
- # From remote, use like `pip install "pytorch-lightning[dev, docs]"`
- # From local copy of repo, use like `PACKAGE_NAME=app pip install ".[dev, docs]"`
- req_files = [Path(p) for p in glob.glob(os.path.join(_PATH_REQUIREMENTS, "*.txt"))]
- common_args = {"path_dir": _PATH_REQUIREMENTS, "unfreeze": "none" if _FREEZE_REQUIREMENTS else "major"}
- extras = {
- p.stem: assistant.load_requirements(file_name=p.name, **common_args)
- for p in req_files
- if p.name not in ("docs.txt", "app.txt")
- }
- extras["extra"] = extras["cloud"] + extras["ui"] + extras["components"]
- extras["all"] = extras["extra"]
- extras["dev"] = extras["all"] + extras["test"] # + extras['docs']
- return extras
-
-
-def _setup_args() -> Dict[str, Any]:
- assistant = _load_assistant()
- about = _load_py_module("about", os.path.join(_PACKAGE_ROOT, "__about__.py"))
- version = _load_py_module("version", os.path.join(_PACKAGE_ROOT, "__version__.py"))
- long_description = assistant.load_readme_description(
- _PACKAGE_ROOT, homepage=about.__homepage__, version=version.version
- )
-
- # TODO: remove this once lightning-ui package is ready as a dependency
- ui_ver_file = os.path.join(_SOURCE_ROOT, "app-ui-version.info")
- if os.path.isfile(ui_ver_file):
- with open(ui_ver_file, encoding="utf-8") as fo:
- ui_version = fo.readlines()[0].strip()
- download_fe_version = {"version": ui_version}
- else:
- print(f"Missing file with FE version: {ui_ver_file}")
- download_fe_version = {}
- assistant._download_frontend(_PACKAGE_ROOT, **download_fe_version)
-
- return {
- "name": "lightning-app",
- "version": version.version,
- "description": about.__docs__,
- "author": about.__author__,
- "author_email": about.__author_email__,
- "url": about.__homepage__,
- "download_url": "https://github.com/Lightning-AI/lightning",
- "license": about.__license__,
- "packages": find_packages(where="src", include=["lightning_app", "lightning_app.*"]),
- "package_dir": {"": "src"},
- "long_description": long_description,
- "long_description_content_type": "text/markdown",
- "include_package_data": True,
- "zip_safe": False,
- "keywords": ["deep learning", "pytorch", "AI"],
- "python_requires": ">=3.8",
- "entry_points": {
- "console_scripts": [
- "lightning_app = lightning_app.cli.lightning_cli:main",
- ],
- },
- "setup_requires": [],
- "install_requires": assistant.load_requirements(
- _PATH_REQUIREMENTS, file_name="app.txt", unfreeze="none" if _FREEZE_REQUIREMENTS else "major"
- ),
- "extras_require": _prepare_extras(),
- "project_urls": {
- "Bug Tracker": "https://github.com/Lightning-AI/lightning/issues",
- "Documentation": "https://lightning.ai/lightning-docs",
- "Source Code": "https://github.com/Lightning-AI/lightning",
- },
- "classifiers": [
- "Environment :: Console",
- "Natural Language :: English",
- # How mature is this project? Common values are
- # 3 - Alpha, 4 - Beta, 5 - Production/Stable
- "Development Status :: 4 - Beta",
- # Indicate who your project is intended for
- "Intended Audience :: Developers",
- "Topic :: Scientific/Engineering :: Artificial Intelligence",
- "Topic :: Scientific/Engineering :: Information Analysis",
- # Pick your license as you wish
- # 'License :: OSI Approved :: BSD License',
- "Operating System :: OS Independent",
- # Specify the Python versions you support here. In particular, ensure
- # that you indicate whether you support Python 2, Python 3 or both.
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- ],
- }
diff --git a/src/lightning_app/__version__.py b/src/lightning_app/__version__.py
deleted file mode 100644
index 1491508baf4b3..0000000000000
--- a/src/lightning_app/__version__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import os
-
-_PACKAGE_ROOT = os.path.dirname(__file__)
-_VERSION_PATH = os.path.join(os.path.dirname(_PACKAGE_ROOT), "version.info")
-if not os.path.exists(_VERSION_PATH):
- # relevant for `bdist_wheel`
- _VERSION_PATH = os.path.join(_PACKAGE_ROOT, "version.info")
-with open(_VERSION_PATH, encoding="utf-8") as fo:
- version = fo.readlines()[0].strip()
diff --git a/src/lightning_app/py.typed b/src/lightning_app/py.typed
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/src/lightning_app/shell-folder_code-lives-lightning.info b/src/lightning_app/shell-folder_code-lives-lightning.info
deleted file mode 100644
index 16ef1072850f2..0000000000000
--- a/src/lightning_app/shell-folder_code-lives-lightning.info
+++ /dev/null
@@ -1,2 +0,0 @@
-This folder serves only for building the `lightning-app` package when you install from source code with env variable `PACKAGE_NAME=app`
-Please do not edit these files - you may see some as they are automatically generated/moved from their current location.
diff --git a/tests/integrations_app/__init__.py b/tests/integrations_app/__init__.py
deleted file mode 100644
index a3c9eb29e7220..0000000000000
--- a/tests/integrations_app/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from os.path import dirname
-
-_PATH_TESTS_DIR = dirname(dirname(__file__))
diff --git a/tests/integrations_app/apps/collect_failures/__init__.py b/tests/integrations_app/apps/collect_failures/__init__.py
deleted file mode 100644
index b022c62adcdf3..0000000000000
--- a/tests/integrations_app/apps/collect_failures/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# TODO: check the tests to be able to run without this init file
diff --git a/tests/integrations_app/apps/collect_failures/app.py b/tests/integrations_app/apps/collect_failures/app.py
deleted file mode 100644
index 068a541aafbe0..0000000000000
--- a/tests/integrations_app/apps/collect_failures/app.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import logging
-import sys
-import time
-
-from lightning.app import LightningApp, LightningFlow, LightningWork
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.INFO)
-logger.addHandler(logging.StreamHandler(sys.stdout))
-
-
-class SimpleWork(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False, parallel=True, raise_exception=False)
- self.is_running_now = False
-
- def run(self):
- self.is_running_now = True
- print("work_is_running")
- for i in range(1, 10):
- time.sleep(1)
- if i % 5 == 0:
- raise Exception(f"invalid_value_of_i_{i}")
- print(f"good_value_of_i_{i}")
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.simple_work = SimpleWork()
-
- def run(self):
- print("useless_garbage_log_that_is_always_there_to_overload_logs")
- self.simple_work.run()
- if not self.simple_work.is_running_now:
- pass
- # work is not ready yet
- print("waiting_for_work_to_be_ready")
- else:
- print("flow_and_work_are_running")
- logger.info("logger_flow_work")
- time.sleep(0.1)
-
-
-if __name__ == "__main__":
- app = LightningApp(RootFlow(), log_level="debug")
diff --git a/tests/integrations_app/apps/collect_failures/requirements.txt b/tests/integrations_app/apps/collect_failures/requirements.txt
deleted file mode 100644
index 7800f0fad3fff..0000000000000
--- a/tests/integrations_app/apps/collect_failures/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-redis
diff --git a/tests/integrations_app/apps/core_features_app/__init__.py b/tests/integrations_app/apps/core_features_app/__init__.py
deleted file mode 100644
index b022c62adcdf3..0000000000000
--- a/tests/integrations_app/apps/core_features_app/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# TODO: check the tests to be able to run without this init file
diff --git a/tests/integrations_app/apps/core_features_app/app.py b/tests/integrations_app/apps/core_features_app/app.py
deleted file mode 100644
index db73ae21c0572..0000000000000
--- a/tests/integrations_app/apps/core_features_app/app.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import os
-
-from lightning.app.core import LightningApp, LightningFlow
-
-
-class EnvVarTestApp(LightningFlow):
- def __init__(self):
- super().__init__()
-
- def run(self):
- # these env vars are set here: tests/integrations_app/test_core_features_app.py:15
- assert os.getenv("FOO", "") == "bar"
- assert os.getenv("BLA", "") == "bloz"
- self.stop()
-
-
-app = LightningApp(EnvVarTestApp())
diff --git a/tests/integrations_app/apps/custom_work_dependencies/__init__.py b/tests/integrations_app/apps/custom_work_dependencies/__init__.py
deleted file mode 100644
index b022c62adcdf3..0000000000000
--- a/tests/integrations_app/apps/custom_work_dependencies/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# TODO: check the tests to be able to run without this init file
diff --git a/tests/integrations_app/apps/custom_work_dependencies/app.py b/tests/integrations_app/apps/custom_work_dependencies/app.py
deleted file mode 100644
index 27f2d3f3ca876..0000000000000
--- a/tests/integrations_app/apps/custom_work_dependencies/app.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import os
-
-from lightning.app import BuildConfig, CloudCompute, LightningApp, LightningFlow, LightningWork
-
-
-class CustomBuildConfig(BuildConfig):
- def build_commands(self):
- return ["sudo apt update", "sudo apt install redis", "pip install lmdb"]
-
-
-class WorkWithCustomDeps(LightningWork):
- def __init__(self, cloud_compute: CloudCompute = CloudCompute(), **kwargs):
- build_config = CustomBuildConfig(requirements=["py"])
- super().__init__(parallel=True, **kwargs, cloud_compute=cloud_compute, cloud_build_config=build_config)
-
- def run(self):
- # installed by the build commands and by requirements in the build config
- import lmdb
-
- print("installed lmdb version:", lmdb.__version__)
-
-
-class WorkWithCustomBaseImage(LightningWork):
- def __init__(self, cloud_compute: CloudCompute = CloudCompute(), **kwargs):
- # this image has been created from ghcr.io/gridai/base-images:v1.8-cpu
- # by just adding an empty file at /content/.e2e_test
- image_tag = os.getenv("LIGHTNING_E2E_TEST_IMAGE_VERSION", "v1.29")
- custom_image = f"ghcr.io/gridai/image-for-testing-custom-images-in-e2e:{image_tag}"
- build_config = BuildConfig(image=custom_image)
- super().__init__(parallel=True, **kwargs, cloud_compute=cloud_compute, cloud_build_config=build_config)
-
- def run(self):
- # checking the existence of the file - this file had been added to the custom base image
- assert ".e2e_test" in os.listdir("/testdir/"), "file not found"
-
-
-class CustomWorkBuildConfigChecker(LightningFlow):
- def run(self):
- # create dynamically the work at runtime
- if not hasattr(self, "work1"):
- self.work1 = WorkWithCustomDeps()
- if not hasattr(self, "work2"):
- self.work2 = WorkWithCustomBaseImage()
-
- self.work1.run()
- self.work2.run()
-
- if self.work1.has_succeeded and self.work2.has_succeeded:
- print("--- Custom Work Dependency checker End ----")
- self.stop()
-
-
-app = LightningApp(CustomWorkBuildConfigChecker())
diff --git a/tests/integrations_app/apps/idle_timeout/__init__.py b/tests/integrations_app/apps/idle_timeout/__init__.py
deleted file mode 100644
index b022c62adcdf3..0000000000000
--- a/tests/integrations_app/apps/idle_timeout/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# TODO: check the tests to be able to run without this init file
diff --git a/tests/integrations_app/apps/idle_timeout/app.py b/tests/integrations_app/apps/idle_timeout/app.py
deleted file mode 100644
index d33df0a616d58..0000000000000
--- a/tests/integrations_app/apps/idle_timeout/app.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import pathlib
-
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork
-from lightning.app.storage.path import _artifacts_path, _filesystem
-from lightning.app.utilities.enum import WorkStageStatus
-
-
-class SourceFileWriterWork(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False, parallel=True, cloud_compute=CloudCompute(idle_timeout=5))
- self.counter = 0
- self.value = None
- self.path = None
-
- def run(self):
- self.path = "lit://boring_file.txt"
- with open(self.path, "w") as f:
- f.write("path")
- self.counter += 1
-
-
-class DestinationWork(LightningWork):
- def run(self, path):
- assert path.exists()
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.make_check = True
- self.work = SourceFileWriterWork()
- self.dest_work = DestinationWork(parallel=True)
-
- def run(self):
- if self.work.counter == 0:
- self.work.run()
-
- elif self.work.status.stage == WorkStageStatus.STOPPED and self.make_check:
- succeeded_statuses = [status for status in self.work.statuses if status.stage == WorkStageStatus.SUCCEEDED]
- # Ensure the work succeeded at some point
- assert len(succeeded_statuses) > 0
- succeeded_status = succeeded_statuses[-1]
-
- stopped_statuses = [status for status in self.work.statuses if status.stage == WorkStageStatus.STOPPED]
-
- # We want to check that the work started shutting down withing the required timeframe, so we take the first
- # status that has `stage == STOPPED`.
- stopped_status = stopped_statuses[0]
-
- # Note: Account for the controlplane, k8s, SIGTERM handler delays.
- assert (stopped_status.timestamp - succeeded_status.timestamp) < 20
-
- fs = _filesystem()
- destination_path = _artifacts_path(self.work) / pathlib.Path(*self.work.path.resolve().parts[1:])
- assert fs.exists(destination_path)
- self.dest_work.run(self.work.path)
- self.make_check = False
- print("Successfully stopped SourceFileWriterWork.")
-
- if self.dest_work.status.stage == WorkStageStatus.SUCCEEDED:
- print("Stopping work")
- self.dest_work.stop()
-
- if self.dest_work.status.stage == WorkStageStatus.STOPPED:
- print(self.dest_work.statuses)
- print("Application End")
- self.stop()
-
-
-app = LightningApp(RootFlow(), log_level="debug")
diff --git a/tests/integrations_app/conftest.py b/tests/integrations_app/conftest.py
deleted file mode 100644
index dec2f12c7ffe4..0000000000000
--- a/tests/integrations_app/conftest.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import contextlib
-import os
-import shutil
-import threading
-from subprocess import Popen
-
-import psutil
-import pytest
-from lightning.app.storage.path import _storage_root_dir
-from lightning.app.utilities.component import _set_context
-from lightning.app.utilities.packaging import cloud_compute
-from lightning.app.utilities.packaging.app_config import _APP_CONFIG_FILENAME
-from lightning.app.utilities.state import AppState
-
-from integrations_app.public import _PATH_EXAMPLES
-
-GITHUB_APP_URLS = {
- "template_react_ui": "https://github.com/Lightning-AI/lightning-template-react.git",
-}
-
-os.environ["LIGHTNING_DISPATCHED"] = "1"
-
-
-def pytest_sessionstart(*_):
- """Pytest hook that get called after the Session object has been created and before performing collection and
- entering the run test loop."""
- for name, url in GITHUB_APP_URLS.items():
- app_path = _PATH_EXAMPLES / name
- if not os.path.exists(app_path):
- Popen(["git", "clone", url, name], cwd=_PATH_EXAMPLES).wait(timeout=90)
- else:
- Popen(["git", "pull", "main"], cwd=app_path).wait(timeout=90)
-
-
-def pytest_sessionfinish(session, exitstatus):
- """Pytest hook that get called after whole test run finished, right before returning the exit status to the
- system."""
- # kill all the processes and threads created by parent
- # TODO this isn't great. We should have each tests doing it's own cleanup
- current_process = psutil.Process()
- for child in current_process.children(recursive=True):
- with contextlib.suppress(psutil.NoSuchProcess):
- params = child.as_dict() or {}
- cmd_lines = params.get("cmdline", [])
- # we shouldn't kill the resource tracker from multiprocessing. If we do,
- # `atexit` will throw as it uses resource tracker to try to clean up
- if cmd_lines and "resource_tracker" in cmd_lines[-1]:
- continue
- child.kill()
-
- main_thread = threading.current_thread()
- for t in threading.enumerate():
- if t is not main_thread:
- t.join(0)
-
-
-@pytest.fixture(autouse=True)
-def cleanup():
- from lightning.app.utilities.app_helpers import _LightningAppRef
-
- yield
- _LightningAppRef._app_instance = None
- shutil.rmtree("./storage", ignore_errors=True)
- shutil.rmtree(_storage_root_dir(), ignore_errors=True)
- shutil.rmtree("./.shared", ignore_errors=True)
- if os.path.isfile(_APP_CONFIG_FILENAME):
- os.remove(_APP_CONFIG_FILENAME)
- _set_context(None)
-
-
-@pytest.fixture(autouse=True)
-def clear_app_state_state_variables():
- """Resets global variables in order to prevent interference between tests."""
- yield
- import lightning.app.utilities.state
-
- lightning.app.utilities.state._STATE = None
- lightning.app.utilities.state._LAST_STATE = None
- AppState._MY_AFFILIATION = ()
- if hasattr(cloud_compute, "_CLOUD_COMPUTE_STORE"):
- cloud_compute._CLOUD_COMPUTE_STORE.clear()
diff --git a/tests/integrations_app/flagship/__init__.py b/tests/integrations_app/flagship/__init__.py
deleted file mode 100644
index be56fdce3190d..0000000000000
--- a/tests/integrations_app/flagship/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import os.path
-
-from integrations_app import _PATH_TESTS_DIR
-
-_PATH_INTEGRATIONS_DIR = os.path.join(_PATH_TESTS_DIR, "_flagship-app")
diff --git a/tests/integrations_app/flagship/test_flashy.py b/tests/integrations_app/flagship/test_flashy.py
deleted file mode 100644
index 429968defa9b7..0000000000000
--- a/tests/integrations_app/flagship/test_flashy.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import contextlib
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-from lightning.app.utilities.imports import _is_playwright_available
-
-from integrations_app.flagship import _PATH_INTEGRATIONS_DIR
-
-if _is_playwright_available():
- import playwright
- from playwright.sync_api import Page, expect
-
-
-# TODO: when this function is moved to the app itself we can just import it, so to keep better aligned
-def validate_app_functionalities(app_page: "Page") -> None:
- """Validate the page after app starts.
-
- this is direct copy-paste of validation living in the app repository:
- https://github.com/Lightning-AI/LAI-Flashy-App/blob/main/tests/test_app_gallery.py#L205
-
- app_page: The UI page of the app to be validated.
-
- """
- while True:
- with contextlib.suppress(playwright._impl._api_types.Error, playwright._impl._api_types.TimeoutError):
- app_page.reload()
- sleep(5)
- app_label = app_page.frame_locator("iframe").locator("text=Choose your AI task")
- app_label.wait_for(timeout=30 * 1000)
- break
-
- input_field = app_page.frame_locator("iframe").locator('input:below(:text("Data URL"))').first
- input_field.wait_for(timeout=1000)
- input_field.type("https://pl-flash-data.s3.amazonaws.com/hymenoptera_data.zip")
- sleep(1)
- upload_btn = app_page.frame_locator("iframe").locator('button:has-text("Upload")')
- upload_btn.wait_for(timeout=1000)
- upload_btn.click()
-
- sleep(10)
-
- train_folder_dropdown = app_page.frame_locator("iframe").locator("#mui-2")
- train_folder_dropdown.click()
-
- train_folder = app_page.frame_locator("iframe").locator('text="hymenoptera_data/train"')
- train_folder.scroll_into_view_if_needed()
- train_folder.click()
-
- val_folder_dropdown = app_page.frame_locator("iframe").locator("#mui-3")
- val_folder_dropdown.click()
-
- val_folder = app_page.frame_locator("iframe").locator('text="hymenoptera_data/val"')
- val_folder.scroll_into_view_if_needed()
- val_folder.click()
-
- train_btn = app_page.frame_locator("iframe").locator('button:has-text("Start training!")')
- train_btn.click()
-
- # Sometimes the results don't show until we refresh the page
- sleep(10)
-
- app_page.reload()
-
- app_page.frame_locator("iframe").locator('button:has-text("RESULTS")').click()
- runs = app_page.frame_locator("iframe").locator("table tbody tr")
- expect(runs).to_have_count(1, timeout=120000)
-
-
-@pytest.mark.cloud()
-def test_app_cloud() -> None:
- with run_app_in_cloud(_PATH_INTEGRATIONS_DIR) as (_, view_page, _, _):
- validate_app_functionalities(view_page)
diff --git a/tests/integrations_app/flagship/test_jupyter.py b/tests/integrations_app/flagship/test_jupyter.py
deleted file mode 100644
index 5d33698040b43..0000000000000
--- a/tests/integrations_app/flagship/test_jupyter.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is placeholder and the reals file ATM is being copied from the original repo/project
diff --git a/tests/integrations_app/flagship/test_muse.py b/tests/integrations_app/flagship/test_muse.py
deleted file mode 100644
index 5d33698040b43..0000000000000
--- a/tests/integrations_app/flagship/test_muse.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is placeholder and the reals file ATM is being copied from the original repo/project
diff --git a/tests/integrations_app/local/__init__.py b/tests/integrations_app/local/__init__.py
deleted file mode 100644
index 1e7d17cc6b536..0000000000000
--- a/tests/integrations_app/local/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from pathlib import Path
-
-_PATH_APPS = Path(__file__).resolve().parents[1] / "apps"
diff --git a/tests/integrations_app/local/test_collect_failures.py b/tests/integrations_app/local/test_collect_failures.py
deleted file mode 100644
index ca11b7528bd40..0000000000000
--- a/tests/integrations_app/local/test_collect_failures.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.local import _PATH_APPS
-
-
-@pytest.mark.cloud()
-def test_collect_failures_example_cloud() -> None:
- # logs are in order
- expected_logs = [
- "useless_garbage_log_that_is_always_there_to_overload_logs",
- "waiting_for_work_to_be_ready",
- "work_is_running",
- "flow_and_work_are_running",
- "logger_flow_work",
- "good_value_of_i_1",
- "good_value_of_i_2",
- "good_value_of_i_3",
- "good_value_of_i_4",
- "invalid_value_of_i_5",
- ]
- with run_app_in_cloud(os.path.join(_PATH_APPS, "collect_failures")) as (
- _,
- _,
- fetch_logs,
- _,
- ):
- last_found_log_index = -1
- while len(expected_logs) != 0:
- for index, log in enumerate(fetch_logs()):
- if expected_logs[0] in log:
- print(f"found expected log: {expected_logs[0]}")
- expected_logs.pop(0)
- assert index > last_found_log_index
- if len(expected_logs) == 0:
- break
- sleep(1)
diff --git a/tests/integrations_app/local/test_core_features_app.py b/tests/integrations_app/local/test_core_features_app.py
deleted file mode 100644
index 49f65fdc73a35..0000000000000
--- a/tests/integrations_app/local/test_core_features_app.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import os
-
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import run_app
-
-from integrations_app.local import _PATH_APPS
-
-
-def test_core_features_app_example():
- runner = CliRunner()
- result = runner.invoke(
- run_app,
- [
- os.path.join(_PATH_APPS, "core_features_app", "app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- "--env", # this is to test env variable
- "FOO=bar",
- "--env",
- "BLA=bloz",
- ],
- catch_exceptions=False,
- )
- assert result.exit_code == 0
diff --git a/tests/integrations_app/local/test_custom_work_dependencies.py b/tests/integrations_app/local/test_custom_work_dependencies.py
deleted file mode 100644
index b7867e2c12a02..0000000000000
--- a/tests/integrations_app/local/test_custom_work_dependencies.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.local import _PATH_APPS
-
-
-@pytest.mark.cloud()
-def test_custom_work_dependencies_example_cloud() -> None:
- # if requirements not installed, the app will fail
- with run_app_in_cloud(
- os.path.join(_PATH_APPS, "custom_work_dependencies"),
- app_name="app.py",
- ) as (_, _, fetch_logs, _):
- has_logs = False
- while not has_logs:
- for log in fetch_logs(["flow"]):
- if "Custom Work Dependency checker End" in log:
- has_logs = True
- print(log)
- sleep(1)
diff --git a/tests/integrations_app/local/test_idle_timeout.py b/tests/integrations_app/local/test_idle_timeout.py
deleted file mode 100644
index 9314cc8b8a99a..0000000000000
--- a/tests/integrations_app/local/test_idle_timeout.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.local import _PATH_APPS
-
-
-@pytest.mark.cloud()
-def test_idle_timeout_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_APPS, "idle_timeout")) as (
- _,
- _,
- fetch_logs,
- _,
- ):
- has_logs = False
- while not has_logs:
- for log in fetch_logs(["flow"]):
- if "Application End" in log:
- has_logs = True
- sleep(1)
diff --git a/tests/integrations_app/public/__init__.py b/tests/integrations_app/public/__init__.py
deleted file mode 100644
index 5ad0242e64c01..0000000000000
--- a/tests/integrations_app/public/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from pathlib import Path
-
-_PATH_EXAMPLES = Path(__file__).resolve().parents[3] / "examples" / "app"
diff --git a/tests/integrations_app/public/test_app_dag.py b/tests/integrations_app/public/test_app_dag.py
deleted file mode 100644
index 8145f1266a138..0000000000000
--- a/tests/integrations_app/public/test_app_dag.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_app_dag_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "dag")) as (_, _, fetch_logs, _):
- launch_log, finish_log = False, False
- while not (launch_log and finish_log):
- for log in fetch_logs(["flow"]):
- if "Launching a new DAG" in log:
- launch_log = True
- elif "Finished training and evaluating" in log:
- finish_log = True
- sleep(1)
diff --git a/tests/integrations_app/public/test_argparse.py b/tests/integrations_app/public/test_argparse.py
deleted file mode 100644
index 80cb7103ad23c..0000000000000
--- a/tests/integrations_app/public/test_argparse.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import os
-import sys
-
-from lightning.app.testing.testing import application_testing
-from lightning.app.utilities.load_app import _patch_sys_argv
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-def test_app_argparse_example():
- original_argv = sys.argv
-
- command_line = [
- os.path.join(_PATH_EXAMPLES, "argparse", "app.py"),
- "--app_args",
- "--use_gpu",
- "--without-server",
- ]
- result = application_testing(command_line=command_line)
- assert result.exit_code == 0, result.__dict__
- assert sys.argv == original_argv
-
-
-def test_patch_sys_argv():
- original_argv = sys.argv
-
- sys.argv = expected = ["lightning", "run", "app", "app.py"]
- with _patch_sys_argv():
- assert sys.argv == ["app.py"]
-
- assert sys.argv == expected
-
- sys.argv = expected = ["lightning", "run", "app", "app.py", "--without-server", "--env", "name=something"]
- with _patch_sys_argv():
- assert sys.argv == ["app.py"]
-
- assert sys.argv == expected
-
- sys.argv = expected = ["lightning", "run", "app", "app.py", "--app_args"]
- with _patch_sys_argv():
- assert sys.argv == ["app.py"]
-
- assert sys.argv == expected
-
- sys.argv = expected = ["lightning", "run", "app", "app.py", "--app_args", "--env", "name=something"]
- with _patch_sys_argv():
- assert sys.argv == ["app.py"]
-
- assert sys.argv == expected
-
- sys.argv = expected = [
- "lightning",
- "run",
- "app",
- "app.py",
- "--without-server",
- "--app_args",
- "--use_gpu",
- "--name=hello",
- "--env",
- "name=something",
- ]
- with _patch_sys_argv():
- assert sys.argv == ["app.py", "--use_gpu", "--name=hello"]
-
- assert sys.argv == expected
-
- sys.argv = original_argv
diff --git a/tests/integrations_app/public/test_boring_app.py b/tests/integrations_app/public/test_boring_app.py
deleted file mode 100644
index 8bfb1d4daf999..0000000000000
--- a/tests/integrations_app/public/test_boring_app.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import os
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import show
-from lightning.app.testing.testing import run_app_in_cloud, wait_for
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_boring_app_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "boring"), app_name="app_dynamic.py", debug=True) as (
- _,
- view_page,
- _,
- name,
- ):
-
- def check_hello_there(*_, **__):
- locator = view_page.frame_locator("iframe").locator('ul:has-text("Hello there!")')
- if len(locator.all_text_contents()):
- return True
- return None
-
- wait_for(view_page, check_hello_there)
-
- runner = CliRunner()
- result = runner.invoke(show.commands["logs"], [name])
-
- assert result.exit_code == 0
- assert result.exception is None
- # TODO: Resolve
- # lines = result.output.splitlines()
- # assert any("Received from root.dict.dst_w" in line for line in lines)
- print("Succeeded App!")
diff --git a/tests/integrations_app/public/test_commands_and_api.py b/tests/integrations_app/public/test_commands_and_api.py
deleted file mode 100644
index 84ac4e6814ee6..0000000000000
--- a/tests/integrations_app/public/test_commands_and_api.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import os
-from subprocess import Popen
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.timeout(300)
-@pytest.mark.cloud()
-def test_commands_and_api_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "commands_and_api")) as (
- _,
- view_page,
- fetch_logs,
- app_name,
- ):
- # Connect to the App and send the first & second command with the client
- # Requires to be run within the same process.
- cmd_1 = f"python -m lightning connect app {app_name}"
- cmd_2 = "python -m lightning command with client --name=this"
- cmd_3 = "python -m lightning command without client --name=is"
- cmd_4 = "python -m lightning command without client --name=awesome"
- cmd_5 = "lightning logout"
- process = Popen(" && ".join([cmd_1, cmd_2, cmd_3, cmd_4, cmd_5]), shell=True)
- process.wait()
- "/".join(view_page.url.split("/")[:-2])
-
- # Validate the logs.
- has_logs = False
- while not has_logs:
- for log in fetch_logs():
- if "['this', 'is', 'awesome']" in log:
- has_logs = True
- sleep(1)
diff --git a/tests/integrations_app/public/test_drive.py b/tests/integrations_app/public/test_drive.py
deleted file mode 100644
index 7401b595c55fe..0000000000000
--- a/tests/integrations_app/public/test_drive.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_drive_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "drive")) as (
- _,
- _,
- fetch_logs,
- _,
- ):
- has_logs = False
- while not has_logs:
- for log in fetch_logs(["flow"]):
- if "Application End!" in log:
- has_logs = True
- sleep(1)
diff --git a/tests/integrations_app/public/test_gradio.py b/tests/integrations_app/public/test_gradio.py
deleted file mode 100644
index 219f720223dfc..0000000000000
--- a/tests/integrations_app/public/test_gradio.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import os
-from unittest import mock
-from unittest.mock import ANY
-
-
-@mock.patch.dict(os.environ, {"LIGHTING_TESTING": "1"})
-@mock.patch("lightning.app.components.serve.gradio_server.gradio")
-def test_serve_gradio(gradio_mock):
- from lightning.app.components.serve.gradio_server import ServeGradio
-
- class MyGradioServe(ServeGradio):
- inputs = gradio_mock.inputs.Image(type="pil")
- outputs = gradio_mock.outputs.Image(type="pil")
- examples = [["./examples/app/components/serve/gradio/beyonce.png"]]
-
- def build_model(self):
- super().build_model()
- return "model"
-
- def predict(self, *args, **kwargs):
- super().predict(*args, **kwargs)
- return "prediction"
-
- comp = MyGradioServe()
- comp.run()
- assert comp.model == "model"
- assert comp.predict() == "prediction"
- gradio_mock.Interface.assert_called_once_with(
- fn=ANY, inputs=ANY, outputs=ANY, examples=ANY, title=None, description=None, theme=ANY
- )
diff --git a/tests/integrations_app/public/test_installation_commands_app.py b/tests/integrations_app/public/test_installation_commands_app.py
deleted file mode 100644
index 1ad4ec55eb4ce..0000000000000
--- a/tests/integrations_app/public/test_installation_commands_app.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_installation_commands_app_example_cloud() -> None:
- # This is expected to pass, since the "setup" flag is passed
- with run_app_in_cloud(
- os.path.join(_PATH_EXAMPLES, "installation_commands"),
- app_name="app.py",
- extra_args=["--setup"],
- debug=True,
- ) as (_, _, fetch_logs, _):
- has_logs = False
- while not has_logs:
- for log in fetch_logs(["work"]):
- if "lmdb successfully installed" in log:
- has_logs = True
diff --git a/tests/integrations_app/public/test_layout.py b/tests/integrations_app/public/test_layout.py
deleted file mode 100644
index c86016c031c47..0000000000000
--- a/tests/integrations_app/public/test_layout.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import os
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import run_app
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.xfail(strict=False, reason="test is skipped because CI was blocking all the PRs.")
-def test_layout_example():
- runner = CliRunner()
- result = runner.invoke(
- run_app,
- [
- os.path.join(_PATH_EXAMPLES, "layout", "app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ],
- catch_exceptions=False,
- )
- assert "Layout End" in str(result.stdout_bytes)
- assert result.exit_code == 0
diff --git a/tests/integrations_app/public/test_multi_node.py b/tests/integrations_app/public/test_multi_node.py
deleted file mode 100644
index ec4597138c8c6..0000000000000
--- a/tests/integrations_app/public/test_multi_node.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import os
-from unittest import mock
-
-import pytest
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.testing.testing import LightningTestApp, application_testing
-from lightning_utilities.core.imports import package_available
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-class LightningTestMultiNodeApp(LightningTestApp):
- def on_before_run_once(self):
- res = super().on_before_run_once()
- if self.works and all(w.has_stopped for w in self.works):
- assert len(self.works) == 2
- return True
- return res
-
-
-# for the skip to work, the package needs to be installed without editable mode
-_SKIP_LIGHTNING_UNAVAILABLE = pytest.mark.skipif(not package_available("lightning"), reason="script requires lightning")
-
-
-@pytest.mark.parametrize(
- "app_name",
- [
- "train_pytorch.py",
- "train_any.py",
- "train_pytorch_spawn.py",
- pytest.param("train_fabric.py", marks=_SKIP_LIGHTNING_UNAVAILABLE),
- pytest.param("train_lt_script.py", marks=_SKIP_LIGHTNING_UNAVAILABLE),
- pytest.param("train_lt.py", marks=_SKIP_LIGHTNING_UNAVAILABLE),
- ],
-)
-@_RunIf(skip_windows=True) # flaky
-@mock.patch("lightning.app.components.multi_node.base.is_running_in_cloud", return_value=True)
-def test_multi_node_examples(_, app_name, monkeypatch):
- # note: this test will fail locally:
- # * if you installed `lightning.app`, then the examples need to be
- # rewritten to use `lightning.app` imports (CI does this)
- # * if you installed `lightning`, then the imports in this file and mocks
- # need to be changed to use `lightning`.
- monkeypatch.chdir(os.path.join(_PATH_EXAMPLES, "multi_node"))
- command_line = [app_name, "--blocking", "False", "--open-ui", "False", "--setup"]
- result = application_testing(LightningTestMultiNodeApp, command_line)
- assert result.exit_code == 0
diff --git a/tests/integrations_app/public/test_payload.py b/tests/integrations_app/public/test_payload.py
deleted file mode 100644
index d7ab5d43e34ef..0000000000000
--- a/tests/integrations_app/public/test_payload.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_payload_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "payload")) as (_, _, fetch_logs, _):
- has_logs = False
- while not has_logs:
- for log in fetch_logs(["flow"]):
- if "Application End!" in log:
- has_logs = True
- sleep(1)
diff --git a/tests/integrations_app/public/test_pickle_or_not.py b/tests/integrations_app/public/test_pickle_or_not.py
deleted file mode 100644
index 5d94ff657d819..0000000000000
--- a/tests/integrations_app/public/test_pickle_or_not.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import os
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import run_app
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-# TODO: Investigate why it doesn't work
-@pytest.mark.xfail(strict=False, reason="test has been ignored for a while and seems not to be working :(")
-def test_pickle_or_not_example():
- runner = CliRunner()
- result = runner.invoke(
- run_app,
- [
- os.path.join(_PATH_EXAMPLES, "pickle_or_not", "app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ],
- catch_exceptions=False,
- )
- assert "Pickle or Not End" in str(result.stdout_bytes)
- assert result.exit_code == 0
diff --git a/tests/integrations_app/public/test_quick_start.py b/tests/integrations_app/public/test_quick_start.py
deleted file mode 100644
index ed1fb399e5cc7..0000000000000
--- a/tests/integrations_app/public/test_quick_start.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import logging
-import os
-from unittest import mock
-
-import pytest
-from click.testing import CliRunner
-from lightning.app import LightningApp
-from lightning.app.cli.lightning_cli import run_app
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.testing.testing import run_app_in_cloud, wait_for
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-class QuickStartApp(LightningApp):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.root.serve_work._parallel = True
-
- def run_once(self):
- done = super().run_once()
- if self.root.train_work.best_model_path:
- return True
- return done
-
-
-# TODO: Investigate why it doesn't work
-@pytest.mark.xfail(strict=False, reason="test is skipped because CI was blocking all the PRs.")
-@_RunIf(pl=True, skip_windows=True, skip_linux=True)
-def test_quick_start_example(caplog, monkeypatch):
- """This test ensures the Quick Start example properly train and serve PyTorch Lightning."""
- monkeypatch.setattr("logging.getLogger", mock.MagicMock(return_value=logging.getLogger()))
-
- with caplog.at_level(logging.INFO):
- with mock.patch("lightning.app.LightningApp", QuickStartApp):
- runner = CliRunner()
- result = runner.invoke(
- run_app,
- [
- os.path.join(_PATH_EXAMPLES, "lightning-quick-start", "app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ],
- catch_exceptions=False,
- )
- assert result.exit_code == 0
-
-
-@pytest.mark.cloud()
-def test_quick_start_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "lightning-quick-start")) as (_, view_page, _, _):
-
- def click_gradio_demo(*_, **__):
- button = view_page.locator('button:has-text("Interactive demo")')
- button.wait_for(timeout=5 * 1000)
- button.click()
- return True
-
- wait_for(view_page, click_gradio_demo)
-
- def check_examples(*_, **__):
- locator = view_page.frame_locator("iframe").locator('button:has-text("Submit")')
- locator.wait_for(timeout=10 * 1000)
- if len(locator.all_text_contents()) > 0:
- return True
- return None
-
- wait_for(view_page, check_examples)
diff --git a/tests/integrations_app/public/test_scripts.py b/tests/integrations_app/public/test_scripts.py
deleted file mode 100644
index 0a777784690d3..0000000000000
--- a/tests/integrations_app/public/test_scripts.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import os
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import run_app
-from lightning.app.testing.helpers import _run_script, _RunIf
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@_RunIf(pl=True, skip_windows=True)
-@pytest.mark.parametrize(
- "file",
- [
- pytest.param("component_tracer.py"),
- pytest.param("component_popen.py"),
- ],
-)
-def test_scripts(file):
- _run_script(str(os.path.join(_PATH_EXAMPLES, f"components/python/{file}")))
-
-
-@pytest.mark.xfail(strict=False, reason="causing some issues with CI, not sure if the test is actually needed")
-@_RunIf(pl=True, skip_windows=True)
-def test_components_app_example():
- runner = CliRunner()
- result = runner.invoke(
- run_app,
- [
- os.path.join(_PATH_EXAMPLES, "components/python/app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ],
- catch_exceptions=False,
- )
- assert result.exit_code == 0
- assert "tracer script succeed" in result.stdout
diff --git a/tests/integrations_app/public/test_template_react_ui.py b/tests/integrations_app/public/test_template_react_ui.py
deleted file mode 100644
index e9653254df27d..0000000000000
--- a/tests/integrations_app/public/test_template_react_ui.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud, wait_for
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_template_react_ui_example_cloud() -> None:
- """This test ensures streamlit works in the cloud by clicking a button and checking the logs."""
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "template_react_ui")) as (
- _,
- view_page,
- fetch_logs,
- _,
- ):
-
- def click_button(*_, **__):
- button = view_page.frame_locator("iframe").locator('button:has-text("Start Printing")')
- button.wait_for(timeout=3 * 1000)
- if button.all_text_contents() == ["Start Printing"]:
- button.click()
- return True
- return None
-
- wait_for(view_page, click_button)
-
- has_logs = False
- while not has_logs:
- for log in fetch_logs():
- if "0: Hello World!" in log:
- has_logs = True
- sleep(1)
diff --git a/tests/integrations_app/public/test_template_streamlit_ui.py b/tests/integrations_app/public/test_template_streamlit_ui.py
deleted file mode 100644
index 34d9b683bc41d..0000000000000
--- a/tests/integrations_app/public/test_template_streamlit_ui.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import os
-from time import sleep
-
-import pytest
-from lightning.app.testing.testing import run_app_in_cloud, wait_for
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-@pytest.mark.cloud()
-def test_template_streamlit_ui_example_cloud() -> None:
- """This test ensures streamlit works in the cloud by clicking a button and checking the logs."""
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "template_streamlit_ui")) as (
- _,
- view_page,
- fetch_logs,
- _,
- ):
-
- def click_button(*_, **__):
- button = view_page.frame_locator("iframe").locator('button:has-text("Should print to the terminal ?")')
-
- if button.all_text_contents() == ["Should print to the terminal ?"]:
- button.click()
- return True
- return None
-
- wait_for(view_page, click_button)
-
- has_logs = False
- while not has_logs:
- for log in fetch_logs():
- if "Hello World!" in log:
- has_logs = True
- sleep(1)
diff --git a/tests/integrations_app/public/test_v0_app.py b/tests/integrations_app/public/test_v0_app.py
deleted file mode 100644
index a880d52ceb694..0000000000000
--- a/tests/integrations_app/public/test_v0_app.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import os
-from time import sleep
-from typing import Tuple
-from unittest import mock
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app import LightningApp
-from lightning.app.runners import CloudRuntime
-from lightning.app.testing import EmptyFlow
-from lightning.app.testing.testing import LightningTestApp, application_testing, run_app_in_cloud, wait_for
-from lightning.app.utilities.enum import AppStage
-from lightning.app.utilities.load_app import load_app_from_file
-
-from integrations_app.public import _PATH_EXAMPLES
-
-
-class LightningAppTestInt(LightningTestApp):
- def run_once(self) -> Tuple[bool, float]:
- if self.root.counter == 1:
- print("V0 App End")
- self.stage = AppStage.STOPPING
- return True, 0.0
- return super().run_once()
-
-
-def test_v0_app_example():
- command_line = [
- os.path.join(_PATH_EXAMPLES, "v0", "app.py"),
- "--blocking",
- "False",
- "--open-ui",
- "False",
- ]
- result = application_testing(LightningAppTestInt, command_line)
- assert result.exit_code == 0
-
-
-def run_v0_app(fetch_logs, view_page):
- def check_content(button_name, text_content):
- button = view_page.locator(f'button:has-text("{button_name}")')
- button.wait_for(timeout=3 * 1000)
- button.click()
- view_page.reload()
- locator = view_page.frame_locator("iframe").locator("div")
- locator.wait_for(timeout=3 * 1000)
- assert text_content in " ".join(locator.all_text_contents())
- print(f"Validated {button_name}")
- return True
-
- wait_for(view_page, check_content, "TAB_1", "Hello from component A")
- wait_for(view_page, check_content, "TAB_2", "Hello from component B")
- has_logs = False
- while not has_logs:
- for log in fetch_logs(["flow"]):
- print(log)
- if "'a': 'a', 'b': 'b'" in log:
- has_logs = True
- sleep(1)
-
-
-@pytest.mark.cloud()
-@pytest.mark.skipif(
- os.environ.get("LIGHTNING_BYOC_CLUSTER_ID") is None,
- reason="missing LIGHTNING_BYOC_CLUSTER_ID environment variable",
-)
-def test_v0_app_example_byoc_cloud() -> None:
- with run_app_in_cloud(
- os.path.join(_PATH_EXAMPLES, "v0"),
- extra_args=["--cluster-id", os.environ.get("LIGHTNING_BYOC_CLUSTER_ID")],
- ) as (_, view_page, fetch_logs, _):
- run_v0_app(fetch_logs, view_page)
-
-
-@pytest.mark.cloud()
-def test_v0_app_example_cloud() -> None:
- with run_app_in_cloud(os.path.join(_PATH_EXAMPLES, "v0")) as (
- _,
- view_page,
- fetch_logs,
- _,
- ):
- run_v0_app(fetch_logs, view_page)
-
-
-@mock.patch(
- "lightning.app.runners.cloud.load_app_from_file",
- MagicMock(side_effect=ModuleNotFoundError("Module X not found")),
-)
-def test_load_app_from_file_module_error():
- empty_app = CloudRuntime.load_app_from_file(os.path.join(_PATH_EXAMPLES, "v0", "app.py"))
- assert isinstance(empty_app, LightningApp)
- assert isinstance(empty_app.root, EmptyFlow)
-
-
-def test_load_app_from_file():
- app = load_app_from_file(os.path.join(_PATH_EXAMPLES, "v0", "app.py"))
- assert isinstance(app, LightningApp)
diff --git a/tests/tests_app/__init__.py b/tests/tests_app/__init__.py
deleted file mode 100644
index e1a00cdd0988b..0000000000000
--- a/tests/tests_app/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import os
-
-_TESTS_ROOT = os.path.dirname(__file__)
-_PROJECT_ROOT = os.path.dirname(os.path.dirname(_TESTS_ROOT))
diff --git a/tests/tests_app/cli/__init__.py b/tests/tests_app/cli/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/cli/jsons/connect_1.json b/tests/tests_app/cli/jsons/connect_1.json
deleted file mode 100644
index 112afa9e9e251..0000000000000
--- a/tests/tests_app/cli/jsons/connect_1.json
+++ /dev/null
@@ -1,311 +0,0 @@
-{
- "openapi": "3.0.2",
- "info": { "title": "FastAPI", "version": "0.1.0" },
- "paths": {
- "/api/v1/state": {
- "get": {
- "summary": "Get State",
- "operationId": "get_state_api_v1_state_get",
- "parameters": [
- {
- "required": false,
- "schema": { "title": "X-Lightning-Type", "type": "string" },
- "name": "x-lightning-type",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Uuid", "type": "string" },
- "name": "x-lightning-session-uuid",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Id", "type": "string" },
- "name": "x-lightning-session-id",
- "in": "header"
- }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- },
- "post": {
- "summary": "Post State",
- "operationId": "post_state_api_v1_state_post",
- "parameters": [
- {
- "required": false,
- "schema": { "title": "X-Lightning-Type", "type": "string" },
- "name": "x-lightning-type",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Uuid", "type": "string" },
- "name": "x-lightning-session-uuid",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Id", "type": "string" },
- "name": "x-lightning-session-id",
- "in": "header"
- }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/api/v1/spec": {
- "get": {
- "summary": "Get Spec",
- "operationId": "get_spec_api_v1_spec_get",
- "parameters": [
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Uuid", "type": "string" },
- "name": "x-lightning-session-uuid",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Id", "type": "string" },
- "name": "x-lightning-session-id",
- "in": "header"
- }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/api/v1/delta": {
- "post": {
- "summary": "Post Delta",
- "description": "This endpoint is used to make an update to the app state using delta diff, mainly used by streamlit to\nupdate the state.",
- "operationId": "post_delta_api_v1_delta_post",
- "parameters": [
- {
- "required": false,
- "schema": { "title": "X-Lightning-Type", "type": "string" },
- "name": "x-lightning-type",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Uuid", "type": "string" },
- "name": "x-lightning-session-uuid",
- "in": "header"
- },
- {
- "required": false,
- "schema": { "title": "X-Lightning-Session-Id", "type": "string" },
- "name": "x-lightning-session-id",
- "in": "header"
- }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/api/v1/upload_file/{filename}": {
- "put": {
- "summary": "Upload File",
- "operationId": "upload_file_api_v1_upload_file__filename__put",
- "parameters": [
- { "required": true, "schema": { "title": "Filename", "type": "string" }, "name": "filename", "in": "path" }
- ],
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": { "$ref": "#/components/schemas/Body_upload_file_api_v1_upload_file__filename__put" }
- }
- },
- "required": true
- },
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/healthz": {
- "get": {
- "summary": "Healthz",
- "description": "Health check endpoint used in the cloud FastAPI servers to check the status periodically.",
- "operationId": "healthz_healthz_get",
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }
- }
- }
- },
- "/user/command_without_client": {
- "post": {
- "tags": ["app_api"],
- "summary": "Command Without Client",
- "operationId": "command_without_client_user_command_without_client_post",
- "parameters": [
- { "required": true, "schema": { "title": "Name", "type": "string" }, "name": "name", "in": "query" }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/command/command_without_client": {
- "post": {
- "tags": ["app_command"],
- "summary": "Command Without Client",
- "description": "A command without a client.",
- "operationId": "command_without_client_command_command_without_client_post",
- "parameters": [
- { "required": true, "schema": { "title": "Name", "type": "string" }, "name": "name", "in": "query" }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/command/command_with_client": {
- "post": {
- "tags": ["app_client_command"],
- "summary": "Command With Client",
- "description": "A command with a client.",
- "operationId": "command_with_client_command_command_with_client_post",
- "requestBody": {
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomConfig" } } },
- "required": true
- },
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- },
- "cls_path": "examples/app/commands_and_api/command.py",
- "cls_name": "CustomCommand"
- }
- },
- "/command/nested_command": {
- "post": {
- "tags": ["app_command"],
- "summary": "Nested Command",
- "description": "A nested command.",
- "operationId": "nested_command_command_nested_command_post",
- "parameters": [
- { "required": true, "schema": { "title": "Name", "type": "string" }, "name": "name", "in": "query" }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/api{full_path}": {
- "get": {
- "summary": "Api Catch All",
- "operationId": "api_catch_all_api_full_path__get",
- "parameters": [
- { "required": true, "schema": { "title": "Full Path", "type": "string" }, "name": "full_path", "in": "path" }
- ],
- "responses": {
- "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- },
- "/{full_path}": {
- "get": {
- "summary": "Frontend Route",
- "operationId": "frontend_route__full_path__get",
- "parameters": [
- { "required": true, "schema": { "title": "Full Path", "type": "string" }, "name": "full_path", "in": "path" }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": { "text/html": { "schema": { "type": "string" } } }
- },
- "422": {
- "description": "Validation Error",
- "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
- }
- }
- }
- }
- },
- "components": {
- "schemas": {
- "Body_upload_file_api_v1_upload_file__filename__put": {
- "title": "Body_upload_file_api_v1_upload_file__filename__put",
- "required": ["uploaded_file"],
- "type": "object",
- "properties": { "uploaded_file": { "title": "Uploaded File", "type": "string", "format": "binary" } }
- },
- "CustomConfig": {
- "title": "CustomConfig",
- "required": ["name"],
- "type": "object",
- "properties": { "name": { "title": "Name", "type": "string" } }
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": { "title": "Detail", "type": "array", "items": { "$ref": "#/components/schemas/ValidationError" } }
- }
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] }
- },
- "msg": { "title": "Message", "type": "string" },
- "type": { "title": "Error Type", "type": "string" }
- }
- }
- }
- }
-}
diff --git a/tests/tests_app/cli/launch_data/app_v0/__init__.py b/tests/tests_app/cli/launch_data/app_v0/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/cli/launch_data/app_v0/app.py b/tests/tests_app/cli/launch_data/app_v0/app.py
deleted file mode 100644
index f0b37bd411289..0000000000000
--- a/tests/tests_app/cli/launch_data/app_v0/app.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# v0_app.py
-import os
-from datetime import datetime
-from time import sleep
-
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.frontend.web import StaticWebFrontend
-
-
-class Word(LightningFlow):
- def __init__(self, letter):
- super().__init__()
- self.letter = letter
- self.repeats = letter
-
- def run(self):
- self.repeats += self.letter
-
- def configure_layout(self):
- return StaticWebFrontend(serve_dir=os.path.join(os.path.dirname(__file__), f"ui/{self.letter}"))
-
-
-class V0App(LightningFlow):
- def __init__(self):
- super().__init__()
- self.aas = Word("a")
- self.bbs = Word("b")
- self.counter = 0
-
- def run(self):
- now = datetime.now()
- now = now.strftime("%H:%M:%S")
- log = {"time": now, "a": self.aas.repeats, "b": self.bbs.repeats}
- print(log)
- self.aas.run()
- self.bbs.run()
-
- sleep(2.0)
- self.counter += 1
-
- def configure_layout(self):
- tab1 = {"name": "Tab_1", "content": self.aas}
- tab2 = {"name": "Tab_2", "content": self.bbs}
- tab3 = {
- "name": "Tab_3",
- "content": "https://tensorboard.dev/experiment/8m1aX0gcQ7aEmH0J7kbBtg/#scalars",
- }
- return [tab1, tab2, tab3]
-
-
-app = LightningApp(V0App())
diff --git a/tests/tests_app/cli/launch_data/app_v0/ui/a/index.html b/tests/tests_app/cli/launch_data/app_v0/ui/a/index.html
deleted file mode 100644
index 6ddb9a5a1323c..0000000000000
--- a/tests/tests_app/cli/launch_data/app_v0/ui/a/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Hello from component A
diff --git a/tests/tests_app/cli/launch_data/app_v0/ui/b/index.html b/tests/tests_app/cli/launch_data/app_v0/ui/b/index.html
deleted file mode 100644
index 3bfd9e24cb7f7..0000000000000
--- a/tests/tests_app/cli/launch_data/app_v0/ui/b/index.html
+++ /dev/null
@@ -1 +0,0 @@
-Hello from component B
diff --git a/tests/tests_app/cli/test_cd.py b/tests/tests_app/cli/test_cd.py
deleted file mode 100644
index 64865fa4ac46b..0000000000000
--- a/tests/tests_app/cli/test_cd.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import os
-import sys
-from unittest import mock
-
-import pytest
-from lightning.app.cli.commands import cd
-from lightning.app.cli.commands.pwd import pwd
-
-
-@mock.patch("lightning.app.cli.commands.cd.ls", mock.MagicMock())
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cd(monkeypatch):
- """This test validates cd behaves as expected."""
- ls = mock.MagicMock()
- monkeypatch.setattr(cd, "ls", ls)
-
- assert cd.cd("/") == "/"
- assert pwd() == "/"
- ls.ls.return_value = ["hero"]
- assert cd.cd("hero") == "/hero"
- assert pwd() == "/hero"
- ls.ls.return_value = ["something_else"]
- assert f"/hero{os.sep}something_else" == cd.cd("something_else")
- assert f"/hero{os.sep}something_else" == pwd()
- ls.ls.return_value = ["hello"]
- assert f"/hero{os.sep}something_else{os.sep}hello{os.sep}a" == cd.cd("hello/a")
- assert f"/hero{os.sep}something_else{os.sep}hello{os.sep}a" == pwd()
- assert f"/hero{os.sep}something_else" == cd.cd(f"..{os.sep}..")
- ls.ls.return_value = ["something_else"]
- assert f"/hero{os.sep}something_else" == pwd()
- assert cd.cd("..") == "/hero"
- assert pwd() == "/hero"
- assert cd.cd("/") == "/"
- assert pwd() == "/"
- ls.ls.return_value = ["a"]
- assert cd.cd("../a") == "/a"
- assert pwd() == "/a"
- ls.ls.return_value = ["thomas"]
- assert f"/a{os.sep}thomas{os.sep}hello" == cd.cd(f"thomas{os.sep}hello")
- assert f"/a{os.sep}thomas{os.sep}hello" == pwd()
- ls.ls.return_value = ["thomas"]
- assert f"/thomas{os.sep}hello" == cd.cd(f"/thomas{os.sep}hello")
- assert f"/thomas{os.sep}hello" == pwd()
- assert cd.cd("/") == "/"
- ls.ls.return_value = ["name with spaces"]
- assert cd.cd("name with spaces") == "/name with spaces"
- ls.ls.return_value = ["name with spaces 2"]
- assert cd.cd("name with spaces 2") == "/name with spaces/name with spaces 2"
-
- os.remove(cd._CD_FILE)
-
- mock_exit = mock.MagicMock()
- monkeypatch.setattr(cd, "_error_and_exit", mock_exit)
- assert cd.cd("/") == "/"
- ls.ls.return_value = ["project_a"]
- cd.cd("project_b")
- assert mock_exit._mock_call_args.args[0] == "no such file or directory: /project_b"
diff --git a/tests/tests_app/cli/test_cli.py b/tests/tests_app/cli/test_cli.py
deleted file mode 100644
index e3e936e22ca0a..0000000000000
--- a/tests/tests_app/cli/test_cli.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import os
-from unittest import mock
-from unittest.mock import MagicMock
-
-import pytest
-from click.testing import CliRunner
-from lightning.app import __version__
-from lightning.app.cli.lightning_cli import _main, logout, run
-from lightning.app.cli.lightning_cli_delete import delete
-from lightning.app.cli.lightning_cli_list import get_list, list_apps
-from lightning.app.utilities.exceptions import _ApiExceptionHandler
-
-
-@pytest.mark.parametrize("command", [_main, run, get_list, delete])
-def test_commands(command):
- runner = CliRunner()
- result = runner.invoke(command)
- assert result.exit_code == 0
-
-
-def test_main_lightning_cli_no_arguments():
- """Validate the Lightning CLI without args."""
- res = os.popen("lightning_app").read()
- assert "login " in res
- assert "logout " in res
- assert "run " in res
- assert "list " in res
- assert "delete " in res
- assert "show " in res
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch("lightning.app.cli.cmd_apps._AppManager.list")
-def test_list_apps(list_command: mock.MagicMock):
- runner = CliRunner()
- runner.invoke(list_apps)
-
-
-@mock.patch("pathlib.Path.unlink")
-@mock.patch("pathlib.Path.exists")
-@pytest.mark.parametrize("creds", [True, False])
-def test_cli_logout(exists: mock.MagicMock, unlink: mock.MagicMock, creds: bool):
- exists.return_value = creds
- runner = CliRunner()
- runner.invoke(logout)
-
- exists.assert_called_once_with()
- if creds:
- unlink.assert_called_once_with()
- else:
- unlink.assert_not_called()
-
-
-def test_lightning_cli_version():
- res = os.popen("lightning_app --version").read()
- assert __version__ in res
-
-
-def test_main_catches_api_exceptions():
- assert isinstance(_main, _ApiExceptionHandler)
diff --git a/tests/tests_app/cli/test_cloud_cli.py b/tests/tests_app/cli/test_cloud_cli.py
deleted file mode 100644
index 42f8d5d6b3d90..0000000000000
--- a/tests/tests_app/cli/test_cloud_cli.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import enum
-import logging
-import os
-from dataclasses import dataclass
-from functools import partial
-from unittest import mock
-from unittest.mock import ANY, MagicMock, call
-
-import lightning.app.runners.backends.cloud as cloud_backend
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import run_app
-from lightning.app.runners import cloud
-from lightning.app.runners.cloud import CloudRuntime
-from lightning_cloud.openapi import (
- V1CloudSpace,
- V1ListCloudSpacesResponse,
- V1ListLightningappInstancesResponse,
- V1ListMembershipsResponse,
- V1Membership,
-)
-from lightning_cloud.openapi.rest import ApiException
-
-from tests_app import _PROJECT_ROOT
-
-_FILE_PATH = os.path.join(_PROJECT_ROOT, "tests", "tests_app", "core", "scripts", "app_metadata.py")
-
-
-@dataclass
-class AppMetadata:
- id: str
-
-
-@dataclass
-class FakeResponse:
- lightningapps = [AppMetadata(id="my_app")]
-
-
-class FakeLightningClient:
- def cloud_space_service_list_cloud_spaces(self, *args, **kwargs):
- return V1ListCloudSpacesResponse(cloudspaces=[])
-
- def lightningapp_instance_service_list_lightningapp_instances(self, *args, **kwargs):
- return V1ListLightningappInstancesResponse(lightningapps=[])
-
- def lightningapp_service_delete_lightningapp(self, id: str = None):
- assert id == "my_app"
-
- def projects_service_list_memberships(self):
- return V1ListMembershipsResponse(memberships=[V1Membership(name="test-project", project_id="test-project-id")])
-
-
-class CloudRuntimePatch(CloudRuntime):
- def __init__(self, *args, **kwargs):
- super_init = super().__init__
- if hasattr(super_init, "__wrapped__"):
- super_init.__wrapped__(self, *args, **kwargs)
- else:
- super_init(*args, **kwargs)
-
-
-class V1LightningappInstanceState(enum.Enum):
- FAILED = "failed"
- SUCCESS = "success"
-
-
-@dataclass
-class FailedStatus:
- phase = V1LightningappInstanceState.FAILED
-
-
-@dataclass
-class SuccessStatus:
- phase = V1LightningappInstanceState.SUCCESS
-
-
-@dataclass
-class RuntimeErrorResponse:
- id = "my_app"
- source_upload_url = "something"
- status = FailedStatus()
-
-
-@dataclass
-class RuntimeErrorResponse2:
- id = "my_app"
- source_upload_url = ""
- status = SuccessStatus()
-
-
-@dataclass
-class SuccessResponse:
- id = "my_app"
- source_upload_url = "something"
- status = SuccessStatus()
-
-
-@dataclass
-class ExceptionResponse:
- status = FailedStatus()
-
-
-class FakeLightningClientCreate(FakeLightningClient):
- def __init__(self, *args, create_response, **kwargs):
- super().__init__()
- self.create_response = create_response
-
- def cloud_space_service_create_cloud_space(self, *args, **kwargs):
- return V1CloudSpace(id="my_app", name="app")
-
- def cloud_space_service_create_lightning_run(self, project_id, cloudspace_id, body):
- assert project_id == "test-project-id"
- return self.create_response
-
- def cloud_space_service_create_lightning_run_instance(self, project_id, cloudspace_id, id, body):
- assert project_id == "test-project-id"
- return self.create_response
-
-
-@mock.patch("lightning.app.core.queues.QueuingSystem", MagicMock())
-@mock.patch("lightning.app.runners.runtime_type.CloudRuntime", CloudRuntimePatch)
-@pytest.mark.parametrize("create_response", [RuntimeErrorResponse(), RuntimeErrorResponse2()])
-def test_start_app(create_response, monkeypatch):
- monkeypatch.setattr(cloud, "V1LightningappInstanceState", MagicMock())
- monkeypatch.setattr(cloud, "CloudspaceIdRunsBody", MagicMock())
- monkeypatch.setattr(cloud, "V1Flowserver", MagicMock())
- monkeypatch.setattr(cloud, "V1LightningappInstanceSpec", MagicMock())
- monkeypatch.setattr(
- cloud_backend,
- "LightningClient",
- partial(FakeLightningClientCreate, create_response=create_response),
- )
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", MagicMock())
-
- runner = CliRunner()
-
- def run():
- result = runner.invoke(run_app, [_FILE_PATH, "--cloud", "--open-ui=False"], catch_exceptions=False)
- assert result.exit_code == 0
-
- if isinstance(create_response, RuntimeErrorResponse):
- cloud.V1LightningappInstanceState.FAILED = V1LightningappInstanceState.FAILED
- with pytest.raises(RuntimeError, match="Failed to create the application"):
- run()
- elif isinstance(create_response, RuntimeErrorResponse2):
- with pytest.raises(RuntimeError, match="The source upload url is empty."):
- run()
- else:
- run()
- mocks_calls = cloud.LocalSourceCodeDir._mock_mock_calls
- assert len(mocks_calls) == 5
- assert str(mocks_calls[0].kwargs["path"]) == os.path.dirname(_FILE_PATH)
- mocks_calls[1].assert_called_once()
- mocks_calls[2].assert_called_once(url="url")
-
- assert cloud.V1Flowserver._mock_call_args_list == [call(name="root.flow_b")]
-
- cloud.V1LightningappInstanceSpec._mock_call_args.assert_called_once(
- app_entrypoint_file=_FILE_PATH,
- enable_app_server=True,
- works=ANY,
- flow_servers=ANY,
- )
-
- cloud.CloudspaceIdRunsBody.assert_called_once()
-
-
-class HttpHeaderDict(dict):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.reason = kwargs["reason"]
- self.status = kwargs["status"]
- self.data = kwargs["data"]
-
- def getheaders(self):
- return {}
-
-
-class FakeLightningClientException(FakeLightningClient):
- def __init__(self, *args, message, **kwargs):
- super().__init__()
- self.message = message
-
- def cloud_space_service_list_cloud_spaces(self, *args, **kwargs):
- raise ApiException(
- http_resp=HttpHeaderDict(
- data=self.message,
- reason="",
- status=500,
- )
- )
-
-
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.runners.runtime_type.CloudRuntime", CloudRuntimePatch)
-@pytest.mark.parametrize(
- "message",
- [
- "Cannot create a new app, you have reached the maximum number (10) of apps. Either increase your quota or delete some of the existing apps" # noqa E501
- ],
-)
-def test_start_app_exception(message, monkeypatch, caplog):
- monkeypatch.setattr(cloud, "V1LightningappInstanceState", MagicMock())
- monkeypatch.setattr(cloud, "CloudspaceIdRunsBody", MagicMock())
- monkeypatch.setattr(cloud, "V1Flowserver", MagicMock())
- monkeypatch.setattr(cloud, "V1LightningappInstanceSpec", MagicMock())
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", MagicMock())
- monkeypatch.setattr(cloud, "logger", logging.getLogger())
-
- runner = CliRunner()
-
- fake_grid_rest_client = partial(FakeLightningClientException, message=message)
- with caplog.at_level(logging.ERROR), mock.patch(
- "lightning.app.runners.backends.cloud.LightningClient", fake_grid_rest_client
- ):
- result = runner.invoke(run_app, [_FILE_PATH, "--cloud", "--open-ui=False"], catch_exceptions=False)
- assert result.exit_code == 1
- assert caplog.messages == [message]
diff --git a/tests/tests_app/cli/test_cmd_apps.py b/tests/tests_app/cli/test_cmd_apps.py
deleted file mode 100644
index ca50d591e0dfa..0000000000000
--- a/tests/tests_app/cli/test_cmd_apps.py
+++ /dev/null
@@ -1,157 +0,0 @@
-from unittest import mock
-from unittest.mock import MagicMock
-
-import pytest as pytest
-from lightning.app.cli.cmd_apps import _AppList, _AppManager
-from lightning_cloud.openapi import (
- Externalv1LightningappInstance,
- V1LightningappInstanceSpec,
- V1LightningappInstanceState,
- V1LightningappInstanceStatus,
- V1LightningworkState,
- V1ListLightningappInstancesResponse,
- V1ListLightningworkResponse,
- V1ListMembershipsResponse,
- V1Membership,
-)
-from rich.text import Text
-
-
-@pytest.mark.parametrize(
- ("current_state", "desired_state", "expected"),
- [
- (
- V1LightningappInstanceStatus(phase=V1LightningappInstanceState.RUNNING),
- V1LightningappInstanceState.DELETED,
- Text("terminating"),
- ),
- (
- V1LightningappInstanceStatus(phase=V1LightningappInstanceState.STOPPED),
- V1LightningappInstanceState.RUNNING,
- Text("restarting"),
- ),
- (
- V1LightningappInstanceStatus(phase=V1LightningappInstanceState.PENDING),
- V1LightningappInstanceState.RUNNING,
- Text("restarting"),
- ),
- (
- V1LightningappInstanceStatus(phase=V1LightningappInstanceState.UNSPECIFIED, start_timestamp=None),
- V1LightningappInstanceState.RUNNING,
- Text("not yet started"),
- ),
- ],
-)
-def test_state_transitions(current_state, desired_state, expected):
- actual = _AppList._textualize_state_transitions(current_state=current_state, desired_state=desired_state)
- assert actual == expected
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch("lightning.app.utilities.network.LightningClient.lightningapp_instance_service_list_lightningapp_instances")
-@mock.patch("lightning.app.utilities.network.LightningClient.projects_service_list_memberships")
-def test_list_all_apps_paginated(list_memberships: mock.MagicMock, list_instances: mock.MagicMock):
- list_memberships.return_value = V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
- list_instances.side_effect = [
- V1ListLightningappInstancesResponse(
- lightningapps=[
- Externalv1LightningappInstance(
- name="test1",
- spec=V1LightningappInstanceSpec(desired_state=V1LightningappInstanceState.RUNNING),
- status=V1LightningappInstanceStatus(phase=V1LightningappInstanceState.RUNNING),
- )
- ],
- next_page_token="page-2",
- ),
- V1ListLightningappInstancesResponse(
- lightningapps=[
- Externalv1LightningappInstance(
- name="test2",
- spec=V1LightningappInstanceSpec(desired_state=V1LightningappInstanceState.STOPPED),
- status=V1LightningappInstanceStatus(phase=V1LightningappInstanceState.RUNNING),
- )
- ],
- ),
- ]
-
- cluster_manager = _AppManager()
- cluster_manager.list()
-
- list_memberships.assert_called_once()
- assert list_instances.mock_calls == [
- mock.call(project_id="default-project", limit=100, phase_in=[]),
- mock.call(project_id="default-project", page_token="page-2", limit=100, phase_in=[]),
- ]
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch("lightning.app.utilities.network.LightningClient.lightningapp_instance_service_list_lightningapp_instances")
-@mock.patch("lightning.app.utilities.network.LightningClient.projects_service_list_memberships")
-def test_list_all_apps(list_memberships: mock.MagicMock, list_instances: mock.MagicMock):
- list_memberships.return_value = V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
- list_instances.return_value = V1ListLightningappInstancesResponse(lightningapps=[])
-
- cluster_manager = _AppManager()
- cluster_manager.list()
-
- list_memberships.assert_called_once()
- list_instances.assert_called_once_with(project_id="default-project", limit=100, phase_in=[])
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch("lightning.app.utilities.network.LightningClient.lightningwork_service_list_lightningwork")
-@mock.patch("lightning.app.utilities.network.LightningClient.projects_service_list_memberships")
-def test_list_components(list_memberships: mock.MagicMock, list_components: mock.MagicMock):
- list_memberships.return_value = V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
- list_components.return_value = V1ListLightningworkResponse(lightningworks=[])
-
- cluster_manager = _AppManager()
- cluster_manager.list_components(app_id="cheese")
-
- list_memberships.assert_called_once()
- list_components.assert_called_once_with(project_id="default-project", app_id="cheese", phase_in=[])
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch("lightning.app.utilities.network.LightningClient.lightningwork_service_list_lightningwork")
-@mock.patch("lightning.app.utilities.network.LightningClient.projects_service_list_memberships")
-def test_list_components_with_phase(list_memberships: mock.MagicMock, list_components: mock.MagicMock):
- list_memberships.return_value = V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
- list_components.return_value = V1ListLightningworkResponse(lightningworks=[])
-
- cluster_manager = _AppManager()
- cluster_manager.list_components(app_id="cheese", phase_in=[V1LightningworkState.RUNNING])
-
- list_memberships.assert_called_once()
- list_components.assert_called_once_with(
- project_id="default-project", app_id="cheese", phase_in=[V1LightningworkState.RUNNING]
- )
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch("lightning.app.utilities.network.LightningClient.lightningapp_instance_service_list_lightningapp_instances")
-@mock.patch("lightning.app.utilities.network.LightningClient.projects_service_list_memberships")
-def test_list_apps_on_cluster(list_memberships: mock.MagicMock, list_instances: mock.MagicMock):
- list_memberships.return_value = V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
- list_instances.return_value = V1ListLightningappInstancesResponse(lightningapps=[])
-
- cluster_manager = _AppManager()
- cluster_manager.list()
-
- list_memberships.assert_called_once()
- list_instances.assert_called_once_with(project_id="default-project", limit=100, phase_in=[])
-
-
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-@mock.patch(
- "lightning.app.utilities.network.LightningClient.lightningapp_instance_service_delete_lightningapp_instance"
-)
-@mock.patch("lightning.app.cli.cmd_apps._get_project")
-def test_delete_app_on_cluster(get_project_mock: mock.MagicMock, delete_app_mock: mock.MagicMock):
- get_project_mock.return_value = V1Membership(project_id="default-project")
-
- cluster_manager = _AppManager()
- cluster_manager.delete(app_id="12345")
-
- delete_app_mock.assert_called()
- delete_app_mock.assert_called_once_with(project_id="default-project", id="12345")
diff --git a/tests/tests_app/cli/test_cmd_cli_delete.py b/tests/tests_app/cli/test_cmd_cli_delete.py
deleted file mode 100644
index 704a3d89fb481..0000000000000
--- a/tests/tests_app/cli/test_cmd_cli_delete.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import sys
-from unittest import mock
-
-import pytest
-from lightning.app.cli.lightning_cli_delete import _find_selected_app_instance_id
-from lightning_cloud.openapi import Externalv1LightningappInstance
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="currently not supported for windows.")
-@mock.patch("lightning_cloud.login.Auth.authenticate", mock.MagicMock())
-@mock.patch("lightning.app.cli.lightning_cli_delete._AppManager.list_apps")
-def test_app_find_selected_app_instance_id_when_app_name_exists(list_apps_mock: mock.MagicMock):
- list_apps_mock.return_value = [
- Externalv1LightningappInstance(name="app-name", id="app-id"),
- ]
- returned_app_instance_id = _find_selected_app_instance_id(app_name="app-name")
- assert returned_app_instance_id == "app-id"
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="currently not supported for windows.")
-@mock.patch("lightning_cloud.login.Auth.authenticate", mock.MagicMock())
-@mock.patch("lightning.app.cli.lightning_cli_delete._AppManager.list_apps")
-def test_app_find_selected_app_instance_id_when_app_id_exists(list_apps_mock: mock.MagicMock):
- list_apps_mock.return_value = [
- Externalv1LightningappInstance(name="app-name", id="app-id"),
- ]
- returned_app_instance_id = _find_selected_app_instance_id(app_name="app-id")
- assert returned_app_instance_id == "app-id"
diff --git a/tests/tests_app/cli/test_cmd_init.py b/tests/tests_app/cli/test_cmd_init.py
deleted file mode 100644
index c8a572107ba3c..0000000000000
--- a/tests/tests_app/cli/test_cmd_init.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import contextlib
-import os
-import re
-import shutil
-import subprocess
-
-import pytest
-from lightning.app.cli import cmd_init
-from lightning.app.utilities.imports import _IS_MACOS, _IS_WINDOWS
-
-
-def test_validate_init_name():
- # test that a good name works (mix chars)
- value = cmd_init._capture_valid_app_component_name("abc1-cde")
- assert value == "abc1-cde"
-
- # test that a good name works (letters only)
- value = cmd_init._capture_valid_app_component_name("abc-cde")
- assert value == "abc-cde"
-
- # assert bad input
- with pytest.raises(SystemExit) as e:
- value = cmd_init._capture_valid_app_component_name("abc-cde#")
-
- assert "Error: your Lightning app name" in str(e.value)
-
-
-@pytest.mark.skipif(_IS_WINDOWS or _IS_MACOS, reason="unsupported OS") # todo
-@pytest.mark.xfail(strict=False, reason="need app fast_dev_run to work via CLI")
-def test_make_app_template():
- template_name = "app-test-template"
- template_name_folder = re.sub("-", "_", template_name)
-
- # remove the template if there
- template_dir = os.path.join(os.getcwd(), template_name)
- with contextlib.suppress(Exception):
- shutil.rmtree(template_dir)
-
- # create template
- subprocess.check_output(f"lightning init app {template_name}", shell=True)
-
- # make sure app is not in the env
- env_output = subprocess.check_output("pip freeze", shell=True)
- assert template_name not in str(env_output)
-
- # install the app
- env_output = subprocess.check_output(
- f"cd {template_name} && pip install -r requirements.txt && pip install -e .", shell=True
- )
- env_output = subprocess.check_output("pip freeze", shell=True)
- assert template_name in str(env_output)
-
- app_dir = os.path.join(template_dir, f"{template_name_folder}/app.py")
- output = subprocess.check_output(f"lightning run app {app_dir} --fast_dev_run") # noqa
- # TODO: verify output
-
- # clean up the template dir
- with contextlib.suppress(Exception):
- shutil.rmtree(template_dir)
-
-
-@pytest.mark.xfail(strict=False, reason="need component fast_dev_run to work via CLI")
-def test_make_component_template():
- template_name = "component-test-template"
- template_name_folder = re.sub("-", "_", template_name)
-
- # remove the template if there
- template_dir = os.path.join(os.getcwd(), template_name)
- with contextlib.suppress(Exception):
- shutil.rmtree(template_dir)
-
- # create template
- subprocess.check_output(f"lightning init component {template_name}", shell=True)
-
- # make sure app is not in the env
- env_output = subprocess.check_output("pip freeze", shell=True)
- assert template_name not in str(env_output)
-
- # install the app
- env_output = subprocess.check_output(
- f"cd {template_name} && pip install -r requirements.txt && pip install -e .", shell=True
- )
- env_output = subprocess.check_output("pip freeze", shell=True)
- assert template_name in str(env_output)
-
- app_dir = os.path.join(template_dir, f"{template_name_folder}/app.py")
- output = subprocess.check_output(f"lightning run app {app_dir} --fast_dev_run") # noqa
- # TODO: verify output
-
- # clean up the template dir
- with contextlib.suppress(Exception):
- shutil.rmtree(template_dir)
diff --git a/tests/tests_app/cli/test_cmd_install.py b/tests/tests_app/cli/test_cmd_install.py
deleted file mode 100644
index 93df54cbc8389..0000000000000
--- a/tests/tests_app/cli/test_cmd_install.py
+++ /dev/null
@@ -1,384 +0,0 @@
-import os
-import subprocess
-from pathlib import Path
-from unittest import mock
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli import cmd_install, lightning_cli
-from lightning.app.testing.helpers import _RunIf
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-@mock.patch("lightning.app.cli.cmd_install.subprocess", mock.MagicMock())
-def test_valid_org_app_name():
- """Valid organization name."""
- runner = CliRunner()
-
- # assert a bad app name should fail
- fake_app = "fakeuser/impossible/name"
- result = runner.invoke(lightning_cli.cmd_install.install_app, [fake_app])
- assert "app name format must have organization/app-name" in result.output
-
- # assert a good name (but unavailable name) should work
- fake_app = "fakeuser/ALKKLJAUHREKJ21234KLAKJDLF"
- result = runner.invoke(lightning_cli.cmd_install.install_app, [fake_app])
- assert f"app: '{fake_app}' is not available on ⚡ Lightning AI ⚡" in result.output
- assert result.exit_code
-
- # assert a good (and availablea name) works
- # This should be an app that's always in the gallery
- real_app = "lightning/invideo"
- result = runner.invoke(lightning_cli.cmd_install.install_app, [real_app])
- assert "Press enter to continue:" in result.output
-
-
-@pytest.mark.xfail(strict=False, reason="need to figure out how to authorize git clone from the private repo")
-def test_valid_unpublished_app_name():
- runner = CliRunner()
-
- # assert warning of non official app given
- real_app = "https://github.com/Lightning-AI/install-app"
- with pytest.raises(subprocess.CalledProcessError, match="WARNING"):
- subprocess.check_output(f"lightning install app {real_app}", shell=True, stderr=subprocess.STDOUT)
-
- # assert aborted install
- result = runner.invoke(lightning_cli.cmd_install.install_app, [real_app], input="q")
- assert "Installation aborted!" in result.output
-
- # assert a bad app name should fail
- fake_app = "https://github.com/Lightning-AI/install-appdd"
- result = runner.invoke(lightning_cli.cmd_install.install_app, [fake_app, "--yes"])
- assert "Looks like the github url was not found" in result.output
-
- # assert a good (and availablea name) works
- result = runner.invoke(lightning_cli.cmd_install.install_app, [real_app])
- assert "Press enter to continue:" in result.output
-
-
-@pytest.mark.xfail(strict=False, reason="need to figure out how to authorize git clone from the private repo")
-def test_app_install(tmpdir, monkeypatch):
- """Tests unpublished app install."""
- monkeypatch.chdir(tmpdir)
-
- real_app = "https://github.com/Lightning-AI/install-app"
- test_app_pip_name = "install-app"
-
- # install app and verify it's in the env
- subprocess.check_output(f"lightning install app {real_app} --yes", shell=True)
- new_env_output = subprocess.check_output("pip freeze", shell=True)
- assert test_app_pip_name in str(new_env_output), f"{test_app_pip_name} should be in the env"
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-@mock.patch("lightning.app.cli.cmd_install.subprocess", mock.MagicMock())
-def test_valid_org_component_name():
- runner = CliRunner()
-
- # assert a bad name should fail
- fake_component = "fakeuser/impossible/name"
- result = runner.invoke(lightning_cli.cmd_install.install_component, [fake_component])
- assert "component name format must have organization/component-name" in result.output
-
- # assert a good name (but unavailable name) should work
- fake_component = "fakeuser/ALKKLJAUHREKJ21234KLAKJDLF"
- result = runner.invoke(lightning_cli.cmd_install.install_component, [fake_component])
- assert f"component: '{fake_component}' is not available on ⚡ Lightning AI ⚡" in result.output
-
- # assert a good (and availablea name) works
- fake_component = "lightning/lit-slack-messenger"
- result = runner.invoke(lightning_cli.cmd_install.install_component, [fake_component])
- assert "Press enter to continue:" in result.output
-
-
-def test_unpublished_component_url_parsing():
- runner = CliRunner()
-
- # assert a bad name should fail (no git@)
- fake_component = "https://github.com/Lightning-AI/LAI-slack-messenger"
- result = runner.invoke(lightning_cli.cmd_install.install_component, [fake_component])
- assert "Error, your github url must be in the following format" in result.output
-
- # assert a good (and availablea name) works
- sha = "14f333456ffb6758bd19458e6fa0bf12cf5575e1"
- real_component = f"git+https://github.com/Lightning-AI/LAI-slack-messenger.git@{sha}"
- result = runner.invoke(lightning_cli.cmd_install.install_component, [real_component])
- assert "Press enter to continue:" in result.output
-
-
-@pytest.mark.xfail(strict=False, reason="need to figure out how to authorize pip install from the private repo")
-@pytest.mark.parametrize(
- ("real_component", "test_component_pip_name"),
- [
- ("lightning/lit-slack-messenger", "lit-slack"),
- (
- "git+https://github.com/Lightning-AI/LAI-slack-messenger.git@14f333456ffb6758bd19458e6fa0bf12cf5575e1",
- "lit-slack",
- ),
- ],
-)
-def test_component_install(real_component, test_component_pip_name):
- """Tests both published and unpublished component installs."""
- # uninstall component just in case and verify it's not in the pip output
- env_output = subprocess.check_output(f"pip uninstall {test_component_pip_name} --yes && pip freeze", shell=True)
- assert test_component_pip_name not in str(env_output), f"{test_component_pip_name} should not be in the env"
-
- # install component and verify it's in the env
- new_env_output = subprocess.check_output(
- f"lightning install component {real_component} --yes && pip freeze", shell=True
- )
- assert test_component_pip_name in str(new_env_output), f"{test_component_pip_name} should be in the env"
-
- # clean up for test
- subprocess.run(f"pip uninstall {test_component_pip_name} --yes", shell=True)
- env_output = subprocess.check_output("pip freeze", shell=True)
- assert test_component_pip_name not in str(
- env_output
- ), f"{test_component_pip_name} should not be in the env after cleanup"
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-def test_prompt_actions():
- # TODO: each of these installs must check that a package is installed in the environment correctly
- app_to_use = "lightning/invideo"
-
- runner = CliRunner()
-
- # assert that the user can cancel the command with any letter other than y
- result = runner.invoke(lightning_cli.cmd_install.install_app, [app_to_use], input="b")
- assert "Installation aborted!" in result.output
-
- # assert that the install happens with --yes
- # result = runner.invoke(lightning_cli.cmd_install.install_app, [app_to_use, "--yes"])
- # assert result.exit_code == 0
-
- # assert that the install happens with y
- # result = runner.invoke(lightning_cli.cmd_install.install_app, [app_to_use], input='y')
- # assert result.exit_code == 0
-
- # # assert that the install happens with yes
- # result = runner.invoke(lightning_cli.cmd_install.install_app, [app_to_use], input='yes')
- # assert result.exit_code == 0
-
- # assert that the install happens with pressing enter
- # result = runner.invoke(lightning_cli.cmd_install.install_app, [app_to_use])
-
- # TODO: how to check the output when the user types ctrl+c?
- # result = runner.invoke(lightning_cli.cmd_install.install_app, [app_to_use], input='')
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-@mock.patch("lightning.app.cli.cmd_install.subprocess", mock.MagicMock())
-def test_version_arg_component(tmpdir, monkeypatch):
- monkeypatch.chdir(tmpdir)
- runner = CliRunner()
-
- # Version does not exist
- component_name = "lightning/lit-slack-messenger"
- version_arg = "NOT-EXIST"
- result = runner.invoke(lightning_cli.cmd_install.install_component, [component_name, f"--version={version_arg}"])
- assert f"component: 'Version {version_arg} for {component_name}' is not" in str(result.exception)
- assert result.exit_code == 1
-
- # Version exists
- # This somwehow fail in test but not when you actually run it
- version_arg = "0.0.1"
- runner = CliRunner()
- result = runner.invoke(
- lightning_cli.cmd_install.install_component, [component_name, f"--version={version_arg}", "--yes"]
- )
- assert result.exit_code == 0
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-@mock.patch("lightning.app.cli.cmd_install.subprocess", mock.MagicMock())
-@mock.patch("lightning.app.cli.cmd_install.os.chdir", mock.MagicMock())
-def test_version_arg_app(tmpdir):
- # Version does not exist
- app_name = "lightning/invideo"
- version_arg = "NOT-EXIST"
- runner = CliRunner()
- result = runner.invoke(lightning_cli.cmd_install.install_app, [app_name, f"--version={version_arg}"])
- assert f"app: 'Version {version_arg} for {app_name}' is not" in str(result.exception)
- assert result.exit_code == 1
-
- # Version exists
- version_arg = "0.0.2"
- runner = CliRunner()
- result = runner.invoke(lightning_cli.cmd_install.install_app, [app_name, f"--version={version_arg}", "--yes"])
- assert result.exit_code == 0
-
-
-@mock.patch("lightning.app.cli.cmd_install.subprocess", mock.MagicMock())
-@mock.patch("lightning.app.cli.cmd_install.os.chdir", mock.MagicMock())
-@mock.patch("lightning.app.cli.cmd_install._show_install_app_prompt")
-def test_install_resolve_latest_version(mock_show_install_app_prompt, tmpdir):
- app_name = "lightning/invideo"
- runner = CliRunner()
- with mock.patch("lightning.app.cli.cmd_install.requests.get") as get_api_mock:
- get_api_mock.return_value.json.return_value = {
- "apps": [
- {
- "canDownloadSourceCode": True,
- "version": "0.0.2",
- "name": "lightning/invideo",
- },
- {
- "canDownloadSourceCode": True,
- "version": "0.0.4",
- "name": "lightning/invideo",
- },
- {
- "canDownloadSourceCode": True,
- "version": "0.0.5",
- "name": "another_app",
- },
- ]
- }
- runner.invoke(
- lightning_cli.cmd_install.install_app, [app_name, "--yes"]
- ) # no version specified so latest is installed
- assert mock_show_install_app_prompt.called
- assert mock_show_install_app_prompt.call_args[0][0]["version"] == "0.0.4"
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-def test_proper_url_parsing():
- name = "lightning/invideo"
-
- # make sure org/app-name name is correct
- org, app = cmd_install._validate_name(name, resource_type="app", example="lightning/lit-slack-component")
- assert org == "lightning"
- assert app == "invideo"
-
- # resolve registry (orgs can have a private registry through their environment variables)
- registry_url = cmd_install._resolve_app_registry()
- assert registry_url == "https://lightning.ai/v1/apps"
-
- # load the component resource
- component_entry = cmd_install._resolve_resource(registry_url, name=name, version_arg="latest", resource_type="app")
-
- source_url, git_url, folder_name, git_sha = cmd_install._show_install_app_prompt(
- component_entry, app, org, True, resource_type="app"
- )
- assert folder_name == "LAI-InVideo-search-App"
- # FixMe: this need to be updated after release with updated org rename
- assert source_url == "https://github.com/Lightning-AI/LAI-InVideo-search-App"
- assert "#ref" not in git_url
- assert git_sha
-
-
-@_RunIf(skip_windows=True)
-def test_install_app_shows_error(tmpdir):
- app_folder_dir = Path(tmpdir / "some_random_directory").absolute()
- app_folder_dir.mkdir()
-
- with pytest.raises(SystemExit, match=f"Folder {str(app_folder_dir)} exists, please delete it and try again."):
- cmd_install._install_app_from_source(
- source_url=mock.ANY, git_url=mock.ANY, folder_name=str(app_folder_dir), overwrite=False
- )
-
-
-# def test_env_creation(tmpdir):
-# cwd = os.getcwd()
-# os.chdir(tmpdir)
-
-# # install app
-# cmd_install.app("lightning/install-app", True, cwd=tmpdir)
-
-# # assert app folder is installed with venv
-# assert "python" in set(os.listdir(os.path.join(tmpdir, "install-app/bin")))
-
-# # assert the deps are in the env
-# env_output = subprocess.check_output("source bin/activate && pip freeze", shell=True)
-# non_env_output = subprocess.check_output("pip freeze", shell=True)
-
-# # assert envs are not the same
-# assert env_output != non_env_output
-
-# # assert the reqs are in the env created and NOT in the non env
-# reqs = open(os.path.join(tmpdir, "install-app/requirements.txt")).read()
-# assert reqs in str(env_output) and reqs not in str(non_env_output)
-
-# # setup.py installs numpy
-# assert "numpy" in str(env_output)
-
-# # run the python script to make sure the file works (in a folder)
-# app_file = os.path.join(tmpdir, "install-app/src/app.py")
-# app_output = subprocess.check_output(f"source bin/activate && python {app_file}", shell=True)
-# assert "b'printed a\\ndeps loaded\\n'" == str(app_output)
-
-# # run the python script to make sure the file works (in root)
-# app_file = os.path.join(tmpdir, "install-app/app_b.py")
-# app_output = subprocess.check_output(f"source bin/activate && python {app_file}", shell=True)
-# assert "b'printed a\\n'" == str(app_output)
-
-# # reset dir
-# os.chdir(cwd)
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-def test_app_and_component_gallery_app(monkeypatch):
- monkeypatch.setattr(cmd_install, "_install_app_from_source", mock.MagicMock())
- path = cmd_install.gallery_apps_and_components("lightning/flashy", True, "latest")
- assert path == os.path.join(os.getcwd(), "app.py")
-
-
-@pytest.mark.xfail(strict=False, reason="lightning app cli was deprecated")
-def test_app_and_component_gallery_component(monkeypatch):
- monkeypatch.setattr(cmd_install, "_install_app_from_source", mock.MagicMock())
- path = cmd_install.gallery_apps_and_components("lightning/lit-jupyter", True, "latest")
- assert path == os.path.join(os.getcwd(), "app.py")
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_APP_REGISTRY": "https://TODO/other_non_PL_registry"})
-def test_private_app_registry():
- registry = cmd_install._resolve_app_registry()
- assert registry == "https://TODO/other_non_PL_registry"
-
-
-def test_public_app_registry():
- registry = cmd_install._resolve_app_registry()
- assert registry == "https://lightning.ai/v1/apps"
-
-
-def test_public_component_registry():
- registry = cmd_install._resolve_component_registry()
- assert registry == "https://lightning.ai/v1/components"
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_COMPONENT_REGISTRY": "https://TODO/other_non_PL_registry"})
-def test_private_component_registry():
- registry = cmd_install._resolve_component_registry()
- assert registry == "https://TODO/other_non_PL_registry"
-
-
-@mock.patch("lightning.app.cli.cmd_install.subprocess")
-@mock.patch("lightning.app.cli.cmd_install.os.chdir", mock.MagicMock())
-@pytest.mark.parametrize(
- ("source_url", "git_url", "git_sha"),
- [
- (
- "https://github.com/PyTorchLightning/lightning-quick-start",
- "https://@github.com/PyTorchLightning/lightning-quick-start",
- None,
- ),
- (
- "https://github.com/PyTorchLightning/lightning-quick-start",
- "https://@github.com/PyTorchLightning/lightning-quick-start",
- "git_sha",
- ),
- ],
-)
-def test_install_app_process(subprocess_mock, source_url, git_url, git_sha, tmpdir):
- app_folder_dir = Path(tmpdir / "some_random_directory").absolute()
- app_folder_dir.mkdir()
-
- cmd_install._install_app_from_source(
- source_url, git_url, folder_name=str(app_folder_dir), overwrite=True, git_sha=git_sha
- )
- assert subprocess_mock.check_output.call_args_list[0].args == (["git", "clone", git_url],)
- if git_sha:
- assert subprocess_mock.check_output.call_args_list[1].args == (["git", "checkout", git_sha],)
- assert subprocess_mock.call.call_args_list[0].args == ("pip install -r requirements.txt",)
- assert subprocess_mock.call.call_args_list[1].args == ("pip install -e .",)
diff --git a/tests/tests_app/cli/test_cmd_launch.py b/tests/tests_app/cli/test_cmd_launch.py
deleted file mode 100644
index 7ce9ea7e5b88c..0000000000000
--- a/tests/tests_app/cli/test_cmd_launch.py
+++ /dev/null
@@ -1,330 +0,0 @@
-import os
-import signal
-import time
-from functools import partial
-from multiprocessing import Process
-from pathlib import Path
-from unittest import mock
-from unittest.mock import ANY, MagicMock, Mock
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli_launch import run_flow, run_flow_and_servers, run_frontend, run_server
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.frontend.web import StaticWebFrontend
-from lightning.app.launcher import launcher
-from lightning.app.runners.runtime import load_app_from_file
-from lightning.app.testing.helpers import EmptyWork, _RunIf
-from lightning.app.utilities.app_commands import run_app_commands
-from lightning.app.utilities.network import find_free_network_port
-
-from tests_app import _PROJECT_ROOT
-
-_FILE_PATH = os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py")
-
-
-def test_run_frontend(monkeypatch):
- """Test that the CLI can be used to start the frontend server of a particular LightningFlow using the cloud
- dispatcher.
-
- This CLI call is made by Lightning AI and is not meant to be invoked by the user directly.
-
- """
- runner = CliRunner()
-
- port = find_free_network_port()
-
- start_server_mock = Mock()
- monkeypatch.setattr(StaticWebFrontend, "start_server", start_server_mock)
-
- result = runner.invoke(
- run_frontend,
- [
- str(Path(__file__).parent / "launch_data" / "app_v0" / "app.py"),
- "--flow-name",
- "root.aas",
- "--host",
- "localhost",
- "--port",
- port,
- ],
- )
- assert result.exit_code == 0
- start_server_mock.assert_called_once()
- start_server_mock.assert_called_with("localhost", port)
-
-
-class MockRedisQueue:
- _MOCKS = {}
-
- def __init__(self, name: str, default_timeout: float):
- self.name = name
- self.default_timeout = default_timeout
- self.queue = [None] # adding a dummy element.
-
- self._MOCKS[name] = MagicMock()
-
- def put(self, item):
- self._MOCKS[self.name].put(item)
- self.queue.put(item)
-
- def get(self, timeout: int = None):
- self._MOCKS[self.name].get(timeout=timeout)
- return self.queue.pop(0)
-
- @property
- def is_running(self):
- self._MOCKS[self.name].is_running()
- return True
-
-
-@mock.patch("lightning.app.core.queues.RedisQueue", MockRedisQueue)
-@mock.patch("lightning.app.launcher.launcher.check_if_redis_running", MagicMock(return_value=True))
-@mock.patch("lightning.app.launcher.launcher.start_server")
-def test_run_server(start_server_mock):
- runner = CliRunner()
- result = runner.invoke(
- run_server,
- [
- _FILE_PATH,
- "--queue-id",
- "1",
- "--host",
- "http://127.0.0.1:7501/view",
- "--port",
- "6000",
- ],
- catch_exceptions=False,
- )
- assert result.exit_code == 0
- start_server_mock.assert_called_once_with(
- host="http://127.0.0.1:7501/view",
- port=6000,
- api_publish_state_queue=ANY,
- api_delta_queue=ANY,
- api_response_queue=ANY,
- spec=ANY,
- apis=ANY,
- )
- kwargs = start_server_mock._mock_call_args.kwargs
- assert isinstance(kwargs["api_publish_state_queue"], MockRedisQueue)
- assert kwargs["api_publish_state_queue"].name.startswith("1")
- assert isinstance(kwargs["api_delta_queue"], MockRedisQueue)
- assert kwargs["api_delta_queue"].name.startswith("1")
-
-
-def mock_server(should_catch=False, sleep=1000):
- if should_catch:
-
- def _sigterm_handler(*_):
- time.sleep(100)
-
- signal.signal(signal.SIGTERM, _sigterm_handler)
-
- time.sleep(sleep)
-
-
-def run_forever_process():
- while True:
- time.sleep(1)
-
-
-def run_for_2_seconds_and_raise():
- time.sleep(2)
- raise RuntimeError("existing")
-
-
-def exit_successfully_immediately():
- return
-
-
-def start_servers(should_catch=False, sleep=1000):
- processes = [
- (
- "p1",
- launcher.start_server_in_process(target=partial(mock_server, should_catch=should_catch, sleep=sleep)),
- ),
- (
- "p2",
- launcher.start_server_in_process(target=partial(mock_server, sleep=sleep)),
- ),
- (
- "p3",
- launcher.start_server_in_process(target=partial(mock_server, sleep=sleep)),
- ),
- ]
-
- launcher.manage_server_processes(processes)
-
-
-@_RunIf(skip_windows=True)
-def test_manage_server_processes():
- p = Process(target=partial(start_servers, sleep=0.5))
- p.start()
- p.join()
-
- assert p.exitcode == 0
-
- p = Process(target=start_servers)
- p.start()
- p.join(0.5)
- p.terminate()
- p.join()
-
- assert p.exitcode in [-15, 0]
-
- p = Process(target=partial(start_servers, should_catch=True))
- p.start()
- p.join(0.5)
- p.terminate()
- p.join()
-
- assert p.exitcode in [-15, 1]
-
-
-def start_processes(**functions):
- processes = []
- for name, fn in functions.items():
- processes.append((name, launcher.start_server_in_process(fn)))
- launcher.manage_server_processes(processes)
-
-
-@pytest.mark.skipif(True, reason="flaky")
-@_RunIf(skip_windows=True)
-def test_manage_server_processes_one_process_gets_killed(capfd):
- functions = {"p1": run_forever_process, "p2": run_for_2_seconds_and_raise}
- p = Process(target=start_processes, kwargs=functions)
- p.start()
-
- for _ in range(40):
- time.sleep(1)
- if p.exitcode is not None:
- break
- assert p.exitcode == 1
- captured = capfd.readouterr()
- assert (
- "Found dead components with non-zero exit codes, exiting execution!!! Components: \n"
- "| Name | Exit Code |\n|------|-----------|\n| p2 | 1 |\n" in captured.out
- )
-
-
-@_RunIf(skip_windows=True, skip_mac_os=True)
-def test_manage_server_processes_all_processes_exits_with_zero_exitcode(capfd):
- functions = {
- "p1": exit_successfully_immediately,
- "p2": exit_successfully_immediately,
- }
- p = Process(target=start_processes, kwargs=functions)
- p.start()
-
- for _ in range(40):
- time.sleep(1)
- if p.exitcode is not None:
- break
- assert p.exitcode == 0
- captured = capfd.readouterr()
- assert "All the components are inactive with exitcode 0. Exiting execution!!!" in captured.out
-
-
-@mock.patch("lightning.app.launcher.launcher.StorageOrchestrator", MagicMock())
-@mock.patch("lightning.app.core.queues.RedisQueue", MockRedisQueue)
-@mock.patch("lightning.app.launcher.launcher.manage_server_processes", Mock())
-def test_run_flow_and_servers(monkeypatch):
- runner = CliRunner()
-
- start_server_mock = Mock()
- monkeypatch.setattr(launcher, "start_server_in_process", start_server_mock)
-
- runner.invoke(
- run_flow_and_servers,
- [
- str(Path(__file__).parent / "launch_data" / "app_v0" / "app.py"),
- "--base-url",
- "https://some.url",
- "--queue-id",
- "1",
- "--host",
- "http://127.0.0.1:7501/view",
- "--port",
- 6000,
- "--flow-port",
- "root.aas",
- 6001,
- "--flow-port",
- "root.bbs",
- 6002,
- ],
- catch_exceptions=False,
- )
-
- start_server_mock.assert_called()
- assert start_server_mock.call_count == 4
-
-
-@mock.patch("lightning.app.core.queues.RedisQueue", MockRedisQueue)
-@mock.patch("lightning.app.launcher.launcher.WorkRunner")
-def test_run_work(mock_work_runner, monkeypatch):
- run_app_commands(_FILE_PATH)
- app = load_app_from_file(_FILE_PATH)
- names = [w.name for w in app.works]
-
- mocked_queue = MagicMock()
- mocked_queue.get.return_value = EmptyWork()
- monkeypatch.setattr(
- QueuingSystem,
- "get_work_queue",
- MagicMock(return_value=mocked_queue),
- )
-
- assert names == [
- "root.flow_a_1.work_a",
- "root.flow_a_2.work_a",
- "root.flow_b.work_b",
- ]
-
- for name in names:
- launcher.run_lightning_work(
- file=_FILE_PATH,
- work_name=name,
- queue_id="1",
- )
- kwargs = mock_work_runner._mock_call_args.kwargs
- assert isinstance(kwargs["work"], EmptyWork)
- assert kwargs["work_name"] == name
- assert isinstance(kwargs["caller_queue"], MockRedisQueue)
- assert kwargs["caller_queue"].name.startswith("1")
- assert isinstance(kwargs["delta_queue"], MockRedisQueue)
- assert kwargs["delta_queue"].name.startswith("1")
- assert isinstance(kwargs["readiness_queue"], MockRedisQueue)
- assert kwargs["readiness_queue"].name.startswith("1")
- assert isinstance(kwargs["error_queue"], MockRedisQueue)
- assert kwargs["error_queue"].name.startswith("1")
- assert isinstance(kwargs["request_queue"], MockRedisQueue)
- assert kwargs["request_queue"].name.startswith("1")
- assert isinstance(kwargs["response_queue"], MockRedisQueue)
- assert kwargs["response_queue"].name.startswith("1")
- assert isinstance(kwargs["copy_request_queue"], MockRedisQueue)
- assert kwargs["copy_request_queue"].name.startswith("1")
- assert isinstance(kwargs["copy_response_queue"], MockRedisQueue)
- assert kwargs["copy_response_queue"].name.startswith("1")
-
- MockRedisQueue._MOCKS["healthz"].is_running.assert_called()
-
-
-@mock.patch("lightning.app.core.queues.QueuingSystem", MagicMock())
-@mock.patch("lightning.app.launcher.launcher.StorageOrchestrator", MagicMock())
-@mock.patch("lightning.app.LightningApp._run")
-@mock.patch("lightning.app.launcher.launcher.CloudBackend")
-def test_run_flow(mock_cloud_backend, mock_lightning_app_run):
- runner = CliRunner()
-
- base_url = "https://lightning.ai/me/apps"
-
- result = runner.invoke(
- run_flow,
- [_FILE_PATH, "--queue-id=1", f"--base-url={base_url}"],
- catch_exceptions=False,
- )
- assert result.exit_code == 0
- mock_lightning_app_run.assert_called_once()
- assert len(mock_cloud_backend._mock_mock_calls) == 13
diff --git a/tests/tests_app/cli/test_cmd_pl_init.py b/tests/tests_app/cli/test_cmd_pl_init.py
deleted file mode 100644
index 192811a56f5a4..0000000000000
--- a/tests/tests_app/cli/test_cmd_pl_init.py
+++ /dev/null
@@ -1,125 +0,0 @@
-import os
-import sys
-from unittest import mock
-from unittest.mock import Mock
-
-import pytest
-from click.testing import CliRunner
-from lightning.app.cli import lightning_cli
-from lightning.app.cli.cmd_pl_init import _can_encode_icon, download_frontend, pl_app
-
-
-def test_pl_app_input_paths_do_not_exist(tmp_path):
- """Test that the CLI prints an error message if the code directory or the script path does not exist."""
- runner = CliRunner()
-
- source_dir = tmp_path / "code"
- script_file = tmp_path / "code" / "script.py"
-
- result = runner.invoke(lightning_cli.init_pl_app, (str(source_dir), str(script_file)))
- assert result.exit_code == 1
- assert "The given source directory does not exist:" in result.output
-
- source_dir.mkdir(parents=True)
-
- result = runner.invoke(lightning_cli.init_pl_app, (str(source_dir), str(script_file)))
- assert result.exit_code == 1
- assert "The given script path does not exist:" in result.output
-
- script_file_as_folder = tmp_path / "code" / "folder"
- script_file_as_folder.mkdir(parents=True)
- result = runner.invoke(lightning_cli.init_pl_app, (str(source_dir), str(script_file_as_folder)))
- assert result.exit_code == 1
- assert "The given script path must be a file, you passed:" in result.output
-
-
-def test_pl_app_script_path_not_subpath(tmp_path):
- """Test that the CLI prints an error message if the provided script path is not a subpath of the source dir."""
- runner = CliRunner()
-
- source_dir = tmp_path / "code"
- script_file = tmp_path / "not_code" / "script.py"
-
- source_dir.mkdir(parents=True)
- script_file.parent.mkdir(parents=True)
- script_file.touch()
-
- result = runner.invoke(lightning_cli.init_pl_app, (str(source_dir), str(script_file)), catch_exceptions=False)
- assert result.exit_code == 1
- assert "The given script path must be a subpath of the source directory." in result.output
-
-
-def test_pl_app_destination_app_already_exists(tmp_path, monkeypatch):
- """Test that the CLI prints an error message if an app with the same name already exists."""
- runner = CliRunner()
- monkeypatch.chdir(tmp_path)
-
- source_dir = tmp_path / "code"
- script_file = source_dir / "script.py"
- source_dir.mkdir(parents=True)
- script_file.parent.mkdir(parents=True, exist_ok=True)
- script_file.touch()
-
- # monkeypatch.chdir(tmp_path)
- app_folder = tmp_path / "existing-app"
- app_folder.mkdir(parents=True)
-
- result = runner.invoke(lightning_cli.init_pl_app, (str(source_dir), str(script_file), "--name", "existing-app"))
- assert result.exit_code == 1
- assert "There is already an app with the name existing-app in the current working directory" in result.output
-
-
-def test_pl_app_incorrect_number_of_arguments(tmp_path):
- """Test that the CLI prints an error message if more than two input arguments for the source are provided."""
- runner = CliRunner()
- result = runner.invoke(lightning_cli.init_pl_app, ("one", "two", "three"))
- assert result.exit_code == 1
- assert "Incorrect number of arguments. You passed (one, two, three) but only either one argument" in result.output
-
-
-def test_pl_app_download_frontend(tmp_path):
- build_dir = tmp_path / "app" / "ui" / "build"
- download_frontend(build_dir)
- contents = os.listdir(build_dir)
- assert "index.html" in contents
- assert "static" in contents
-
-
-def test_pl_app_encode_icon(monkeypatch):
- stdout_mock = Mock(wraps=sys.stdout)
- monkeypatch.setattr(sys, "stdout", stdout_mock)
-
- stdout_mock.encoding = "utf-8"
- assert _can_encode_icon("📂")
- assert _can_encode_icon("📄")
-
- stdout_mock.encoding = "ascii"
- assert not _can_encode_icon("📂")
- assert not _can_encode_icon("📄")
-
-
-@pytest.mark.parametrize(
- ("cwd", "source_dir", "script_path"),
- [
- ("./", "./", "train.py"),
- ("./", "./code", "./code/train.py"),
- ],
-)
-@mock.patch("lightning.app.cli.cmd_pl_init.project_file_from_template")
-@mock.patch("lightning.app.cli.cmd_pl_init.download_frontend")
-def test_pl_app_relative_paths(_, __, cwd, source_dir, script_path, tmp_path, monkeypatch):
- source_dir = tmp_path / source_dir
- source_dir.mkdir(parents=True, exist_ok=True)
- script_path = tmp_path / script_path
- script_path.parent.mkdir(parents=True, exist_ok=True)
- script_path.touch()
- cwd = tmp_path / cwd
- monkeypatch.chdir(cwd)
-
- pl_app(source_dir=str(source_dir), script_path=str(script_path), name="app-name", overwrite=False)
- assert (cwd / "app-name").is_dir()
-
- expected_source_files = set(os.listdir(source_dir))
- if cwd == source_dir:
- expected_source_files.remove("app-name")
- assert set(os.listdir(cwd / "app-name" / "source")) == expected_source_files
diff --git a/tests/tests_app/cli/test_cmd_react_ui_init.py b/tests/tests_app/cli/test_cmd_react_ui_init.py
deleted file mode 100644
index 0f28420e291ba..0000000000000
--- a/tests/tests_app/cli/test_cmd_react_ui_init.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import os
-
-import lightning.app as la
-import pytest
-from lightning.app.cli import cmd_init, cmd_react_ui_init
-from lightning.app.testing.helpers import _RunIf
-
-
-@pytest.mark.skipif(os.getenv("GITHUB_ACTIONS") is None, reason="not running in GH actions.")
-@pytest.mark.xfail(strict=False, reason="need to figure out how to mock not having npm")
-def test_missing_npm():
- with pytest.raises(SystemExit, match="This machine is missing 'npm'"):
- cmd_react_ui_init._check_react_prerequisites()
-
-
-@pytest.mark.skipif(os.getenv("GITHUB_ACTIONS") is None, reason="not running in GH actions.")
-@pytest.mark.xfail(strict=False, reason="need to figure out how to mock not having node")
-def test_missing_nodejs():
- with pytest.raises(SystemExit, match="This machine is missing 'node'"):
- cmd_react_ui_init._check_react_prerequisites()
-
-
-@pytest.mark.skipif(os.getenv("GITHUB_ACTIONS") is None, reason="not running in GH actions")
-@pytest.mark.xfail(strict=False, reason="need to figure out how to mock not having yarn")
-def test_missing_yarn():
- with pytest.raises(SystemExit, match="This machine is missing 'yarn'"):
- cmd_react_ui_init._check_react_prerequisites()
-
-
-@_RunIf(skip_windows=True)
-def test_copy_and_setup_react_ui(tmpdir):
- dest_dir = os.path.join(tmpdir, "react-ui")
- os.system(f"lightning_app init react-ui --dest_dir={dest_dir}")
-
- # make sure package is minimal
- files = sorted(f for f in os.listdir(dest_dir) if f != "__pycache__")
- assert len(files) == 3, "should only be 3 objects: readme.md, example_app.py and ui dir"
-
- # make sure index.html has the vite app placeholder
- with open(dest_dir + "/ui/dist/index.html") as fo:
- index_content = fo.read()
- assert "Vite App " in index_content
-
- # read the compiled js file
- js_file = [x for x in os.listdir(os.path.join(dest_dir, "ui", "dist", "assets")) if ".js" in x]
- js_file = os.path.join(dest_dir, f"ui/dist/assets/{js_file[0]}")
- with open(js_file) as fo:
- index_content = fo.read()
-
- # if this is in the compiled file, the compilation worked and the app will work
- assert "Total number of prints in your terminal:" in index_content, "react app was not compiled properly"
- assert "LightningState.subscribe" in index_content, "react app was not compiled properly"
-
-
-@pytest.mark.skipif(os.getenv("GITHUB_ACTIONS") is None, reason="not running in GH actions")
-def test_correct_num_react_template_files():
- template_dir = os.path.join(la.__path__[0], "cli/react-ui-template")
- files = cmd_init._ls_recursively(template_dir)
- # TODO: remove lock file!!!
- assert len(files) == 16, "react-ui template files must be minimal... do not add nice to haves"
diff --git a/tests/tests_app/cli/test_cmd_show_logs.py b/tests/tests_app/cli/test_cmd_show_logs.py
deleted file mode 100644
index 80d30d5cd12a2..0000000000000
--- a/tests/tests_app/cli/test_cmd_show_logs.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from unittest import mock
-
-from click.testing import CliRunner
-from lightning.app.cli.lightning_cli import show
-
-
-@mock.patch("lightning.app.cli.commands.logs.LightningClient")
-@mock.patch("lightning.app.cli.commands.logs._get_project")
-def test_show_logs_errors(_, client):
- """Test that the CLI prints the errors for the show logs command."""
- runner = CliRunner()
-
- # Response prep
- app = mock.MagicMock()
- app.name = "My-FakeApp"
- app.display_name = "My_FakeApp"
- work = mock.MagicMock()
- work.name = "MyFakeWork"
- flow = mock.MagicMock()
- flow.name = "MyFakeFlow"
-
- # No apps ever run
- apps = {}
- client.return_value.lightningapp_instance_service_list_lightningapp_instances.return_value.lightningapps = apps
-
- result = runner.invoke(show.commands["logs"], ["NonExistentApp"])
-
- assert result.exit_code == 1
- assert "Error: You don't have any application in the cloud" in result.output
-
- # App not specified
- apps = {app}
- client.return_value.lightningapp_instance_service_list_lightningapp_instances.return_value.lightningapps = apps
-
- result = runner.invoke(show.commands["logs"])
-
- assert result.exit_code == 1
- assert "Please select one of the following: [My_FakeApp]" in str(result.output)
-
- # App does not exit
- apps = {app}
- client.return_value.lightningapp_instance_service_list_lightningapp_instances.return_value.lightningapps = apps
-
- result = runner.invoke(show.commands["logs"], ["ThisAppDoesNotExist"])
-
- assert result.exit_code == 1
- assert "The Lightning App 'ThisAppDoesNotExist' does not exist." in str(result.output)
-
- # Component does not exist
- apps = {app}
- works = {work}
- flows = {flow}
- client.return_value.lightningapp_instance_service_list_lightningapp_instances.return_value.lightningapps = apps
- client.return_value.lightningwork_service_list_lightningwork.return_value.lightningworks = works
- app.spec.flow_servers = flows
-
- result = runner.invoke(show.commands["logs"], ["My_FakeApp", "NonExistentComponent"])
-
- assert result.exit_code == 1
- assert "Component 'root.NonExistentComponent' does not exist in app My_FakeApp." in result.output
diff --git a/tests/tests_app/cli/test_connect.py b/tests/tests_app/cli/test_connect.py
deleted file mode 100644
index 458238a7826d5..0000000000000
--- a/tests/tests_app/cli/test_connect.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import json
-import os
-from unittest.mock import MagicMock
-
-import click
-import psutil
-import pytest
-from lightning.app import _PROJECT_ROOT
-from lightning.app.cli.connect.app import (
- _list_app_commands,
- _resolve_command_path,
- _retrieve_connection_to_an_app,
- connect_app,
- disconnect_app,
-)
-from lightning.app.utilities import cli_helpers
-from lightning.app.utilities.commands import base
-
-
-def monkeypatch_connection(monkeypatch, tmpdir, ppid):
- connection_path = os.path.join(tmpdir, ppid)
- monkeypatch.setattr("lightning.app.cli.connect.app._clean_lightning_connection", MagicMock())
- monkeypatch.setattr("lightning.app.cli.connect.app._PPID", ppid)
- monkeypatch.setattr("lightning.app.cli.connect.app._LIGHTNING_CONNECTION", tmpdir)
- monkeypatch.setattr("lightning.app.cli.connect.app._LIGHTNING_CONNECTION_FOLDER", connection_path)
- return connection_path
-
-
-@pytest.mark.flaky(reruns=3, reruns_delay=2)
-def test_connect_disconnect_local(tmpdir, monkeypatch):
- disconnect_app()
-
- with pytest.raises(Exception, match="Connection wasn't successful. Is your app localhost running ?"):
- connect_app("localhost")
-
- with open(os.path.join(os.path.dirname(__file__), "jsons/connect_1.json")) as f:
- data = json.load(f)
-
- data["paths"]["/command/command_with_client"]["post"]["cls_path"] = os.path.join(
- _PROJECT_ROOT,
- data["paths"]["/command/command_with_client"]["post"]["cls_path"],
- )
-
- messages = []
-
- disconnect_app()
-
- def fn(msg):
- messages.append(msg)
-
- monkeypatch.setattr(click, "echo", fn)
-
- response = MagicMock()
- response.status_code = 200
- response.json.return_value = data
- monkeypatch.setattr(cli_helpers.requests, "get", MagicMock(return_value=response))
- connect_app("localhost")
- assert _retrieve_connection_to_an_app() == ("localhost", None)
- command_path = _resolve_command_path("nested_command")
- assert not os.path.exists(command_path)
- command_path = _resolve_command_path("command_with_client")
- assert os.path.exists(command_path)
- messages = []
- connect_app("localhost")
- assert messages == ["You are connected to the local Lightning App."]
-
- messages = []
- disconnect_app()
- assert messages == ["You are disconnected from the local Lightning App."]
- messages = []
- disconnect_app()
- assert messages == [
- "You aren't connected to any Lightning App."
- " Please use `lightning_app connect app_name_or_id` to connect to one."
- ]
-
- assert _retrieve_connection_to_an_app() == (None, None)
-
-
-def test_connect_disconnect_cloud(tmpdir, monkeypatch):
- disconnect_app()
-
- ppid_1 = str(psutil.Process(os.getpid()).ppid())
- ppid_2 = "222"
-
- target_file = _resolve_command_path("command_with_client")
-
- if os.path.exists(target_file):
- os.remove(target_file)
-
- with open(os.path.join(os.path.dirname(__file__), "jsons/connect_1.json")) as f:
- data = json.load(f)
-
- data["paths"]["/command/command_with_client"]["post"]["cls_path"] = os.path.join(
- _PROJECT_ROOT,
- data["paths"]["/command/command_with_client"]["post"]["cls_path"],
- )
-
- messages = []
-
- def fn(msg):
- messages.append(msg)
-
- monkeypatch.setattr(click, "echo", fn)
-
- response = MagicMock()
- response.status_code = 200
- response.json.return_value = data
- monkeypatch.setattr(cli_helpers.requests, "get", MagicMock(return_value=response))
- project = MagicMock()
- project.project_id = "custom_project_name"
- monkeypatch.setattr(cli_helpers, "_get_project", MagicMock(return_value=project))
- client = MagicMock()
- lightningapps = MagicMock()
-
- app = MagicMock()
- app.display_name = "example"
- app.id = "1234"
-
- lightningapps.lightningapps = [app]
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = lightningapps
- monkeypatch.setattr(cli_helpers, "LightningClient", MagicMock(return_value=client))
-
- monkeypatch.setattr(base, "_get_project", MagicMock(return_value=project))
-
- artifact = MagicMock()
- artifact.filename = "commands/command_with_client.py"
- artifacts = MagicMock()
- artifacts.artifacts = [artifact]
- client.lightningapp_instance_service_list_lightningapp_instance_artifacts.return_value = artifacts
- monkeypatch.setattr(base, "LightningClient", MagicMock(return_value=client))
-
- with open(data["paths"]["/command/command_with_client"]["post"]["cls_path"], "rb") as f:
- response.content = f.read()
-
- connect_app("example")
- assert _retrieve_connection_to_an_app() == ("example", "1234")
- commands = _list_app_commands()
- assert commands == ["command with client", "command without client", "nested command"]
- command_path = _resolve_command_path("nested_command")
- assert not os.path.exists(command_path)
- command_path = _resolve_command_path("command_with_client")
- assert os.path.exists(command_path)
- messages = []
- connect_app("example")
- assert messages == ["You are already connected to the cloud Lightning App: example."]
-
- _ = monkeypatch_connection(monkeypatch, tmpdir, ppid=ppid_2)
-
- messages = []
- connect_app("example")
- assert "The lightning App CLI now responds to app commands" in messages[0]
-
- messages = []
- disconnect_app()
- assert messages == ["You are disconnected from the cloud Lightning App: example."]
-
- _ = monkeypatch_connection(monkeypatch, tmpdir, ppid=ppid_1)
-
- messages = []
- disconnect_app()
- assert "You aren't connected to any Lightning App" in messages[0]
-
- messages = []
- disconnect_app()
- assert messages == [
- "You aren't connected to any Lightning App."
- " Please use `lightning_app connect app_name_or_id` to connect to one."
- ]
-
- assert _retrieve_connection_to_an_app() == (None, None)
diff --git a/tests/tests_app/cli/test_connect_data.py b/tests/tests_app/cli/test_connect_data.py
deleted file mode 100644
index 5778b12fa8855..0000000000000
--- a/tests/tests_app/cli/test_connect_data.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import sys
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app.cli.connect import data
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="lightning connect data isn't supported on windows")
-def test_connect_data_no_project(monkeypatch):
- from lightning_cloud.openapi import V1ListMembershipsResponse, V1Membership
-
- client = MagicMock()
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(memberships=[])
- monkeypatch.setattr(data, "LightningClient", MagicMock(return_value=client))
-
- _error_and_exit = MagicMock()
- monkeypatch.setattr(data, "_error_and_exit", _error_and_exit)
-
- _get_project = MagicMock()
- _get_project.return_value = V1Membership(name="project-0", project_id="project-id-0")
- monkeypatch.setattr(data, "_get_project", _get_project)
-
- data.connect_data("imagenet", region="us-east-1", source="imagenet", destination="", project_name="project-0")
-
- _get_project.assert_called()
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="lightning connect data isn't supported on windows")
-def test_connect_data(monkeypatch):
- from lightning_cloud.openapi import Create, V1AwsDataConnection, V1ListMembershipsResponse, V1Membership
-
- client = MagicMock()
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[
- V1Membership(name="project-0", project_id="project-id-0"),
- V1Membership(name="project-1", project_id="project-id-1"),
- V1Membership(name="project 2", project_id="project-id-2"),
- ]
- )
- monkeypatch.setattr(data, "LightningClient", MagicMock(return_value=client))
-
- _error_and_exit = MagicMock()
- monkeypatch.setattr(data, "_error_and_exit", _error_and_exit)
- data.connect_data("imagenet", region="us-east-1", source="imagenet", destination="", project_name="project-0")
-
- _error_and_exit.assert_called_with(
- "Only public S3 folders are supported for now. Please, open a Github issue with your use case."
- )
-
- data.connect_data("imagenet", region="us-east-1", source="s3://imagenet", destination="", project_name="project-0")
-
- client.data_connection_service_create_data_connection.assert_called_with(
- project_id="project-id-0",
- body=Create(
- name="imagenet",
- aws=V1AwsDataConnection(destination="", region="us-east-1", source="s3://imagenet", secret_arn_name=""),
- ),
- )
diff --git a/tests/tests_app/cli/test_cp.py b/tests/tests_app/cli/test_cp.py
deleted file mode 100644
index 303d93968a8c1..0000000000000
--- a/tests/tests_app/cli/test_cp.py
+++ /dev/null
@@ -1,242 +0,0 @@
-import os
-import sys
-from pathlib import PosixPath
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app.cli.commands import cp
-from lightning.app.cli.commands.cd import _CD_FILE, cd
-from lightning_cloud.openapi import (
- Externalv1Cluster,
- Externalv1LightningappInstance,
- V1CloudSpace,
- V1ClusterDriver,
- V1ClusterSpec,
- V1KubernetesClusterDriver,
- V1LightningappInstanceArtifact,
- V1LightningappInstanceSpec,
- V1ListCloudSpacesResponse,
- V1ListClustersResponse,
- V1ListLightningappInstanceArtifactsResponse,
- V1ListLightningappInstancesResponse,
- V1ListMembershipsResponse,
- V1ListProjectClusterBindingsResponse,
- V1Membership,
- V1ProjectClusterBinding,
- V1UploadProjectArtifactResponse,
-)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cp_local_to_remote(tmpdir, monkeypatch):
- error_and_exit = MagicMock()
- monkeypatch.setattr(cp, "_error_and_exit", error_and_exit)
-
- client = MagicMock()
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="project-0")]
- )
-
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = V1ListLightningappInstancesResponse(
- lightningapps=[Externalv1LightningappInstance(name="app-name-0", id="app-id-0")]
- )
-
- client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[V1ProjectClusterBinding(cluster_id="my-cluster", cluster_name="my-cluster")]
- )
-
- result = MagicMock()
- result.get.return_value = V1UploadProjectArtifactResponse(urls=["http://foo.bar"])
- client.lightningapp_instance_service_upload_project_artifact.return_value = result
-
- monkeypatch.setattr(cp, "LightningClient", MagicMock(return_value=client))
-
- assert cd("/", verify=False) == "/"
- cp.cp(str(tmpdir), "r:.")
- assert error_and_exit._mock_call_args_list[0].args[0] == "Uploading files at the project level isn't allowed yet."
-
- assert cd("/project-0/app-name-0", verify=False) == "/project-0/app-name-0"
- with open(f"{tmpdir}/a.txt", "w") as f:
- f.write("hello world !")
-
- file_uploader = MagicMock()
- monkeypatch.setattr(cp, "FileUploader", file_uploader)
-
- cp.cp(str(tmpdir), "r:.")
- assert file_uploader._mock_call_args[1]["name"] == f"{tmpdir}/a.txt"
-
- os.remove(_CD_FILE)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cp_cloud_to_local(tmpdir, monkeypatch):
- error_and_exit = MagicMock()
- monkeypatch.setattr(cp, "_error_and_exit", error_and_exit)
-
- client = MagicMock()
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="project-0")]
- )
-
- clusters = MagicMock()
- clusters.clusters = [MagicMock()]
- client.projects_service_list_project_cluster_bindings.return_value = clusters
-
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = V1ListLightningappInstancesResponse(
- lightningapps=[Externalv1LightningappInstance(name="app-name-0", id="app-id-0")]
- )
-
- artifacts = [
- V1LightningappInstanceArtifact(filename=".file_1.txt", url="http://foo.bar/file_1.txt", size_bytes=123),
- V1LightningappInstanceArtifact(
- filename=".folder_1/file_2.txt", url="http://foo.bar/folder_1/file_2.txt", size_bytes=123
- ),
- V1LightningappInstanceArtifact(
- filename=".folder_2/folder_3/file_3.txt", url="http://foo.bar/folder_2/folder_3/file_3.txt", size_bytes=123
- ),
- V1LightningappInstanceArtifact(
- filename=".folder_4/file_4.txt", url="http://foo.bar/folder_4/file_4.txt", size_bytes=123
- ),
- ]
-
- client.lightningapp_instance_service_list_project_artifacts.return_value = (
- V1ListLightningappInstanceArtifactsResponse(artifacts=artifacts)
- )
-
- monkeypatch.setattr(cp, "LightningClient", MagicMock(return_value=client))
-
- assert cd("/", verify=False) == "/"
- cp.cp(str(tmpdir), "r:.")
- assert error_and_exit._mock_call_args_list[0].args[0] == "Uploading files at the project level isn't allowed yet."
-
- assert cd("/project-0/app-name-0", verify=False) == "/project-0/app-name-0"
-
- download_file = MagicMock()
- monkeypatch.setattr(cp, "_download_file", download_file)
-
- cp.cp("r:.", str(tmpdir))
-
- assert len(download_file.call_args_list) == 4
- for i, call in enumerate(download_file.call_args_list):
- assert call.args[0] == PosixPath(tmpdir / artifacts[i].filename)
- assert call.args[1] == artifacts[i].url
-
- # cleanup
- os.remove(_CD_FILE)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_sanitize_path():
- path, is_remote = cp._sanitize_path("r:default-project", "/")
- assert path == "/default-project"
- assert is_remote
-
- path, _ = cp._sanitize_path("r:foo", "/default-project")
- assert path == "/default-project/foo"
-
- path, _ = cp._sanitize_path("foo", "/default-project")
- assert path == "foo"
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cp_zip_arg_order(monkeypatch):
- assert cd("/", verify=False) == "/"
-
- error_and_exit = MagicMock()
- monkeypatch.setattr(cp, "_error_and_exit", error_and_exit)
- monkeypatch.setattr(cp, "LightningClient", MagicMock(return_value=MagicMock()))
- cp.cp("./my-resource", "r:./my-resource", zip=True)
- error_and_exit.assert_called_once()
- assert "Zipping uploads isn't supported yet" in error_and_exit.call_args_list[0].args[0]
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cp_zip_src_path_too_short(monkeypatch):
- error_and_exit = MagicMock()
- monkeypatch.setattr(cp, "_error_and_exit", error_and_exit)
- monkeypatch.setattr(cp, "LightningClient", MagicMock(return_value=MagicMock()))
- cp.cp("r:/my-project", ".", zip=True)
- error_and_exit.assert_called_once()
- assert "The source path must be at least two levels deep" in error_and_exit.call_args_list[0].args[0]
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cp_zip_remote_to_local_cloudspace_artifact(monkeypatch):
- assert cd("/", verify=False) == "/"
-
- token_getter = MagicMock()
- token_getter._get_api_token.return_value = "my-token"
- monkeypatch.setattr(cp, "_AuthTokenGetter", MagicMock(return_value=token_getter))
-
- client = MagicMock()
- client.cluster_service_list_clusters.return_value = V1ListClustersResponse(
- default_cluster="my-cluster",
- clusters=[
- Externalv1Cluster(
- id="my-cluster",
- spec=V1ClusterSpec(
- driver=V1ClusterDriver(kubernetes=V1KubernetesClusterDriver(root_domain_name="my-domain"))
- ),
- )
- ],
- )
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="my-project", project_id="my-project-id")]
- )
- client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=[V1CloudSpace(name="my-cloudspace", id="my-cloudspace-id")],
- )
- monkeypatch.setattr(cp, "LightningClient", MagicMock(return_value=client))
-
- download_file = MagicMock()
- monkeypatch.setattr(cp, "_download_file", download_file)
-
- cloudspace_artifact = "r:/my-project/my-cloudspace/my-artifact"
- cp.cp(cloudspace_artifact, ".", zip=True)
-
- download_file.assert_called_once()
- assert download_file.call_args_list[0].args[0] == "./my-artifact.zip"
- assert (
- download_file.call_args_list[0].args[1]
- == "https://storage.my-domain/v1/projects/my-project-id/artifacts/download"
- + "?prefix=/cloudspaces/my-cloudspace-id/my-artifact&token=my-token"
- )
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_cp_zip_remote_to_local_app_artifact(monkeypatch):
- assert cd("/", verify=False) == "/"
-
- token_getter = MagicMock()
- token_getter._get_api_token.return_value = "my-token"
- monkeypatch.setattr(cp, "_AuthTokenGetter", MagicMock(return_value=token_getter))
-
- client = MagicMock()
- client.cluster_service_get_cluster.return_value = Externalv1Cluster(
- spec=V1ClusterSpec(driver=V1ClusterDriver(kubernetes=V1KubernetesClusterDriver(root_domain_name="my-domain")))
- )
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="my-project", project_id="my-project-id")]
- )
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = V1ListLightningappInstancesResponse(
- lightningapps=[
- Externalv1LightningappInstance(
- name="my-app", id="my-app-id", spec=V1LightningappInstanceSpec(cluster_id="my-cluster")
- )
- ]
- )
- monkeypatch.setattr(cp, "LightningClient", MagicMock(return_value=client))
-
- download_file = MagicMock()
- monkeypatch.setattr(cp, "_download_file", download_file)
-
- app_artifact = "r:/my-project/my-app/my-artifact"
- cp.cp(app_artifact, ".", zip=True)
-
- download_file.assert_called_once()
- assert download_file.call_args_list[0].args[0] == "./my-artifact.zip"
- assert (
- download_file.call_args_list[0].args[1]
- == "https://storage.my-domain/v1/projects/my-project-id/artifacts/download"
- + "?prefix=/lightningapps/my-app-id/my-artifact&token=my-token"
- )
diff --git a/tests/tests_app/cli/test_ls.py b/tests/tests_app/cli/test_ls.py
deleted file mode 100644
index 06b18fff5d5ae..0000000000000
--- a/tests/tests_app/cli/test_ls.py
+++ /dev/null
@@ -1,101 +0,0 @@
-import os
-import sys
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app.cli.commands import cd, ls
-from lightning_cloud.openapi import (
- Externalv1LightningappInstance,
- V1LightningappInstanceArtifact,
- V1ListCloudSpacesResponse,
- V1ListLightningappInstanceArtifactsResponse,
- V1ListLightningappInstancesResponse,
- V1ListMembershipsResponse,
- V1Membership,
-)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_ls(monkeypatch):
- """This test validates ls behaves as expected."""
- if os.path.exists(cd._CD_FILE):
- os.remove(cd._CD_FILE)
-
- client = MagicMock()
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[
- V1Membership(name="project-0", project_id="project-id-0"),
- V1Membership(name="project-1", project_id="project-id-1"),
- V1Membership(name="project 2", project_id="project-id-2"),
- ]
- )
-
- client.lightningapp_instance_service_list_lightningapp_instances().get.return_value = (
- V1ListLightningappInstancesResponse(
- lightningapps=[
- Externalv1LightningappInstance(name="app-name-0", id="app-id-0"),
- Externalv1LightningappInstance(name="app-name-1", id="app-id-1"),
- Externalv1LightningappInstance(name="app name 2", id="app-id-1"),
- ]
- )
- )
-
- client.cloud_space_service_list_cloud_spaces().get.return_value = V1ListCloudSpacesResponse(cloudspaces=[])
-
- clusters = MagicMock()
- clusters.clusters = [MagicMock()]
- client.projects_service_list_project_cluster_bindings.return_value = clusters
-
- def fn(*args, prefix, **kwargs):
- splits = [split for split in prefix.split("/") if split != ""]
- if len(splits) == 2:
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[
- V1LightningappInstanceArtifact(filename="file_1.txt"),
- V1LightningappInstanceArtifact(filename="folder_1/file_2.txt"),
- V1LightningappInstanceArtifact(filename="folder_2/folder_3/file_3.txt"),
- V1LightningappInstanceArtifact(filename="folder_2/file_4.txt"),
- ]
- )
- if splits[-1] == "folder_1":
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[V1LightningappInstanceArtifact(filename="file_2.txt")]
- )
- if splits[-1] == "folder_2":
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[
- V1LightningappInstanceArtifact(filename="folder_3/file_3.txt"),
- V1LightningappInstanceArtifact(filename="file_4.txt"),
- ]
- )
- if splits[-1] == "folder_3":
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[
- V1LightningappInstanceArtifact(filename="file_3.txt"),
- ]
- )
- return None
-
- client.lightningapp_instance_service_list_project_artifacts = fn
-
- monkeypatch.setattr(ls, "LightningClient", MagicMock(return_value=client))
-
- assert ls.ls() == ["project-0", "project-1", "project 2"]
- assert cd.cd("project-0", verify=False) == "/project-0"
-
- assert ls.ls() == ["app name 2", "app-name-0", "app-name-1"]
- assert f"/project-0{os.sep}app-name-1" == cd.cd("app-name-1", verify=False)
- assert ls.ls() == ["file_1.txt", "folder_1", "folder_2"]
- assert f"/project-0{os.sep}app-name-1{os.sep}folder_1" == cd.cd("folder_1", verify=False)
- assert ls.ls() == ["file_2.txt"]
- assert f"/project-0{os.sep}app-name-1{os.sep}folder_2" == cd.cd("../folder_2", verify=False)
- assert ls.ls() == ["folder_3", "file_4.txt"]
- assert f"/project-0{os.sep}app-name-1{os.sep}folder_2{os.sep}folder_3" == cd.cd("folder_3", verify=False)
- assert ls.ls() == ["file_3.txt"]
-
- assert cd.cd("/project 2", verify=False) == "/project 2"
- assert ls.ls() == ["app name 2", "app-name-0", "app-name-1"]
- assert cd.cd("app name 2", verify=False) == "/project 2/app name 2"
- assert ls.ls() == ["file_1.txt", "folder_1", "folder_2"]
-
- os.remove(cd._CD_FILE)
diff --git a/tests/tests_app/cli/test_rm.py b/tests/tests_app/cli/test_rm.py
deleted file mode 100644
index 9751f9f81d873..0000000000000
--- a/tests/tests_app/cli/test_rm.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import os
-import sys
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app.cli.commands import cd, ls, rm
-from lightning_cloud.openapi import (
- Externalv1LightningappInstance,
- V1LightningappInstanceArtifact,
- V1ListCloudSpacesResponse,
- V1ListLightningappInstanceArtifactsResponse,
- V1ListLightningappInstancesResponse,
- V1ListMembershipsResponse,
- V1Membership,
-)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="not supported on windows yet")
-def test_rm(monkeypatch):
- """This test validates rm behaves as expected."""
- if os.path.exists(cd._CD_FILE):
- os.remove(cd._CD_FILE)
-
- client = MagicMock()
- client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[
- V1Membership(name="project-0", project_id="project-id-0"),
- V1Membership(name="project-1", project_id="project-id-1"),
- V1Membership(name="project 2", project_id="project-id-2"),
- ]
- )
-
- client.lightningapp_instance_service_list_lightningapp_instances().get.return_value = (
- V1ListLightningappInstancesResponse(
- lightningapps=[
- Externalv1LightningappInstance(name="app-name-0", id="app-id-0"),
- Externalv1LightningappInstance(name="app-name-1", id="app-id-1"),
- Externalv1LightningappInstance(name="app name 2", id="app-id-1"),
- ]
- )
- )
-
- client.cloud_space_service_list_cloud_spaces().get.return_value = V1ListCloudSpacesResponse(cloudspaces=[])
-
- clusters = MagicMock()
- clusters.clusters = [MagicMock()]
- client.projects_service_list_project_cluster_bindings.return_value = clusters
-
- def fn(*args, prefix, **kwargs):
- splits = [split for split in prefix.split("/") if split != ""]
- if len(splits) == 2:
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[
- V1LightningappInstanceArtifact(filename="file_1.txt"),
- V1LightningappInstanceArtifact(filename="folder_1/file_2.txt"),
- V1LightningappInstanceArtifact(filename="folder_2/folder_3/file_3.txt"),
- V1LightningappInstanceArtifact(filename="folder_2/file_4.txt"),
- ]
- )
- if splits[-1] == "folder_1":
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[V1LightningappInstanceArtifact(filename="file_2.txt")]
- )
- if splits[-1] == "folder_2":
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[
- V1LightningappInstanceArtifact(filename="folder_3/file_3.txt"),
- V1LightningappInstanceArtifact(filename="file_4.txt"),
- ]
- )
- if splits[-1] == "folder_3":
- return V1ListLightningappInstanceArtifactsResponse(
- artifacts=[
- V1LightningappInstanceArtifact(filename="file_3.txt"),
- ]
- )
- return None
-
- client.lightningapp_instance_service_list_project_artifacts = fn
-
- client.lightningapp_instance_service_delete_project_artifact = MagicMock()
-
- monkeypatch.setattr(rm, "LightningClient", MagicMock(return_value=client))
- monkeypatch.setattr(ls, "LightningClient", MagicMock(return_value=client))
-
- assert ls.ls() == ["project-0", "project-1", "project 2"]
- assert cd.cd("project-0", verify=False) == "/project-0"
-
- assert f"/project-0{os.sep}app-name-1" == cd.cd("app-name-1", verify=False)
-
- assert f"/project-0{os.sep}app-name-1{os.sep}folder_1" == cd.cd("folder_1", verify=False)
-
- rm.rm("file_2.txt")
-
- kwargs = client.lightningapp_instance_service_delete_project_artifact._mock_call_args.kwargs
- assert kwargs["project_id"] == "project-id-0"
- assert kwargs["filename"] == "/lightningapps/app-id-1/folder_1/file_2.txt"
-
- os.remove(cd._CD_FILE)
diff --git a/tests/tests_app/cli/test_run_app.py b/tests/tests_app/cli/test_run_app.py
deleted file mode 100644
index 56d833b3b25d0..0000000000000
--- a/tests/tests_app/cli/test_run_app.py
+++ /dev/null
@@ -1,224 +0,0 @@
-import logging
-import os
-from pathlib import Path
-from unittest import mock
-
-import click
-import lightning.app.core.constants as constants
-import pytest
-from click.testing import CliRunner
-from lightning.app import LightningApp
-from lightning.app.cli.lightning_cli import _run_app, run_app
-from lightning.app.runners.runtime_type import RuntimeType
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.app_helpers import convert_print_to_logger_info
-
-from tests_app import _PROJECT_ROOT
-
-
-@_RunIf(skip_windows=True, skip_mac_os=True)
-@mock.patch("click.launch")
-@pytest.mark.parametrize("open_ui", [True, False])
-def test_lightning_run_app(lauch_mock: mock.MagicMock, open_ui, caplog, monkeypatch):
- """This test validates the command is runned properly and the LightningApp method is being executed."""
- monkeypatch.setattr("lightning.app._logger", logging.getLogger())
-
- original_method = LightningApp._run
-
- @convert_print_to_logger_info
- def _lightning_app_run_and_logging(self, *args, **kwargs):
- original_method(self, *args, **kwargs)
- print("1" if open_ui else "0")
- print(self)
-
- with caplog.at_level(logging.INFO):
- with mock.patch("lightning.app.LightningApp._run", _lightning_app_run_and_logging):
- runner = CliRunner()
- pytest_env = os.environ.pop("PYTEST_CURRENT_TEST")
- try:
- result = runner.invoke(
- run_app,
- [
- os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py"),
- "--blocking",
- "False",
- "--open-ui",
- str(open_ui),
- ],
- catch_exceptions=False,
- )
- finally:
- os.environ["PYTEST_CURRENT_TEST"] = pytest_env
- # capture logs.
- if open_ui:
- # Get the designated port
- port = constants.APP_SERVER_PORT
-
- lauch_mock.assert_called_with(f"http://127.0.0.1:{port}/view")
- else:
- lauch_mock.assert_not_called()
- assert result.exit_code == 0
- assert len(caplog.messages) == 4
- assert bool(int(caplog.messages[0])) is open_ui
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_CLOUD_URL": "https://beta.lightning.ai"})
-@mock.patch("lightning.app.cli.lightning_cli.dispatch")
-@pytest.mark.parametrize("open_ui", [True, False])
-def test_lightning_run_app_cloud(mock_dispatch: mock.MagicMock, open_ui, caplog, monkeypatch):
- """This test validates the command has ran properly when --cloud argument is passed.
-
- It tests it by checking if the click.launch is called with the right url if --open-ui was true and also checks the
- call to `dispatch` for the right arguments.
-
- """
- monkeypatch.setattr("lightning.app.runners.cloud.logger", logging.getLogger())
-
- with caplog.at_level(logging.INFO):
- _run_app(
- file=os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py"),
- cloud=True,
- without_server=False,
- name="",
- blocking=False,
- open_ui=open_ui,
- no_cache=True,
- env=("FOO=bar",),
- secret=("BAR=my-secret",),
- run_app_comment_commands=False,
- enable_basic_auth="",
- )
-
- # Get the designated port
- port = constants.APP_SERVER_PORT
-
- # capture logs.
- # TODO(yurij): refactor the test, check if the actual HTTP request is being sent and that the proper admin
- # page is being opened
- mock_dispatch.assert_called_with(
- Path(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py")),
- RuntimeType.CLOUD,
- start_server=True,
- blocking=False,
- open_ui=open_ui,
- name="",
- no_cache=True,
- env_vars={"FOO": "bar"},
- secrets={"BAR": "my-secret"},
- run_app_comment_commands=False,
- enable_basic_auth="",
- port=port,
- )
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_CLOUD_URL": "https://beta.lightning.ai"})
-@mock.patch("lightning.app.cli.lightning_cli.dispatch")
-@pytest.mark.parametrize("open_ui", [True, False])
-def test_lightning_run_app_cloud_with_run_app_commands(mock_dispatch: mock.MagicMock, open_ui, caplog, monkeypatch):
- """This test validates the command has ran properly when --cloud argument is passed.
-
- It tests it by checking if the click.launch is called with the right url if --open-ui was true and also checks the
- call to `dispatch` for the right arguments.
-
- """
- monkeypatch.setattr("lightning.app.runners.cloud.logger", logging.getLogger())
-
- with caplog.at_level(logging.INFO):
- _run_app(
- file=os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py"),
- cloud=True,
- without_server=False,
- name="",
- blocking=False,
- open_ui=open_ui,
- no_cache=True,
- env=("FOO=bar",),
- secret=("BAR=my-secret",),
- run_app_comment_commands=True,
- enable_basic_auth="",
- )
-
- # Get the designated port
- port = constants.APP_SERVER_PORT
-
- # capture logs.
- # TODO(yurij): refactor the test, check if the actual HTTP request is being sent and that the proper admin
- # page is being opened
- mock_dispatch.assert_called_with(
- Path(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py")),
- RuntimeType.CLOUD,
- start_server=True,
- blocking=False,
- open_ui=open_ui,
- name="",
- no_cache=True,
- env_vars={"FOO": "bar"},
- secrets={"BAR": "my-secret"},
- run_app_comment_commands=True,
- enable_basic_auth="",
- port=port,
- )
-
-
-def test_lightning_run_app_secrets(monkeypatch):
- """Validates that running apps only supports the `--secrets` argument if the `--cloud` argument is passed."""
- monkeypatch.setattr("lightning.app.runners.cloud.logger", logging.getLogger())
-
- with pytest.raises(click.exceptions.ClickException):
- _run_app(
- file=os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py"),
- cloud=False,
- without_server=False,
- name="",
- blocking=False,
- open_ui=False,
- no_cache=True,
- env=(),
- secret=("FOO=my-secret"),
- run_app_comment_commands=False,
- enable_basic_auth="",
- )
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_CLOUD_URL": "https://beta.lightning.ai"})
-@mock.patch("lightning.app.cli.lightning_cli.dispatch")
-def test_lightning_run_app_enable_basic_auth_passed(mock_dispatch: mock.MagicMock, caplog, monkeypatch):
- """This test just validates the command has ran properly when --enable-basic-auth argument is passed.
-
- It checks the call to `dispatch` for the right arguments.
-
- """
- monkeypatch.setattr("lightning.app.runners.cloud.logger", logging.getLogger())
-
- with caplog.at_level(logging.INFO):
- _run_app(
- file=os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py"),
- cloud=True,
- without_server=False,
- name="",
- blocking=False,
- open_ui=False,
- no_cache=True,
- env=("FOO=bar",),
- secret=("BAR=my-secret",),
- run_app_comment_commands=False,
- enable_basic_auth="username:password",
- )
-
- # Get the designated port
- port = constants.APP_SERVER_PORT
-
- mock_dispatch.assert_called_with(
- Path(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py")),
- RuntimeType.CLOUD,
- start_server=True,
- blocking=False,
- open_ui=False,
- name="",
- no_cache=True,
- env_vars={"FOO": "bar"},
- secrets={"BAR": "my-secret"},
- run_app_comment_commands=False,
- enable_basic_auth="username:password",
- port=port,
- )
diff --git a/tests/tests_app/components/__init__.py b/tests/tests_app/components/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/components/database/test_client_server.py b/tests/tests_app/components/database/test_client_server.py
deleted file mode 100644
index decc610130f40..0000000000000
--- a/tests/tests_app/components/database/test_client_server.py
+++ /dev/null
@@ -1,202 +0,0 @@
-import os
-import sys
-import tempfile
-import time
-import traceback
-from pathlib import Path
-from time import sleep
-from typing import List, Optional
-from uuid import uuid4
-
-import pytest
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.components.database import Database, DatabaseClient
-from lightning.app.components.database.utilities import _GeneralModel, _pydantic_column_type
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.utilities.imports import _is_sqlmodel_available
-
-if _is_sqlmodel_available():
- from sqlalchemy import Column
- from sqlmodel import Field, SQLModel
-
- class Secret(SQLModel):
- name: str
- value: str
-
- class TestConfig(SQLModel, table=True):
- __table_args__ = {"extend_existing": True}
-
- id: Optional[int] = Field(default=None, primary_key=True)
- name: str
- secrets: List[Secret] = Field(..., sa_column=Column(_pydantic_column_type(List[Secret])))
-
-
-class Work(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.done = False
-
- def run(self, client: DatabaseClient):
- rows = client.select_all()
- while len(rows) == 0:
- print(rows)
- sleep(0.1)
- rows = client.select_all()
- self.done = True
-
-
-@pytest.mark.skipif(not _is_sqlmodel_available(), reason="sqlmodel is required for this test.")
-def test_client_server():
- database_path = Path("database.db").resolve()
- if database_path.exists():
- os.remove(database_path)
-
- secrets = [Secret(name="example", value="secret")]
-
- general = _GeneralModel.from_obj(TestConfig(name="name", secrets=secrets), token="a")
- assert general.cls_name == "TestConfig"
- assert general.data == '{"id": null, "name": "name", "secrets": [{"name": "example", "value": "secret"}]}'
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self._token = str(uuid4())
- self.db = Database(models=[TestConfig])
- self._client = None
- self.tracker = None
- self.work = Work()
-
- def run(self):
- self.db.run(token=self._token)
-
- if not self.db.alive():
- return
-
- if not self._client:
- self._client = DatabaseClient(model=TestConfig, db_url=self.db.url, token=self._token)
-
- assert self._client
-
- self.work.run(self._client)
-
- if self.tracker is None:
- self._client.insert(TestConfig(name="name", secrets=secrets))
- elem = self._client.select_all(TestConfig)[0]
- assert elem.name == "name"
- self.tracker = "update"
- assert isinstance(elem.secrets[0], Secret)
- assert elem.secrets[0].name == "example"
- assert elem.secrets[0].value == "secret"
-
- elif self.tracker == "update":
- elem = self._client.select_all(TestConfig)[0]
- elem.name = "new_name"
- self._client.update(elem)
-
- elem = self._client.select_all(TestConfig)[0]
- assert elem.name == "new_name"
- self.tracker = "delete"
-
- elif self.tracker == "delete" and self.work.done:
- self.work.stop()
-
- elem = self._client.select_all(TestConfig)[0]
- elem = self._client.delete(elem)
-
- assert not self._client.select_all(TestConfig)
- self._client.insert(TestConfig(name="name", secrets=secrets))
-
- assert self._client.select_all(TestConfig)
- self.stop()
-
- app = LightningApp(Flow())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- database_path = Path("database.db").resolve()
- if database_path.exists():
- os.remove(database_path)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="currently not supported for windows.")
-@pytest.mark.skipif(not _is_sqlmodel_available(), reason="sqlmodel is required for this test.")
-def test_work_database_restart():
- id = str(uuid4()).split("-")[0]
-
- class Flow(LightningFlow):
- def __init__(self, db_root=".", restart=False):
- super().__init__()
- self._db_filename = os.path.join(db_root, id)
- self.db = Database(db_filename=self._db_filename, models=[TestConfig])
- self._client = None
- self.restart = restart
-
- def run(self):
- self.db.run()
-
- if not self.db.alive():
- return
- if not self._client:
- self._client = DatabaseClient(self.db.db_url, None, model=TestConfig)
-
- if not self.restart:
- self._client.insert(TestConfig(name="echo", secrets=[Secret(name="example", value="secret")]))
- self.stop()
- else:
- assert os.path.exists(self._db_filename)
- assert len(self._client.select_all()) == 1
- self.stop()
-
- with tempfile.TemporaryDirectory() as tmpdir:
- app = LightningApp(Flow(db_root=tmpdir))
- MultiProcessRuntime(app).dispatch()
-
- # Note: Waiting for SIGTERM signal to be handled
- sleep(2)
-
- app = LightningApp(Flow(db_root=tmpdir, restart=True))
- MultiProcessRuntime(app).dispatch()
-
- # Note: Waiting for SIGTERM signal to be handled
- sleep(2)
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="currently not supported for windows.")
-@pytest.mark.skipif(not _is_sqlmodel_available(), reason="sqlmodel is required for this test.")
-def test_work_database_periodic_store():
- id = str(uuid4()).split("-")[0]
-
- class Flow(LightningFlow):
- def __init__(self, db_root="."):
- super().__init__()
- self._db_filename = os.path.join(db_root, id)
- self.db = Database(db_filename=self._db_filename, models=[TestConfig], store_interval=1)
- self._client = None
- self._start_time = None
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
- self.db.run()
-
- if not self.db.alive():
- return
-
- if not self._client:
- self._client = DatabaseClient(self.db.db_url, None, model=TestConfig)
-
- if self._start_time is None:
- self._client.insert(TestConfig(name="echo", secrets=[Secret(name="example", value="secret")]))
- self._start_time = time.time()
-
- elif (time.time() - self._start_time) > 2:
- assert os.path.exists(self._db_filename)
- assert len(self._client.select_all()) == 1
- self.stop()
-
- try:
- with tempfile.TemporaryDirectory() as tmpdir:
- app = LightningApp(Flow(tmpdir))
- MultiProcessRuntime(app).dispatch()
- except Exception:
- print(traceback.print_exc())
diff --git a/tests/tests_app/components/multi_node/__init__.py b/tests/tests_app/components/multi_node/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/components/multi_node/test_base.py b/tests/tests_app/components/multi_node/test_base.py
deleted file mode 100644
index a39802f855378..0000000000000
--- a/tests/tests_app/components/multi_node/test_base.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from re import escape
-from unittest import mock
-
-import pytest
-from lightning.app import CloudCompute, LightningWork
-from lightning.app.components import MultiNode
-from lightning_utilities.test.warning import no_warning_call
-
-
-def test_multi_node_warn_running_locally():
- class Work(LightningWork):
- def run(self):
- pass
-
- with pytest.warns(UserWarning, match=escape("You set MultiNode(num_nodes=2, ...)` but ")):
- MultiNode(Work, num_nodes=2, cloud_compute=CloudCompute("gpu"))
-
- with no_warning_call(UserWarning, match=escape("You set MultiNode(num_nodes=1, ...)` but ")):
- MultiNode(Work, num_nodes=1, cloud_compute=CloudCompute("gpu"))
-
-
-@mock.patch("lightning.app.components.multi_node.base.is_running_in_cloud", mock.Mock(return_value=True))
-def test_multi_node_separate_cloud_computes():
- class Work(LightningWork):
- def run(self):
- pass
-
- m = MultiNode(Work, num_nodes=2, cloud_compute=CloudCompute("gpu"))
-
- assert len({w.cloud_compute._internal_id for w in m.ws}) == len(m.ws)
diff --git a/tests/tests_app/components/multi_node/test_fabric.py b/tests/tests_app/components/multi_node/test_fabric.py
deleted file mode 100644
index 5fbcd20123a5e..0000000000000
--- a/tests/tests_app/components/multi_node/test_fabric.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import os
-from copy import deepcopy
-from functools import partial
-from unittest import mock
-
-import lightning.fabric as lf
-import pytest
-from lightning.app.components.multi_node.fabric import _FabricRunExecutor
-from lightning_utilities.core.imports import module_available
-from lightning_utilities.test.warning import no_warning_call
-
-
-class DummyFabric(lf.Fabric):
- def run(self):
- pass
-
-
-def dummy_callable(**kwargs):
- fabric = DummyFabric(**kwargs)
- return fabric._all_passed_kwargs
-
-
-def dummy_init(self, **kwargs):
- self._all_passed_kwargs = kwargs
-
-
-def _get_args_after_tracer_injection(**kwargs):
- with mock.patch.object(lf.Fabric, "__init__", dummy_init):
- ret_val = _FabricRunExecutor.run(
- local_rank=0,
- work_run=partial(dummy_callable, **kwargs),
- main_address="1.2.3.4",
- main_port=5,
- node_rank=6,
- num_nodes=7,
- nprocs=8,
- )
- env_vars = deepcopy(os.environ)
- return ret_val, env_vars
-
-
-def check_lightning_fabric_mps():
- if module_available("lightning.fabric"):
- return lf.accelerators.MPSAccelerator.is_available()
- return False
-
-
-@pytest.mark.skipif(not check_lightning_fabric_mps(), reason="Fabric not available or mps not available")
-@pytest.mark.parametrize(
- ("accelerator_given", "accelerator_expected"), [("cpu", "cpu"), ("auto", "cpu"), ("gpu", "cpu")]
-)
-def test_fabric_run_executor_mps_forced_cpu(accelerator_given, accelerator_expected):
- warning_str = r"Forcing `accelerator=cpu` as MPS does not support distributed training."
- if accelerator_expected != accelerator_given:
- warning_context = pytest.warns(UserWarning, match=warning_str)
- else:
- warning_context = no_warning_call(match=warning_str + "*")
-
- with warning_context:
- ret_val, _ = _get_args_after_tracer_injection(accelerator=accelerator_given)
- assert ret_val["accelerator"] == accelerator_expected
-
-
-@pytest.mark.parametrize(
- ("args_given", "args_expected"),
- [
- ({"devices": 1, "num_nodes": 1, "accelerator": "gpu"}, {"devices": 8, "num_nodes": 7, "accelerator": "auto"}),
- ({"strategy": "ddp_spawn"}, {"strategy": "ddp"}),
- ({"strategy": "ddp_sharded_spawn"}, {"strategy": "ddp_sharded"}),
- ],
-)
-@pytest.mark.skipif(not module_available("lightning"), reason="Lightning is required for this test")
-def test_trainer_run_executor_arguments_choices(args_given: dict, args_expected: dict):
- # ddp with mps devices not available (tested separately, just patching here for cross-os testing of other args)
- if lf.accelerators.MPSAccelerator.is_available():
- args_expected["accelerator"] = "cpu"
-
- ret_val, env_vars = _get_args_after_tracer_injection(**args_given)
-
- for k, v in args_expected.items():
- assert ret_val[k] == v
-
- assert env_vars["MASTER_ADDR"] == "1.2.3.4"
- assert env_vars["MASTER_PORT"] == "5"
- assert env_vars["GROUP_RANK"] == "6"
- assert env_vars["RANK"] == str(0 + 6 * 8)
- assert env_vars["LOCAL_RANK"] == "0"
- assert env_vars["WORLD_SIZE"] == str(7 * 8)
- assert env_vars["LOCAL_WORLD_SIZE"] == "8"
- assert env_vars["TORCHELASTIC_RUN_ID"] == "1"
- assert env_vars["LT_CLI_USED"] == "1"
-
-
-@pytest.mark.skipif(not module_available("lightning"), reason="Lightning not available")
-def test_run_executor_invalid_strategy_instances():
- with pytest.raises(ValueError, match="DDP Spawned strategies aren't supported yet."):
- _, _ = _get_args_after_tracer_injection(strategy=lf.strategies.DDPStrategy(start_method="spawn"))
diff --git a/tests/tests_app/components/multi_node/test_trainer.py b/tests/tests_app/components/multi_node/test_trainer.py
deleted file mode 100644
index bd7b5836c6d12..0000000000000
--- a/tests/tests_app/components/multi_node/test_trainer.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import os
-from copy import deepcopy
-from functools import partial
-from unittest import mock
-
-import pytest
-import pytorch_lightning as pl
-from lightning.app.components.multi_node.trainer import _LightningTrainerRunExecutor
-from lightning_utilities.core.imports import module_available
-from lightning_utilities.test.warning import no_warning_call
-
-
-def dummy_callable(**kwargs):
- t = pl.Trainer(**kwargs)
- return t._all_passed_kwargs
-
-
-def dummy_init(self, **kwargs):
- self._all_passed_kwargs = kwargs
-
-
-def _get_args_after_tracer_injection(**kwargs):
- with mock.patch.object(pl.Trainer, "__init__", dummy_init):
- ret_val = _LightningTrainerRunExecutor.run(
- local_rank=0,
- work_run=partial(dummy_callable, **kwargs),
- main_address="1.2.3.4",
- main_port=5,
- node_rank=6,
- num_nodes=7,
- nprocs=8,
- )
- env_vars = deepcopy(os.environ)
- return ret_val, env_vars
-
-
-def check_lightning_pytorch_and_mps():
- if module_available("pytorch_lightning"):
- return pl.accelerators.MPSAccelerator.is_available()
- return False
-
-
-@pytest.mark.skipif(not check_lightning_pytorch_and_mps(), reason="pytorch_lightning and mps are required")
-@pytest.mark.parametrize(
- ("accelerator_given", "accelerator_expected"), [("cpu", "cpu"), ("auto", "cpu"), ("gpu", "cpu")]
-)
-def test_trainer_run_executor_mps_forced_cpu(accelerator_given, accelerator_expected):
- warning_str = r"Forcing `accelerator=cpu` as MPS does not support distributed training."
- if accelerator_expected != accelerator_given:
- warning_context = pytest.warns(UserWarning, match=warning_str)
- else:
- warning_context = no_warning_call(match=warning_str + "*")
-
- with warning_context:
- ret_val, _ = _get_args_after_tracer_injection(accelerator=accelerator_given)
- assert ret_val["accelerator"] == accelerator_expected
-
-
-@pytest.mark.parametrize(
- ("args_given", "args_expected"),
- [
- ({"devices": 1, "num_nodes": 1, "accelerator": "gpu"}, {"devices": 8, "num_nodes": 7, "accelerator": "auto"}),
- ({"strategy": "ddp_spawn"}, {"strategy": "ddp"}),
- ({"strategy": "ddp_sharded_spawn"}, {"strategy": "ddp_sharded"}),
- ],
-)
-@pytest.mark.skipif(not module_available("torch"), reason="PyTorch is not available")
-def test_trainer_run_executor_arguments_choices(
- args_given: dict,
- args_expected: dict,
-):
- if pl.accelerators.MPSAccelerator.is_available():
- args_expected.pop("accelerator", None) # Cross platform tests -> MPS is tested separately
-
- ret_val, env_vars = _get_args_after_tracer_injection(**args_given)
-
- for k, v in args_expected.items():
- assert ret_val[k] == v
-
- assert env_vars["MASTER_ADDR"] == "1.2.3.4"
- assert env_vars["MASTER_PORT"] == "5"
- assert env_vars["GROUP_RANK"] == "6"
- assert env_vars["RANK"] == str(0 + 6 * 8)
- assert env_vars["LOCAL_RANK"] == "0"
- assert env_vars["WORLD_SIZE"] == str(7 * 8)
- assert env_vars["LOCAL_WORLD_SIZE"] == "8"
- assert env_vars["TORCHELASTIC_RUN_ID"] == "1"
-
-
-@pytest.mark.skipif(True, reason="not maintained")
-@pytest.mark.skipif(not module_available("lightning"), reason="lightning not available")
-def test_trainer_run_executor_invalid_strategy_instances():
- with pytest.raises(ValueError, match="DDP Spawned strategies aren't supported yet."):
- _, _ = _get_args_after_tracer_injection(strategy=pl.strategies.DDPStrategy(start_method="spawn"))
diff --git a/tests/tests_app/components/python/scripts/a.py b/tests/tests_app/components/python/scripts/a.py
deleted file mode 100644
index 414be73ce4a51..0000000000000
--- a/tests/tests_app/components/python/scripts/a.py
+++ /dev/null
@@ -1 +0,0 @@
-print("Hello World !")
diff --git a/tests/tests_app/components/python/scripts/b.py b/tests/tests_app/components/python/scripts/b.py
deleted file mode 100644
index 53254da11906b..0000000000000
--- a/tests/tests_app/components/python/scripts/b.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import sys
-
-print(sys.argv)
diff --git a/tests/tests_app/components/python/scripts/c.py b/tests/tests_app/components/python/scripts/c.py
deleted file mode 100644
index eb56e6de70a61..0000000000000
--- a/tests/tests_app/components/python/scripts/c.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import os
-
-if __name__ == "__main__":
- assert int(os.environ["VARIABLE"]) == 0
diff --git a/tests/tests_app/components/python/test_python.py b/tests/tests_app/components/python/test_python.py
deleted file mode 100644
index f0d14ebe518d8..0000000000000
--- a/tests/tests_app/components/python/test_python.py
+++ /dev/null
@@ -1,148 +0,0 @@
-import os
-import tarfile
-
-import pytest
-from lightning.app.components.python import PopenPythonScript, TracerPythonScript
-from lightning.app.components.python.tracer import Code
-from lightning.app.storage.drive import Drive
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.testing.testing import run_work_isolated
-from lightning.app.utilities.component import _set_work_context
-from lightning.app.utilities.enum import CacheCallsKeys
-from tests_app import _PROJECT_ROOT
-
-COMPONENTS_SCRIPTS_FOLDER = str(os.path.join(_PROJECT_ROOT, "tests/tests_app/components/python/scripts/"))
-
-
-def test_non_existing_python_script():
- match = "tests/components/python/scripts/0.py"
- with pytest.raises(FileNotFoundError, match=match):
- python_script = PopenPythonScript(match)
- run_work_isolated(python_script)
- assert not python_script.has_started
-
- python_script = TracerPythonScript(match, raise_exception=False)
- run_work_isolated(python_script)
- assert python_script.has_failed
-
-
-def test_simple_python_script():
- python_script = PopenPythonScript(COMPONENTS_SCRIPTS_FOLDER + "a.py")
- run_work_isolated(python_script)
- assert python_script.has_succeeded
-
- python_script = TracerPythonScript(COMPONENTS_SCRIPTS_FOLDER + "a.py")
- run_work_isolated(python_script)
- assert python_script.has_succeeded
-
-
-def test_simple_popen_python_script_with_kwargs():
- python_script = PopenPythonScript(
- COMPONENTS_SCRIPTS_FOLDER + "b.py",
- script_args="--arg_0=hello --arg_1=world",
- )
- run_work_isolated(python_script)
- assert python_script.has_succeeded
-
-
-@_RunIf(skip_windows=True)
-def test_popen_python_script_failure():
- python_script = PopenPythonScript(
- COMPONENTS_SCRIPTS_FOLDER + "c.py",
- env={"VARIABLE": "1"},
- raise_exception=False,
- )
- run_work_isolated(python_script)
- assert python_script.has_failed
- assert "Exception(self.exit_code)" in python_script.status.message
-
-
-def test_tracer_python_script_with_kwargs():
- python_script = TracerPythonScript(
- COMPONENTS_SCRIPTS_FOLDER + "b.py",
- script_args="--arg_0=hello --arg_1=world",
- raise_exception=False,
- )
- run_work_isolated(python_script)
- assert python_script.has_succeeded
-
- python_script = TracerPythonScript(
- COMPONENTS_SCRIPTS_FOLDER + "c.py",
- env={"VARIABLE": "1"},
- raise_exception=False,
- )
- run_work_isolated(python_script)
- assert python_script.has_failed
-
-
-def test_tracer_component_with_code():
- """This test ensures the Tracer Component gets the latest code from the code object that is provided and arguments
- are cleaned."""
-
- drive = Drive("lit://code")
- drive.component_name = "something"
- code = Code(drive=drive, name="sample.tar.gz")
-
- with open("file.py", "w") as f:
- f.write('raise Exception("An error")')
-
- with tarfile.open("sample.tar.gz", "w:gz") as tar:
- tar.add("file.py")
-
- drive.put("sample.tar.gz")
- os.remove("file.py")
- os.remove("sample.tar.gz")
-
- python_script = TracerPythonScript("file.py", script_args=["--b=1"], raise_exception=False, code=code)
- run_work_isolated(python_script, params={"--a": "1"}, restart_count=0)
- assert "An error" in python_script.status.message
-
- with open("file.py", "w") as f:
- f.write("import sys\n")
- f.write("print(sys.argv)\n")
-
- with tarfile.open("sample.tar.gz", "w:gz") as tar:
- tar.add("file.py")
-
- _set_work_context()
- drive.put("sample.tar.gz")
- os.remove("file.py")
- os.remove("sample.tar.gz")
-
- with open("file.py", "w") as f:
- f.write('raise Exception("An error")')
-
- call_hash = python_script._calls[CacheCallsKeys.LATEST_CALL_HASH]
- python_script._calls[call_hash]["statuses"].pop(-1)
- python_script._calls[call_hash]["statuses"].pop(-1)
-
- run_work_isolated(python_script, params={"--a": "1"}, restart_count=1)
- assert python_script.has_succeeded
- assert python_script.script_args == ["--b=1", "--a=1"]
- os.remove("file.py")
- os.remove("sample.tar.gz")
-
-
-def test_tracer_component_with_code_in_dir(tmp_path):
- """This test ensures the Tracer Component gets the latest code from the code object that is provided and arguments
- are cleaned."""
-
- drive = Drive("lit://code")
- drive.component_name = "something"
- code = Code(drive=drive, name="sample.tar.gz")
-
- with open("file.py", "w") as f:
- f.write('raise Exception("An error")')
-
- with tarfile.open("sample.tar.gz", "w:gz") as tar:
- tar.add("file.py")
-
- drive.put("sample.tar.gz")
- os.remove("file.py")
- os.remove("sample.tar.gz")
-
- python_script = TracerPythonScript("file.py", script_args=["--b=1"], raise_exception=False, code=code)
- run_work_isolated(python_script, params={"--a": "1"}, restart_count=0, code_dir=str(tmp_path))
- assert "An error" in python_script.status.message
-
- assert os.path.exists(os.path.join(str(tmp_path), "file.py"))
diff --git a/tests/tests_app/components/sample_package_repo/external_lightning_component_package/__init__.py b/tests/tests_app/components/sample_package_repo/external_lightning_component_package/__init__.py
deleted file mode 100644
index f818186c96f55..0000000000000
--- a/tests/tests_app/components/sample_package_repo/external_lightning_component_package/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from lightning.app import LightningFlow, LightningWork
-
-
-class MyCustomLightningWork(LightningWork):
- @staticmethod
- def special_method():
- return "Hi, I'm an external lightning work component and can be added to any lightning project."
-
-
-class MyCustomLightningFlow(LightningFlow):
- @staticmethod
- def special_method():
- return "Hi, I'm an external lightning flow component and can be added to any lightning project."
-
-
-def exported_lightning_components():
- return [MyCustomLightningWork, MyCustomLightningFlow]
diff --git a/tests/tests_app/components/sample_package_repo/setup.py b/tests/tests_app/components/sample_package_repo/setup.py
deleted file mode 100644
index fdfeb663fe7a2..0000000000000
--- a/tests/tests_app/components/sample_package_repo/setup.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import json
-import os
-
-from setuptools import find_packages, setup
-from setuptools.command.install import install
-
-LIGHTNING_COMPONENT_INFO = {
- "package": "external_lightning_component_package",
- "version": "0.0.1",
- "entry_point": "myorg.lightning_modules",
-}
-
-
-class PostInstallCommand(install):
- def run(self):
- install.run(self)
- os.system(f"echo Installed lightning component package: {json.dumps(json.dumps(LIGHTNING_COMPONENT_INFO))}")
-
-
-setup(
- name=LIGHTNING_COMPONENT_INFO["package"],
- version=LIGHTNING_COMPONENT_INFO["version"],
- description="example of an external lightning package that contains lightning components",
- author="manskx",
- author_email="mansy@grid.ai",
- url="grid.ai",
- download_url="https://github.com/Lightning-AI/lightning",
- license="TBD",
- packages=find_packages(exclude=["tests", "docs"]),
- long_description="example of an external lightning package that contains lightning components",
- long_description_content_type="text/markdown",
- include_package_data=True,
- zip_safe=False,
- keywords=["deep learning", "pytorch", "AI"],
- python_requires=">=3.6",
- entry_points={
- "lightning.app.external_components": [
- f"{LIGHTNING_COMPONENT_INFO['entry_point']}= "
- f"{LIGHTNING_COMPONENT_INFO['package']}:exported_lightning_components",
- ],
- },
- cmdclass={
- "install": PostInstallCommand,
- },
- setup_requires=["wheel"],
-)
diff --git a/tests/tests_app/components/serve/test_auto_scaler.py b/tests/tests_app/components/serve/test_auto_scaler.py
deleted file mode 100644
index 0da489907b69f..0000000000000
--- a/tests/tests_app/components/serve/test_auto_scaler.py
+++ /dev/null
@@ -1,222 +0,0 @@
-import time
-import uuid
-from unittest import mock
-from unittest.mock import patch
-
-import pytest
-from fastapi import HTTPException
-from lightning.app import CloudCompute, LightningWork
-from lightning.app.components import AutoScaler, ColdStartProxy, Text
-from lightning.app.components.serve.auto_scaler import _LoadBalancer
-
-
-class EmptyWork(LightningWork):
- def run(self):
- pass
-
-
-class AutoScaler1(AutoScaler):
- def scale(self, replicas: int, metrics) -> int:
- # only upscale
- return replicas + 1
-
-
-class AutoScaler2(AutoScaler):
- def scale(self, replicas: int, metrics) -> int:
- # only downscale
- return replicas - 1
-
-
-@patch("uvicorn.run")
-@patch("lightning.app.components.serve.auto_scaler._LoadBalancer.url")
-@patch("lightning.app.components.serve.auto_scaler.AutoScaler.num_pending_requests")
-def test_num_replicas_not_above_max_replicas(*_):
- """Test self.num_replicas doesn't exceed max_replicas."""
- max_replicas = 6
- auto_scaler = AutoScaler1(
- EmptyWork,
- min_replicas=1,
- max_replicas=max_replicas,
- scale_out_interval=0.001,
- scale_in_interval=0.001,
- )
-
- for _ in range(max_replicas + 1):
- time.sleep(0.002)
- auto_scaler.run()
-
- assert auto_scaler.num_replicas == max_replicas
-
-
-@patch("uvicorn.run")
-@patch("lightning.app.components.serve.auto_scaler._LoadBalancer.url")
-@patch("lightning.app.components.serve.auto_scaler.AutoScaler.num_pending_requests")
-def test_num_replicas_not_below_min_replicas(*_):
- """Test self.num_replicas doesn't exceed max_replicas."""
- min_replicas = 1
- auto_scaler = AutoScaler2(
- EmptyWork,
- min_replicas=min_replicas,
- max_replicas=4,
- scale_out_interval=0.001,
- scale_in_interval=0.001,
- )
-
- for _ in range(3):
- time.sleep(0.002)
- auto_scaler.run()
-
- assert auto_scaler.num_replicas == min_replicas
-
-
-@pytest.mark.parametrize(
- ("replicas", "metrics", "expected_replicas"),
- [
- pytest.param(1, {"pending_requests": 1, "pending_works": 0}, 2, id="increase if no pending work"),
- pytest.param(1, {"pending_requests": 1, "pending_works": 1}, 1, id="dont increase if pending works"),
- pytest.param(8, {"pending_requests": 1, "pending_works": 0}, 7, id="reduce if requests < 25% capacity"),
- pytest.param(8, {"pending_requests": 2, "pending_works": 0}, 8, id="dont reduce if requests >= 25% capacity"),
- ],
-)
-def test_scale(replicas, metrics, expected_replicas):
- """Test `scale()`, the default scaling strategy."""
- auto_scaler = AutoScaler(
- EmptyWork,
- min_replicas=1,
- max_replicas=8,
- max_batch_size=1,
- )
-
- assert auto_scaler.scale(replicas, metrics) == expected_replicas
-
-
-def test_scale_from_zero_min_replica():
- auto_scaler = AutoScaler(
- EmptyWork,
- min_replicas=0,
- max_replicas=2,
- max_batch_size=10,
- )
-
- resp = auto_scaler.scale(0, {"pending_requests": 0, "pending_works": 0})
- assert resp == 0
-
- resp = auto_scaler.scale(0, {"pending_requests": 1, "pending_works": 0})
- assert resp == 1
-
- resp = auto_scaler.scale(0, {"pending_requests": 1, "pending_works": 1})
- assert resp <= 0
-
-
-def test_create_work_cloud_compute_cloned():
- """Test CloudCompute is cloned to avoid creating multiple works in a single machine."""
- cloud_compute = CloudCompute("gpu")
- auto_scaler = AutoScaler(EmptyWork, cloud_compute=cloud_compute)
- _ = auto_scaler.create_work()
- assert auto_scaler._work_kwargs["cloud_compute"] is not cloud_compute
-
-
-fastapi_mock = mock.MagicMock()
-mocked_fastapi_creater = mock.MagicMock(return_value=fastapi_mock)
-
-
-@patch("lightning.app.components.serve.auto_scaler._create_fastapi", mocked_fastapi_creater)
-@patch("lightning.app.components.serve.auto_scaler.uvicorn.run", mock.MagicMock())
-def test_API_ACCESS_ENDPOINT_creation():
- auto_scaler = AutoScaler(EmptyWork, input_type=Text, output_type=Text)
- assert auto_scaler.load_balancer._api_name == "EmptyWork"
-
- auto_scaler.load_balancer.run()
- fastapi_mock.mount.assert_called_once_with("/endpoint-info", mock.ANY, name="static")
-
-
-def test_autoscaler_scale_up(monkeypatch):
- monkeypatch.setattr(AutoScaler, "num_pending_works", 0)
- monkeypatch.setattr(AutoScaler, "num_pending_requests", 100)
- monkeypatch.setattr(AutoScaler, "scale", mock.MagicMock(return_value=1))
- monkeypatch.setattr(AutoScaler, "create_work", mock.MagicMock())
- monkeypatch.setattr(AutoScaler, "add_work", mock.MagicMock())
-
- auto_scaler = AutoScaler(EmptyWork, min_replicas=0, max_replicas=4, scale_out_interval=0.001)
-
- # Mocking the attributes
- auto_scaler._last_autoscale = time.time() - 100000
- auto_scaler.num_replicas = 0
-
- # triggering scale up
- auto_scaler.autoscale()
- auto_scaler.scale.assert_called_once()
- auto_scaler.create_work.assert_called_once()
- auto_scaler.add_work.assert_called_once()
-
-
-def test_autoscaler_scale_down(monkeypatch):
- monkeypatch.setattr(AutoScaler, "num_pending_works", 0)
- monkeypatch.setattr(AutoScaler, "num_pending_requests", 0)
- monkeypatch.setattr(AutoScaler, "scale", mock.MagicMock(return_value=0))
- monkeypatch.setattr(AutoScaler, "remove_work", mock.MagicMock())
- monkeypatch.setattr(AutoScaler, "workers", mock.MagicMock())
-
- auto_scaler = AutoScaler(EmptyWork, min_replicas=0, max_replicas=4, scale_in_interval=0.001)
-
- # Mocking the attributes
- auto_scaler._last_autoscale = time.time() - 100000
- auto_scaler.num_replicas = 1
- auto_scaler.__dict__["load_balancer"] = mock.MagicMock()
-
- # triggering scale up
- auto_scaler.autoscale()
- auto_scaler.scale.assert_called_once()
- auto_scaler.remove_work.assert_called_once()
-
-
-class TestLoadBalancerProcessRequest:
- @pytest.mark.asyncio()
- async def test_workers_not_ready_with_cold_start_proxy(self, monkeypatch):
- monkeypatch.setattr(ColdStartProxy, "handle_request", mock.AsyncMock())
- load_balancer = _LoadBalancer(
- input_type=Text, output_type=Text, endpoint="/predict", cold_start_proxy=ColdStartProxy("url")
- )
- req_id = uuid.uuid4().hex
- await load_balancer.process_request("test", req_id)
- load_balancer._cold_start_proxy.handle_request.assert_called_once_with("test")
-
- @pytest.mark.asyncio()
- async def test_workers_not_ready_without_cold_start_proxy(self, monkeypatch):
- load_balancer = _LoadBalancer(
- input_type=Text,
- output_type=Text,
- endpoint="/predict",
- )
- req_id = uuid.uuid4().hex
- # populating the responses so the while loop exists
- load_balancer._responses = {req_id: "Dummy"}
- with pytest.raises(HTTPException):
- await load_balancer.process_request("test", req_id)
-
- @pytest.mark.asyncio()
- async def test_workers_have_no_capacity_with_cold_start_proxy(self, monkeypatch):
- monkeypatch.setattr(ColdStartProxy, "handle_request", mock.AsyncMock())
- load_balancer = _LoadBalancer(
- input_type=Text, output_type=Text, endpoint="/predict", cold_start_proxy=ColdStartProxy("url")
- )
- load_balancer._fastapi_app = mock.MagicMock()
- load_balancer._fastapi_app.num_current_requests = 1000
- load_balancer.servers.append(mock.MagicMock())
- req_id = uuid.uuid4().hex
- await load_balancer.process_request("test", req_id)
- load_balancer._cold_start_proxy.handle_request.assert_called_once_with("test")
-
- @pytest.mark.asyncio()
- async def test_workers_are_free(self):
- load_balancer = _LoadBalancer(
- input_type=Text,
- output_type=Text,
- endpoint="/predict",
- )
- load_balancer.servers.append(mock.MagicMock())
- req_id = uuid.uuid4().hex
- # populating the responses so the while loop exists
- load_balancer._responses = {req_id: "Dummy"}
- await load_balancer.process_request("test", req_id)
- assert load_balancer._batch == [(req_id, "test")]
diff --git a/tests/tests_app/components/serve/test_model_inference_api.py b/tests/tests_app/components/serve/test_model_inference_api.py
deleted file mode 100644
index 64c11445386b0..0000000000000
--- a/tests/tests_app/components/serve/test_model_inference_api.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import base64
-import multiprocessing as mp
-import os
-from unittest.mock import ANY, MagicMock
-
-import pytest
-from lightning.app.components.serve import serve
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.imports import _is_numpy_available, _is_torch_available
-from lightning.app.utilities.network import _configure_session, find_free_network_port
-from tests_app import _PROJECT_ROOT
-
-if _is_numpy_available():
- import numpy as np
-
-if _is_torch_available():
- import torch
-
-
-class ImageServer(serve.ModelInferenceAPI):
- def build_model(self):
- return lambda x: x
-
- def predict(self, image):
- image = self.model(image)
- return torch.from_numpy(np.asarray(image))
-
-
-def target_fn(port, workers):
- image_server = ImageServer(input="image", output="image", port=port, workers=workers)
- image_server.run()
-
-
-@pytest.mark.xfail(strict=False, reason="test has been ignored for a while and seems not to be working :(")
-@pytest.mark.skipif(not (_is_torch_available() and _is_numpy_available()), reason="Missing torch and numpy")
-@pytest.mark.parametrize("workers", [0])
-# avoid the error: Failed to establish a new connection: [WinError 10061] No connection could be made because the
-# target machine actively refused it
-@_RunIf(skip_windows=True)
-def test_model_inference_api(workers):
- port = find_free_network_port()
- process = mp.Process(target=target_fn, args=(port, workers))
- process.start()
-
- image_path = os.path.join(_PROJECT_ROOT, "docs/source-app/_static/images/logo.png")
- with open(image_path, "rb") as f:
- imgstr = base64.b64encode(f.read()).decode("UTF-8")
-
- session = _configure_session()
- res = session.post(f"http://127.0.0.1:{port}/predict", params={"data": imgstr})
- process.terminate()
- # TODO: Investigate why this doesn't match exactly `imgstr`.
- assert res.json()
- process.kill()
-
-
-class EmptyServer(serve.ModelInferenceAPI):
- def build_model(self):
- return lambda x: x
-
- def serialize(self, x):
- return super().serialize(x)
-
- def deserialize(self, x):
- return super().deserialize(x)
-
- def predict(self, x):
- return super().predict(x)
-
-
-def test_model_inference_api_mock(monkeypatch):
- monkeypatch.setattr(serve, "uvicorn", MagicMock())
- comp = EmptyServer()
- comp.run()
- serve.uvicorn.run.assert_called_once_with(app=ANY, host=comp.host, port=comp.port, log_level="error")
-
- with pytest.raises(Exception, match="Only input in"):
- EmptyServer(input="something")
-
- with pytest.raises(Exception, match="Only output in"):
- EmptyServer(output="something")
diff --git a/tests/tests_app/components/serve/test_python_server.py b/tests/tests_app/components/serve/test_python_server.py
deleted file mode 100644
index 9d353f98ef33e..0000000000000
--- a/tests/tests_app/components/serve/test_python_server.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import multiprocessing as mp
-
-from lightning.app.components import Category, Image, Number, PythonServer, Text
-from lightning.app.utilities.network import _configure_session, find_free_network_port
-
-
-class SimpleServer(PythonServer):
- def __init__(self, port):
- super().__init__(port=port)
- self._model = None
-
- def setup(self):
- self._model = lambda x: x
-
- def predict(self, data):
- return {"prediction": self._model(data.payload)}
-
-
-def target_fn(port):
- image_server = SimpleServer(port=port)
- image_server.run()
-
-
-def test_python_server_component():
- port = find_free_network_port()
- process = mp.Process(target=target_fn, args=(port,))
- process.start()
- session = _configure_session()
- res = session.post(f"http://127.0.0.1:{port}/predict", json={"payload": "test"})
- process.terminate()
- assert res.json()["prediction"] == "test"
- process.kill()
-
-
-def test_image_sample_data():
- data = Image().get_sample_data()
- assert isinstance(data, dict)
- assert "image" in data
- assert len(data["image"]) > 100
-
-
-def test_text_sample_data():
- data = Text().get_sample_data()
- assert isinstance(data, dict)
- assert "text" in data
- assert len(data["text"]) > 20
-
-
-def test_number_sample_data():
- data = Number().get_sample_data()
- assert isinstance(data, dict)
- assert "prediction" in data
- assert data["prediction"] == 463
-
-
-def test_category_sample_data():
- data = Category().get_sample_data()
- assert isinstance(data, dict)
- assert "category" in data
- assert data["category"] == 463
diff --git a/tests/tests_app/components/serve/test_streamlit.py b/tests/tests_app/components/serve/test_streamlit.py
deleted file mode 100644
index 29eff20d3206a..0000000000000
--- a/tests/tests_app/components/serve/test_streamlit.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import os
-import sys
-from unittest import mock
-
-import lightning.app
-import pytest
-from lightning.app.components.serve.streamlit import ServeStreamlit, _build_model, _PatchedWork
-from lightning_utilities.core.imports import RequirementCache
-
-_STREAMLIT_AVAILABLE = RequirementCache("streamlit")
-
-
-class ServeStreamlitTest(ServeStreamlit):
- def __init__(self):
- super().__init__()
-
- self.test_variable = -1
-
- @property
- def test_property(self):
- return self.test_variable
-
- def test_method(self):
- return "test_method"
-
- @staticmethod
- def test_staticmethod():
- return "test_staticmethod"
-
- def build_model(self):
- return "model"
-
- def render():
- pass
-
-
-@pytest.mark.skipif(not _STREAMLIT_AVAILABLE, reason="requires streamlit")
-@mock.patch("lightning.app.components.serve.streamlit.subprocess")
-def test_streamlit_start_stop_server(subprocess_mock):
- """Test that `ServeStreamlit.run()` invokes subprocess.Popen with the right parameters."""
- work = ServeStreamlitTest()
- work._name = "test_work"
- work._host = "hostname"
- work._port = 1111
-
- work.run()
-
- subprocess_mock.Popen.assert_called_once()
-
- env_variables = subprocess_mock.method_calls[0].kwargs["env"]
- call_args = subprocess_mock.method_calls[0].args[0]
- assert call_args == [
- sys.executable,
- "-m",
- "streamlit",
- "run",
- lightning.app.components.serve.streamlit.__file__,
- "--server.address",
- "hostname",
- "--server.port",
- "1111",
- "--server.headless",
- "true",
- ]
-
- assert env_variables["LIGHTNING_COMPONENT_NAME"] == "test_work"
- assert env_variables["LIGHTNING_WORK"] == "ServeStreamlitTest"
- assert env_variables["LIGHTNING_WORK_MODULE_FILE"] == __file__
-
- assert "LIGHTNING_COMPONENT_NAME" not in os.environ
- assert "LIGHTNING_WORK" not in os.environ
- assert "LIGHTNING_WORK_MODULE_FILE" not in os.environ
-
- work.on_exit()
- subprocess_mock.Popen().kill.assert_called_once()
-
-
-def test_patched_work():
- class TestState:
- test_variable = 1
-
- patched_work = _PatchedWork(TestState(), ServeStreamlitTest)
-
- assert patched_work.test_variable == 1
- assert patched_work.test_property == 1
- assert patched_work.test_method() == "test_method"
- assert patched_work.test_staticmethod() == "test_staticmethod"
-
-
-@pytest.mark.skipif(not _STREAMLIT_AVAILABLE, reason="requires streamlit")
-def test_build_model():
- import streamlit as st
-
- st.session_state = {}
- st.spinner = mock.MagicMock()
-
- class TestState:
- test_variable = 1
-
- patched_work = _PatchedWork(TestState(), ServeStreamlitTest)
- patched_work.build_model = mock.MagicMock(return_value="test_model")
-
- _build_model(patched_work)
-
- assert st.session_state["_model"] == "test_model"
- assert patched_work.model == "test_model"
- patched_work.build_model.assert_called_once()
-
- patched_work.build_model.reset_mock()
-
- _build_model(patched_work)
-
- patched_work.build_model.assert_not_called()
diff --git a/tests/tests_app/conftest.py b/tests/tests_app/conftest.py
deleted file mode 100644
index e6e87112ac8e1..0000000000000
--- a/tests/tests_app/conftest.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import contextlib
-import os
-import shutil
-import signal
-import threading
-from datetime import datetime
-from pathlib import Path
-from threading import Thread
-
-import psutil
-import py
-import pytest
-from lightning.app.core import constants
-from lightning.app.utilities.app_helpers import _collect_child_process_pids
-from lightning.app.utilities.component import _set_context
-from lightning.app.utilities.packaging import cloud_compute
-from lightning.app.utilities.packaging.app_config import _APP_CONFIG_FILENAME
-from lightning.app.utilities.state import AppState
-
-os.environ["LIGHTNING_DISPATCHED"] = "1"
-
-original_method = Thread._wait_for_tstate_lock
-
-
-def fn(self, *args, timeout=None, **kwargs):
- original_method(self, *args, timeout=1, **kwargs)
-
-
-Thread._wait_for_tstate_lock = fn
-
-
-def pytest_sessionfinish(session, exitstatus):
- """Pytest hook that get called after whole test run finished, right before returning the exit status to the
- system."""
- # kill all the processes and threads created by parent
- # TODO this isn't great. We should have each tests doing it's own cleanup
- current_process = psutil.Process()
- for child in current_process.children(recursive=True):
- with contextlib.suppress(psutil.NoSuchProcess):
- params = child.as_dict() or {}
- cmd_lines = params.get("cmdline", [])
- # we shouldn't kill the resource tracker from multiprocessing. If we do,
- # `atexit` will throw as it uses resource tracker to try to clean up
- if cmd_lines and "resource_tracker" in cmd_lines[-1]:
- continue
- child.kill()
-
- main_thread = threading.current_thread()
- for t in threading.enumerate():
- if t is not main_thread:
- t.join(0)
-
- for child_pid in _collect_child_process_pids(os.getpid()):
- os.kill(child_pid, signal.SIGTERM)
-
-
-@pytest.fixture(autouse=True)
-def cleanup():
- from lightning.app.utilities.app_helpers import _LightningAppRef
-
- yield
- _LightningAppRef._app_instance = None
- shutil.rmtree("./storage", ignore_errors=True)
- shutil.rmtree("./.storage", ignore_errors=True)
- shutil.rmtree("./.shared", ignore_errors=True)
- if os.path.isfile(_APP_CONFIG_FILENAME):
- os.remove(_APP_CONFIG_FILENAME)
- _set_context(None)
-
-
-@pytest.fixture(autouse=True)
-def clear_app_state_state_variables():
- """Resets global variables in order to prevent interference between tests."""
- yield
- import lightning.app.utilities.state
-
- lightning.app.utilities.state._STATE = None
- lightning.app.utilities.state._LAST_STATE = None
- AppState._MY_AFFILIATION = ()
- if hasattr(cloud_compute, "_CLOUD_COMPUTE_STORE"):
- cloud_compute._CLOUD_COMPUTE_STORE.clear()
-
-
-@pytest.fixture()
-def another_tmpdir(tmp_path: Path) -> py.path.local:
- random_dir = datetime.now().strftime("%m-%d-%Y-%H-%M-%S")
- tmp_path = os.path.join(tmp_path, random_dir)
- return py.path.local(tmp_path)
-
-
-@pytest.fixture()
-def caplog(caplog):
- """Workaround for https://github.com/pytest-dev/pytest/issues/3697.
-
- Setting ``filterwarnings`` with pytest breaks ``caplog`` when ``not logger.propagate``.
-
- """
- import logging
-
- root_logger = logging.getLogger()
- root_propagate = root_logger.propagate
- root_logger.propagate = True
-
- propagation_dict = {
- name: logging.getLogger(name).propagate
- for name in logging.root.manager.loggerDict
- if name.startswith("lightning.app")
- }
- for name in propagation_dict:
- logging.getLogger(name).propagate = True
-
- yield caplog
-
- root_logger.propagate = root_propagate
- for name, propagate in propagation_dict.items():
- logging.getLogger(name).propagate = propagate
-
-
-@pytest.fixture()
-def patch_constants(request):
- """This fixture can be used with indirect parametrization to patch values in `lightning.app.core.constants` for the
- duration of a test.
-
- Example::
-
- @pytest.mark.parametrize("patch_constants", [{"LIGHTNING_CLOUDSPACE_HOST": "any"}], indirect=True)
- def test_my_stuff(patch_constants):
- ...
-
- """
- # Set constants
- old_constants = {}
- for constant, value in request.param.items():
- old_constants[constant] = getattr(constants, constant)
- setattr(constants, constant, value)
-
- yield
-
- for constant, value in old_constants.items():
- setattr(constants, constant, value)
diff --git a/tests/tests_app/core/__init__.py b/tests/tests_app/core/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/core/lightning_app/__init__.py b/tests/tests_app/core/lightning_app/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/core/lightning_app/test_configure_layout.py b/tests/tests_app/core/lightning_app/test_configure_layout.py
deleted file mode 100644
index 757cf42738b65..0000000000000
--- a/tests/tests_app/core/lightning_app/test_configure_layout.py
+++ /dev/null
@@ -1,243 +0,0 @@
-from re import escape
-from unittest import mock
-from unittest.mock import Mock
-
-import pytest
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.frontend.stream_lit import StreamlitFrontend
-from lightning.app.frontend.web import StaticWebFrontend
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.testing.helpers import EmptyFlow
-from lightning.app.utilities.imports import _IS_WINDOWS
-
-
-@pytest.mark.parametrize("return_val", [1, None, set(), "string"])
-def test_invalid_layout(return_val):
- class Root(EmptyFlow):
- def configure_layout(self):
- return return_val
-
- root = Root()
- with pytest.raises(TypeError, match=escape("The return value of configure_layout() in `Root`")):
- LightningApp(root)
-
-
-def test_invalid_layout_missing_content_key():
- class Root(EmptyFlow):
- def configure_layout(self):
- return [{"name": "one"}]
-
- root = Root()
- with pytest.raises(
- ValueError, match=escape("A dictionary returned by `Root.configure_layout()` is missing a key 'content'.")
- ):
- LightningApp(root)
-
-
-def test_invalid_layout_unsupported_content_value():
- class Root(EmptyFlow):
- def configure_layout(self):
- return [{"name": "one", "content": [1, 2, 3]}]
-
- root = Root()
-
- with pytest.raises(
- ValueError,
- match=escape("A dictionary returned by `Root.configure_layout()"),
- ):
- LightningApp(root)
-
-
-class StreamlitFrontendFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- if self.counter > 2:
- self.stop()
- self.counter += 1
-
- def configure_layout(self):
- frontend = StreamlitFrontend(render_fn=_render_streamlit_fn)
- frontend.start_server = Mock()
- frontend.stop_server = Mock()
- return frontend
-
-
-def _render_streamlit_fn():
- pass
-
-
-class StaticWebFrontendFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- if self.counter > 2:
- self.stop()
- self.counter += 1
-
- def configure_layout(self):
- frontend = StaticWebFrontend(serve_dir="a/b/c")
- frontend.start_server = Mock()
- frontend.stop_server = Mock()
- return frontend
-
-
-@pytest.mark.skipif(_IS_WINDOWS, reason="strange TimeOut exception")
-@pytest.mark.xfail(strict=False, reason="hanging... need to be fixed") # fixme
-@pytest.mark.parametrize("flow", [StaticWebFrontendFlow, StreamlitFrontendFlow])
-@mock.patch("lightning.app.runners.multiprocess.find_free_network_port")
-def test_layout_leaf_node(find_ports_mock, flow):
- find_ports_mock.side_effect = lambda: 100
- flow = flow()
- app = LightningApp(flow)
- assert flow._layout == {}
- # we copy the dict here because after we dispatch the dict will get update with new instances
- # as the layout gets updated during the loop.
- frontends = app.frontends.copy()
- MultiProcessRuntime(app).dispatch()
- assert flow.counter == 3
-
- # The target url is available for the frontend after we started the servers in dispatch
- assert flow._layout == {"target": "http://localhost:100/root"}
- assert app.frontends[flow.name].flow is flow
-
- # we start the servers for the frontends that we collected at the time of app instantiation
- frontends[flow.name].start_server.assert_called_once()
-
- # leaf layout nodes can't be changed, they stay the same from when they first got configured
- assert app.frontends[flow.name] == frontends[flow.name]
-
-
-def test_default_content_layout():
- class SimpleFlow(EmptyFlow):
- def configure_layout(self):
- frontend = StaticWebFrontend(serve_dir="a/b/c")
- frontend.start_server = Mock()
- return frontend
-
- class TestContentComponent(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.component0 = SimpleFlow()
- self.component1 = SimpleFlow()
- self.component2 = SimpleFlow()
-
- root = TestContentComponent()
- LightningApp(root)
- assert root._layout == [
- {"name": "root.component0", "content": "root.component0"},
- {"name": "root.component1", "content": "root.component1"},
- {"name": "root.component2", "content": "root.component2"},
- ]
-
-
-def test_url_content_layout():
- class TestContentComponent(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.component0 = EmptyFlow()
- self.component1 = EmptyFlow()
-
- def configure_layout(self):
- return [
- {"name": "one", "content": self.component0},
- {"name": "url", "content": "https://lightning.ai"},
- {"name": "two", "content": self.component1},
- ]
-
- root = TestContentComponent()
- LightningApp(root)
- assert root._layout == [
- {"name": "one", "content": "root.component0"},
- {"name": "url", "content": "https://lightning.ai", "target": "https://lightning.ai"},
- {"name": "two", "content": "root.component1"},
- ]
-
-
-def test_single_content_layout():
- """Test that returning a single dict also works (does not have to be returned in a list)."""
-
- class TestContentComponent(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.component0 = EmptyFlow()
- self.component1 = EmptyFlow()
-
- def configure_layout(self):
- return {"name": "single", "content": self.component1}
-
- root = TestContentComponent()
- LightningApp(root)
- assert root._layout == [{"name": "single", "content": "root.component1"}]
-
-
-class DynamicContentComponent(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.component0 = EmptyFlow()
- self.component1 = EmptyFlow()
- self.counter = 0
- self.configure_layout_called = 0
-
- def run(self):
- self.run_assertion()
- self.counter += 1
- if self.counter == 3:
- self.stop()
-
- def configure_layout(self):
- self.configure_layout_called += 1
- tabs = [
- {"name": "one", "content": self.component0},
- {"name": f"{self.counter}", "content": self.component1},
- ]
- # reverse the order of the two tabs every time the counter is odd
- if self.counter % 2 != 0:
- tabs = tabs[::-1]
- return tabs
-
- def run_assertion(self):
- """Assert that the layout changes as the counter changes its value."""
- layout_even = [
- {"name": "one", "content": "root.component0"},
- {"name": f"{self.counter}", "content": "root.component1"},
- ]
- layout_odd = layout_even[::-1]
- assert (
- self.counter % 2 == 0
- and self._layout == layout_even
- or self.counter % 2 == 1
- and self._layout == layout_odd
- )
-
-
-@pytest.mark.skipif(_IS_WINDOWS, reason="strange TimeOut exception")
-@pytest.mark.xfail(strict=False, reason="hanging... need to be fixed") # fixme
-def test_dynamic_content_layout_update():
- """Test that the `configure_layout()` gets called as part of the loop and can return new layouts."""
- flow = DynamicContentComponent()
- app = LightningApp(flow)
- MultiProcessRuntime(app).dispatch()
- assert flow.configure_layout_called == 5
-
-
-@mock.patch("lightning.app.utilities.layout.is_running_in_cloud", return_value=True)
-def test_http_url_warning(*_):
- class Root(EmptyFlow):
- def configure_layout(self):
- return [
- {"name": "warning expected", "content": "http://github.com/very/long/link/to/display"},
- {"name": "no warning expected", "content": "https://github.com"},
- ]
-
- root = Root()
-
- with pytest.warns(
- UserWarning,
- match=escape("You configured an http link http://github.com/very/long/link... but it won't be accessible"),
- ):
- LightningApp(root)
diff --git a/tests/tests_app/core/scripts/app_metadata.py b/tests/tests_app/core/scripts/app_metadata.py
deleted file mode 100644
index 7194ab2c2a649..0000000000000
--- a/tests/tests_app/core/scripts/app_metadata.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from lightning.app.core.app import LightningApp
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.work import LightningWork
-from lightning.app.frontend.web import StaticWebFrontend
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-
-
-class WorkA(LightningWork):
- def __init__(self):
- """WorkA."""
- super().__init__()
-
- def run(self):
- pass
-
-
-class WorkB(LightningWork):
- def __init__(self):
- """WorkB."""
- super().__init__(cloud_compute=CloudCompute("gpu"))
-
- def run(self):
- pass
-
-
-class FlowA(LightningFlow):
- def __init__(self):
- """FlowA Component."""
- super().__init__()
- self.work_a = WorkA()
-
- def run(self):
- pass
-
-
-class FlowB(LightningFlow):
- def __init__(self):
- """FlowB."""
- super().__init__()
- self.work_b = WorkB()
-
- def run(self):
- pass
-
- def configure_layout(self):
- return StaticWebFrontend(serve_dir=".")
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- """RootFlow."""
- super().__init__()
- self.flow_a_1 = FlowA()
- self.flow_a_2 = FlowA()
- self.flow_b = FlowB()
-
- def run(self):
- self.stop()
-
-
-app = LightningApp(RootFlow())
diff --git a/tests/tests_app/core/scripts/app_with_env.py b/tests/tests_app/core/scripts/app_with_env.py
deleted file mode 100644
index 6493a1918bb20..0000000000000
--- a/tests/tests_app/core/scripts/app_with_env.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import os
-
-from lightning.app import CloudCompute, LightningApp, LightningWork
-
-
-class MyWork(LightningWork):
- def __init__(self):
- super().__init__(cloud_compute=CloudCompute(name=os.environ.get("COMPUTE_NAME", "default")))
-
- def run(self):
- pass
-
-
-app = LightningApp(MyWork())
diff --git a/tests/tests_app/core/scripts/app_with_local_import.py b/tests/tests_app/core/scripts/app_with_local_import.py
deleted file mode 100644
index 38ff38df54419..0000000000000
--- a/tests/tests_app/core/scripts/app_with_local_import.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from app_metadata import RootFlow
-from lightning.app.core.app import LightningApp
-
-app = LightningApp(RootFlow())
diff --git a/tests/tests_app/core/scripts/empty.py b/tests/tests_app/core/scripts/empty.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/core/scripts/example_1.py b/tests/tests_app/core/scripts/example_1.py
deleted file mode 100644
index 486a0566ff80d..0000000000000
--- a/tests/tests_app/core/scripts/example_1.py
+++ /dev/null
@@ -1 +0,0 @@
-from numbers import Rational # noqa F401
diff --git a/tests/tests_app/core/scripts/example_2.py b/tests/tests_app/core/scripts/example_2.py
deleted file mode 100644
index 54e6f30908c85..0000000000000
--- a/tests/tests_app/core/scripts/example_2.py
+++ /dev/null
@@ -1 +0,0 @@
-from lightning.app import LightningApp # noqa F401
diff --git a/tests/tests_app/core/scripts/lightning_cli.py b/tests/tests_app/core/scripts/lightning_cli.py
deleted file mode 100644
index 486747206b334..0000000000000
--- a/tests/tests_app/core/scripts/lightning_cli.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from lightning.app.utilities.imports import _is_pytorch_lightning_available, _is_torch_available
-
-if _is_torch_available():
- import torch
- from torch.utils.data import DataLoader, Dataset
-
-if _is_pytorch_lightning_available():
- from pytorch_lightning import LightningDataModule, LightningModule, cli
-
-if __name__ == "__main__":
-
- class RandomDataset(Dataset):
- def __init__(self, size, length):
- self.len = length
- self.data = torch.randn(length, size)
-
- def __getitem__(self, index):
- return self.data[index]
-
- def __len__(self):
- return self.len
-
- class BoringDataModule(LightningDataModule):
- def train_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def val_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def test_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def predict_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- class BoringModel(LightningModule):
- def __init__(self):
- super().__init__()
- self.layer = torch.nn.Linear(32, 2)
-
- def forward(self, x):
- return self.layer(x)
-
- def training_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("train_loss", loss)
- return {"loss": loss}
-
- def validation_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("valid_loss", loss)
-
- def test_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("test_loss", loss)
-
- def configure_optimizers(self):
- return torch.optim.SGD(self.layer.parameters(), lr=0.1)
-
- cli.LightningCLI(BoringModel, BoringDataModule)
diff --git a/tests/tests_app/core/scripts/lightning_overrides.py b/tests/tests_app/core/scripts/lightning_overrides.py
deleted file mode 100644
index 8cb69c0fc727b..0000000000000
--- a/tests/tests_app/core/scripts/lightning_overrides.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from lightning.app.utilities.imports import _is_pytorch_lightning_available, _is_torch_available
-
-if _is_torch_available():
- from torch.utils.data import Dataset
-
-if _is_pytorch_lightning_available():
- from lightning.fabric import Fabric
- from pytorch_lightning import LightningDataModule, LightningModule, Trainer
- from pytorch_lightning.accelerators.accelerator import Accelerator
- from pytorch_lightning.callbacks import Callback
- from pytorch_lightning.loggers import Logger
- from pytorch_lightning.plugins import PrecisionPlugin
- from pytorch_lightning.profilers import Profiler
- from torchmetrics import Metric
-
-
-if __name__ == "__main__":
-
- class RandomDataset(Dataset):
- pass
-
- class BoringDataModule(LightningDataModule):
- pass
-
- class BoringModel(LightningModule):
- pass
-
- class BoringTrainer(Trainer):
- pass
-
- class BoringPrecisionPlugin(PrecisionPlugin):
- pass
-
- class BoringAccelerator(Accelerator):
- pass
-
- class BoringCallback(Callback):
- pass
-
- class BoringLogger(Logger):
- pass
-
- class BoringMetric(Metric):
- pass
-
- class BoringFabric(Fabric):
- pass
-
- class BoringProfiler(Profiler):
- pass
diff --git a/tests/tests_app/core/scripts/lightning_trainer.py b/tests/tests_app/core/scripts/lightning_trainer.py
deleted file mode 100644
index 2b9e92fabc799..0000000000000
--- a/tests/tests_app/core/scripts/lightning_trainer.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import argparse
-
-from lightning.app.utilities.imports import _is_pytorch_lightning_available, _is_torch_available
-
-if _is_torch_available():
- import torch
- from torch.utils.data import DataLoader, Dataset
-
-if _is_pytorch_lightning_available():
- import pytorch_lightning as pl
- from pytorch_lightning import LightningDataModule, LightningModule
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--max_epochs", type=int, default=10)
- args = parser.parse_args()
-
- class RandomDataset(Dataset):
- def __init__(self, size, length):
- self.len = length
- self.data = torch.randn(length, size)
-
- def __getitem__(self, index):
- return self.data[index]
-
- def __len__(self):
- return self.len
-
- class BoringDataModule(LightningDataModule):
- def train_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def val_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def test_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def predict_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- class BoringModel(LightningModule):
- def __init__(self):
- super().__init__()
- self.layer = torch.nn.Linear(32, 2)
-
- def forward(self, x):
- return self.layer(x)
-
- def training_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("train_loss", loss)
- return {"loss": loss}
-
- def validation_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("valid_loss", loss)
-
- def test_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("test_loss", loss)
-
- def configure_optimizers(self):
- return torch.optim.SGD(self.layer.parameters(), lr=0.1)
-
- model = BoringModel()
- datamodule = BoringDataModule()
- trainer = pl.Trainer(**vars(args))
- trainer.fit(model, datamodule)
-
-
-if __name__ == "__main__":
- main()
diff --git a/tests/tests_app/core/scripts/registry.py b/tests/tests_app/core/scripts/registry.py
deleted file mode 100644
index 0ae13b05af9fb..0000000000000
--- a/tests/tests_app/core/scripts/registry.py
+++ /dev/null
@@ -1,102 +0,0 @@
-from lightning.app.utilities.imports import _is_pytorch_lightning_available
-
-if _is_pytorch_lightning_available():
- import torch
- from pytorch_lightning import LightningDataModule, LightningModule
- from pytorch_lightning.cli import LightningCLI
- from torch.utils.data import DataLoader, Dataset
-
- class RandomDataset(Dataset):
- def __init__(self, size, length):
- self.len = length
- self.data = torch.randn(length, size)
-
- def __getitem__(self, index):
- return self.data[index]
-
- def __len__(self):
- return self.len
-
- class BoringDataModule(LightningDataModule):
- def __init__(self, root_folder: str = "./", batch_size: int = 32):
- super().__init__()
-
- def train_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def val_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def test_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def predict_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- class BoringDataModule2(LightningDataModule):
- def __init__(self, root_folder: str = "./", batch_size: int = 32, num_workers: int = 6):
- super().__init__()
-
- def train_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def val_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def test_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- def predict_dataloader(self):
- return DataLoader(RandomDataset(32, 64), batch_size=2)
-
- class BoringModel(LightningModule):
- def __init__(self, hidden_size: int = 16):
- super().__init__()
- self.layer = torch.nn.Linear(32, 2)
-
- def forward(self, x):
- return self.layer(x)
-
- def training_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("train_loss", loss)
- return {"loss": loss}
-
- def validation_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("valid_loss", loss)
-
- def test_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("test_loss", loss)
-
- def configure_optimizers(self):
- return torch.optim.SGD(self.layer.parameters(), lr=0.1)
-
- class BoringModel2(LightningModule):
- def __init__(self, hidden_size: int = 16, batch_norm: bool = False):
- super().__init__()
- self.layer = torch.nn.Linear(32, 2)
-
- def forward(self, x):
- return self.layer(x)
-
- def training_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("train_loss", loss)
- return {"loss": loss}
-
- def validation_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("valid_loss", loss)
-
- def test_step(self, batch, batch_idx):
- loss = self(batch).sum()
- self.log("test_loss", loss)
-
- def configure_optimizers(self):
- return torch.optim.SGD(self.layer.parameters(), lr=0.1)
-
-
-if __name__ == "__main__":
- LightningCLI()
diff --git a/tests/tests_app/core/scripts/script_with_error.py b/tests/tests_app/core/scripts/script_with_error.py
deleted file mode 100644
index b3a669f13fcf3..0000000000000
--- a/tests/tests_app/core/scripts/script_with_error.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from lightning.app import LightningApp, LightningFlow
-
-
-class EmptyFlow(LightningFlow):
- def run(self):
- pass
-
-
-if __name__ == "__main__":
- # trigger a Python exception `IndexError: list index out of range` before we can load the app
- _ = [1, 2, 3][4]
-
- app = LightningApp(EmptyFlow())
diff --git a/tests/tests_app/core/scripts/two_apps.py b/tests/tests_app/core/scripts/two_apps.py
deleted file mode 100644
index 944e11b67d67d..0000000000000
--- a/tests/tests_app/core/scripts/two_apps.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from lightning.app import LightningApp, LightningFlow
-
-
-class EmptyFlow(LightningFlow):
- def run(self):
- pass
-
-
-app_1 = LightningApp(EmptyFlow())
-app_2 = LightningApp(EmptyFlow())
diff --git a/tests/tests_app/core/test_constants.py b/tests/tests_app/core/test_constants.py
deleted file mode 100644
index df407a7d5ed71..0000000000000
--- a/tests/tests_app/core/test_constants.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import json
-import os
-from unittest import mock
-
-from lightning.app.core.constants import DistributedPluginChecker, get_lightning_cloud_url
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_CLOUD_URL": "https://beta.lightning.ai"})
-def test_defaults():
- assert get_lightning_cloud_url() == "https://beta.lightning.ai"
-
-
-def test_distributed_checker(monkeypatch):
- monkeypatch.setenv("DISTRIBUTED_ARGUMENTS", str(json.dumps({"num_instances": 2})))
- monkeypatch.setenv("LIGHTNING_CLOUD_WORK_NAME", "nodes.0")
- assert bool(DistributedPluginChecker())
-
- monkeypatch.setenv("LIGHTNING_CLOUD_WORK_NAME", "nodes.1")
- assert bool(DistributedPluginChecker())
-
- monkeypatch.setenv("LIGHTNING_CLOUD_WORK_NAME", "nodes.2")
- assert bool(DistributedPluginChecker())
-
- mock_work = mock.MagicMock()
- mock_work.name = "nodes.1"
- assert not DistributedPluginChecker().should_create_work(mock_work)
-
- mock_work.name = "nodes.2"
- assert DistributedPluginChecker().should_create_work(mock_work)
diff --git a/tests/tests_app/core/test_lightning_api.py b/tests/tests_app/core/test_lightning_api.py
deleted file mode 100644
index 3b47f02f6e208..0000000000000
--- a/tests/tests_app/core/test_lightning_api.py
+++ /dev/null
@@ -1,595 +0,0 @@
-import asyncio
-import contextlib
-import json
-import logging
-import multiprocessing as mp
-import os
-import sys
-from copy import deepcopy
-from multiprocessing import Process
-from pathlib import Path
-from time import sleep, time
-from unittest import mock
-
-import aiohttp
-import lightning.app
-import pytest
-import requests
-from deepdiff import DeepDiff, Delta
-from fastapi import HTTPException, Request
-from httpx import AsyncClient
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.api.http_methods import Post
-from lightning.app.core import api
-from lightning.app.core.api import (
- UIRefresher,
- fastapi_service,
- global_app_state_store,
- register_global_routes,
- start_server,
-)
-from lightning.app.core.constants import APP_SERVER_PORT
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.drive import Drive
-from lightning.app.testing.helpers import _MockQueue
-from lightning.app.utilities.app_status import AppStatus
-from lightning.app.utilities.component import _set_frontend_context, _set_work_context
-from lightning.app.utilities.enum import AppStage
-from lightning.app.utilities.load_app import extract_metadata_from_app
-from lightning.app.utilities.redis import check_if_redis_running
-from lightning.app.utilities.state import AppState, headers_for
-from pydantic import BaseModel
-
-register_global_routes()
-
-
-class WorkA(LightningWork):
- def __init__(self):
- super().__init__(parallel=True, start_with_flow=False)
- self.var_a = 0
- self.drive = Drive("lit://test_app_state_api")
-
- def run(self):
- state = AppState()
- assert state._my_affiliation == ("work_a",)
- # this would download and push data to the REST API.
- assert state.var_a == 0
- assert isinstance(state.drive, Drive)
- assert state.drive.component_name == "root.work_a"
-
- with open("test_app_state_api.txt", "w") as f:
- f.write("here")
- state.drive.put("test_app_state_api.txt")
- state.var_a = -1
-
-
-class _A(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work_a = WorkA()
-
- def run(self):
- if self.work_a.var_a == -1:
- self.stop()
- self.work_a.run()
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="too slow on Windows or macOs")
-def test_app_state_api():
- """This test validates the AppState can properly broadcast changes from work within its own process."""
- app = LightningApp(_A(), log_level="debug")
- MultiProcessRuntime(app, start_server=True).dispatch()
- assert app.root.work_a.var_a == -1
- _set_work_context()
- assert app.root.work_a.drive.list(".") == ["test_app_state_api.txt"]
- _set_frontend_context()
- assert app.root.work_a.drive.list(".") == ["test_app_state_api.txt"]
- os.remove("test_app_state_api.txt")
-
-
-class A2(LightningFlow):
- def __init__(self):
- super().__init__()
- self.var_a = 0
- self.a = _A()
-
- def update_state(self):
- state = AppState()
- # this would download and push data to the REST API.
- assert state.a.work_a.var_a == 0
- assert state.var_a == 0
- state.var_a = -1
-
- def run(self):
- if self.var_a == 0:
- self.update_state()
- elif self.var_a == -1:
- self.stop()
-
-
-@pytest.mark.skipif(sys.platform == "win32" or sys.platform == "darwin", reason="too slow on Windows or macOs")
-def test_app_state_api_with_flows():
- """This test validates the AppState can properly broadcast changes from flows."""
- app = LightningApp(A2(), log_level="debug")
- MultiProcessRuntime(app, start_server=True).dispatch()
- assert app.root.var_a == -1
-
-
-class NestedFlow(LightningFlow):
- def run(self):
- pass
-
- def configure_layout(self):
- return {"name": "main", "content": "https://te"}
-
-
-class FlowA(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
- self.flow = NestedFlow()
- self.dict = lightning.app.structures.Dict(**{"0": NestedFlow()})
- self.list = lightning.app.structures.List(*[NestedFlow()])
-
- def run(self):
- self.counter += 1
- if self.counter >= 3:
- self.stop()
-
- def configure_layout(self):
- return [
- {"name": "main_1", "content": "https://te"},
- {"name": "main_2", "content": self.flow},
- {"name": "main_3", "content": self.dict["0"]},
- {"name": "main_4", "content": self.list[0]},
- ]
-
-
-class AppStageTestingApp(LightningApp):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.counter_running = 0
- self.counter_stopped = 0
- self.counter = 0
-
- def _change_stage(self, enum):
- previous_state = deepcopy(self.state)
- current_state = self.state
- current_state["app_state"]["stage"] = enum.value
- deep_diff = DeepDiff(previous_state, current_state, verbose_level=2)
- self.api_delta_queue.put(Delta(deep_diff))
-
- def maybe_apply_changes(self):
- if self.counter_stopped == 1 and self.counter_running == 1:
- if self.counter == 0:
- self._change_stage(AppStage.RUNNING)
- self.counter += 1
- if self.counter == 3:
- self._change_stage(AppStage.STOPPING)
-
- # emulate pending from the UI.
- elif self.stage == AppStage.BLOCKING:
- self._change_stage(AppStage.RUNNING)
- self.counter_running += 1
-
- elif self.root.counter == 2:
- self._change_stage(AppStage.RESTARTING)
- self.counter_stopped += 1
-
- super().maybe_apply_changes()
-
-
-# FIXME: This test doesn't assert anything
-@pytest.mark.xfail(strict=False, reason="TODO: Resolve flaky test.")
-def test_app_stage_from_frontend():
- """This test validates that delta from the `api_delta_queue` manipulating the ['app_state']['stage'] would start
- and stop the app."""
- app = AppStageTestingApp(FlowA(), log_level="debug")
- app.stage = AppStage.BLOCKING
- MultiProcessRuntime(app, start_server=True).dispatch()
-
-
-def test_update_publish_state_and_maybe_refresh_ui():
- """This test checks that the method properly:
-
- - receives the state from the `publish_state_queue` and populates the app_state_store
- - receives a notification to refresh the UI and makes a GET Request (streamlit).
-
- """
- app = AppStageTestingApp(FlowA(), log_level="debug")
- publish_state_queue = _MockQueue("publish_state_queue")
- api_response_queue = _MockQueue("api_response_queue")
-
- publish_state_queue.put((app.state_with_changes, None))
-
- thread = UIRefresher(publish_state_queue, api_response_queue)
- thread.run_once()
-
- assert global_app_state_store.get_app_state("1234") == app.state_with_changes
- global_app_state_store.remove("1234")
- global_app_state_store.add("1234")
-
-
-@pytest.mark.parametrize("x_lightning_type", ["DEFAULT", "STREAMLIT"])
-@pytest.mark.anyio()
-async def test_start_server(x_lightning_type, monkeypatch):
- """This test relies on FastAPI TestClient and validates that the REST API properly provides:
-
- - the state on GET /api/v1/state
- - push a delta when making a POST request to /api/v1/state
-
- """
-
- class InfiniteQueue(_MockQueue):
- def get(self, timeout: int = 0):
- return self._queue[0]
-
- app = AppStageTestingApp(FlowA(), log_level="debug")
- app._update_layout()
- app.stage = AppStage.BLOCKING
- publish_state_queue = InfiniteQueue("publish_state_queue")
- change_state_queue = _MockQueue("change_state_queue")
- has_started_queue = _MockQueue("has_started_queue")
- api_response_queue = _MockQueue("api_response_queue")
- state = app.state_with_changes
- publish_state_queue.put((state, AppStatus(is_ui_ready=True, work_statuses={})))
- spec = extract_metadata_from_app(app)
- ui_refresher = start_server(
- publish_state_queue,
- change_state_queue,
- api_response_queue,
- has_started_queue=has_started_queue,
- uvicorn_run=False,
- spec=spec,
- )
- headers = headers_for({"type": x_lightning_type})
-
- async with AsyncClient(app=fastapi_service, base_url="http://test") as client:
- with pytest.raises(Exception, match="X-Lightning-Session-UUID"):
- await client.get("/api/v1/spec")
-
- with pytest.raises(Exception, match="X-Lightning-Session-ID"):
- await client.get("/api/v1/spec", headers={"X-Lightning-Session-UUID": headers["X-Lightning-Session-UUID"]})
-
- response = await client.get("/api/v1/spec", headers=headers)
- assert response.json() == spec
-
- with pytest.raises(Exception, match="X-Lightning-Session-UUID"):
- await client.get("/api/v1/state")
-
- with pytest.raises(Exception, match="X-Lightning-Session-ID"):
- await client.get("/api/v1/state", headers={"X-Lightning-Session-UUID": headers["X-Lightning-Session-UUID"]})
-
- response = await client.get("/api/v1/state", headers=headers)
- assert response.json() == state
- assert response.status_code == 200
-
- new_state = deepcopy(state)
- new_state["vars"]["counter"] += 1
-
- with pytest.raises(Exception, match="X-Lightning-Session-UUID"):
- await client.post("/api/v1/state")
-
- with pytest.raises(Exception, match="X-Lightning-Session-ID"):
- await client.post(
- "/api/v1/state", headers={"X-Lightning-Session-UUID": headers["X-Lightning-Session-UUID"]}
- )
-
- response = await client.post("/api/v1/state", json={"stage": "running"}, headers=headers)
- assert change_state_queue._queue[0].to_dict() == {
- "values_changed": {"root['app_state']['stage']": {"new_value": "running"}}
- }
- assert response.status_code == 200
-
- response = await client.get("/api/v1/layout")
- assert json.loads(response.json()) == [
- {"name": "main_1", "content": "https://te", "target": "https://te"},
- {"name": "main_2", "content": "https://te"},
- {"name": "main_3", "content": "https://te"},
- {"name": "main_4", "content": "https://te"},
- ]
-
- response = await client.get("/api/v1/status")
- assert response.json() == {"is_ui_ready": True, "work_statuses": {}}
-
- response = await client.post("/api/v1/state", json={"state": new_state}, headers=headers)
- assert change_state_queue._queue[1].to_dict() == {
- "values_changed": {"root['vars']['counter']": {"new_value": 1}}
- }
- assert response.status_code == 200
-
- response = await client.post(
- "/api/v1/delta",
- json={
- "delta": {
- "values_changed": {"root['flows']['video_search']['vars']['should_process']": {"new_value": True}}
- }
- },
- headers=headers,
- )
- assert change_state_queue._queue[2].to_dict() == {
- "values_changed": {"root['flows']['video_search']['vars']['should_process']": {"new_value": True}}
- }
- assert response.status_code == 200
-
- monkeypatch.setattr(api, "ENABLE_PULLING_STATE_ENDPOINT", False)
-
- response = await client.get("/api/v1/state", headers=headers)
- assert response.status_code == 405
-
- response = await client.post("/api/v1/state", json={"state": new_state}, headers=headers)
- assert response.status_code == 200
-
- monkeypatch.setattr(api, "ENABLE_PUSHING_STATE_ENDPOINT", False)
-
- response = await client.post("/api/v1/state", json={"state": new_state}, headers=headers)
- assert response.status_code == 405
-
- response = await client.post(
- "/api/v1/delta",
- json={
- "delta": {
- "values_changed": {"root['flows']['video_search']['vars']['should_process']": {"new_value": True}}
- }
- },
- headers=headers,
- )
- assert change_state_queue._queue[2].to_dict() == {
- "values_changed": {"root['flows']['video_search']['vars']['should_process']": {"new_value": True}}
- }
- assert response.status_code == 405
-
- # used to clean the app_state_store to following test.
- global_app_state_store.remove("1234")
- global_app_state_store.add("1234")
-
- del client
- ui_refresher.join(0)
-
-
-@pytest.mark.parametrize(
- ("path", "expected_status_code"), [("/api/v1", 404), ("/api/v1/asdf", 404), ("/api/asdf", 404), ("/api", 404)]
-)
-@pytest.mark.anyio()
-async def test_state_api_routes(path, expected_status_code):
- async with AsyncClient(app=fastapi_service, base_url="http://test") as client:
- response = await client.get(path)
- assert response.status_code == expected_status_code
-
-
-@pytest.mark.skipif(not check_if_redis_running(), reason="redis not running")
-@pytest.mark.anyio()
-async def test_health_endpoint_success():
- global_app_state_store.store = {}
- global_app_state_store.add("1234")
- async with AsyncClient(app=fastapi_service, base_url="http://test") as client:
- # will respond 503 if redis is not running
- response = await client.get("/healthz")
- assert response.status_code == 500
- assert response.json() == {"status": "failure", "reason": "State is empty {}"}
- global_app_state_store.set_app_state("1234", {"state": None})
- response = await client.get("/healthz")
- assert response.status_code == 200
- assert response.json() == {"status": "ok"}
- global_app_state_store.remove("1234")
- global_app_state_store.store = {}
- global_app_state_store.add("1234")
-
-
-@pytest.mark.skipif(
- check_if_redis_running(), reason="this is testing the failure condition " "for which the redis should not run"
-)
-@pytest.mark.anyio()
-async def test_health_endpoint_failure(monkeypatch):
- monkeypatch.setenv("LIGHTNING_APP_STATE_URL", "http://someurl") # adding this to make is_running_in_cloud pass
- monkeypatch.setitem(os.environ, "LIGHTNING_CLOUD_QUEUE_TYPE", "redis")
- async with AsyncClient(app=fastapi_service, base_url="http://test") as client:
- # will respond 503 if redis is not running
- response = await client.get("/healthz")
- assert response.status_code == 500
-
-
-@pytest.mark.parametrize(
- ("path", "expected_status_code"),
- [
- ("/", 200),
- ("/asdf", 200),
- ("/view/component_a", 200),
- ],
-)
-@pytest.mark.anyio()
-async def test_frontend_routes(path, expected_status_code):
- async with AsyncClient(app=fastapi_service, base_url="http://test") as client:
- response = await client.get(path)
- assert response.status_code == expected_status_code
-
-
-@pytest.mark.xfail(sys.platform == "linux", strict=False, reason="No idea why... need to be fixed") # fixme
-def test_start_server_started():
- """This test ensures has_started_queue receives a signal when the REST API has started."""
- api_publish_state_queue = mp.Queue()
- api_delta_queue = mp.Queue()
- has_started_queue = mp.Queue()
- api_response_queue = mp.Queue()
- kwargs = {
- "api_publish_state_queue": api_publish_state_queue,
- "api_delta_queue": api_delta_queue,
- "has_started_queue": has_started_queue,
- "api_response_queue": api_response_queue,
- "port": 1111,
- "root_path": "",
- }
-
- server_proc = mp.Process(target=start_server, kwargs=kwargs)
- server_proc.start()
- # requires to wait for the UI to be clicked on.
-
- # wait for server to be ready
- assert has_started_queue.get() == "SERVER_HAS_STARTED"
- server_proc.kill()
-
-
-@mock.patch("uvicorn.run")
-@mock.patch("lightning.app.core.api.UIRefresher")
-@pytest.mark.parametrize("host", ["http://0.0.0.1", "0.0.0.1"])
-def test_start_server_info_message(ui_refresher, uvicorn_run, caplog, monkeypatch, host):
- api_publish_state_queue = _MockQueue()
- api_delta_queue = _MockQueue()
- has_started_queue = _MockQueue()
- api_response_queue = _MockQueue()
- kwargs = {
- "host": host,
- "port": 1111,
- "api_publish_state_queue": api_publish_state_queue,
- "api_delta_queue": api_delta_queue,
- "has_started_queue": has_started_queue,
- "api_response_queue": api_response_queue,
- "root_path": "test",
- }
-
- monkeypatch.setattr(api, "logger", logging.getLogger())
-
- with caplog.at_level(logging.INFO):
- start_server(**kwargs)
-
- assert "Your app has started. View it in your browser: http://0.0.0.1:1111/view" in caplog.text
-
- ui_refresher.assert_called_once()
- uvicorn_run.assert_called_once_with(host="0.0.0.1", port=1111, log_level="error", app=mock.ANY, root_path="test")
-
-
-class InputRequestModel(BaseModel):
- index: int
- name: str
-
-
-class OutputRequestModel(BaseModel):
- name: str
- counter: int
-
-
-async def handler():
- print("Has been called")
- return "Hello World !"
-
-
-class FlowAPI(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- if self.counter == 501:
- self.stop()
-
- def request(self, config: InputRequestModel, request: Request) -> OutputRequestModel:
- self.counter += 1
- if config.index % 5 == 0:
- raise HTTPException(status_code=400, detail="HERE")
- assert request.body()
- assert request.json()
- assert request.headers
- assert request.method
- return OutputRequestModel(name=config.name, counter=self.counter)
-
- def configure_api(self):
- return [Post("/api/v1/request", self.request), Post("/api/v1/handler", handler)]
-
-
-def target():
- app = LightningApp(FlowAPI())
- MultiProcessRuntime(app).dispatch()
-
-
-async def async_request(url: str, data: InputRequestModel):
- async with aiohttp.ClientSession() as session, session.post(url, json=data.dict()) as result:
- return await result.json()
-
-
-@pytest.mark.xfail(strict=False, reason="No idea why... need to be fixed") # fixme
-def test_configure_api():
- # Setup
- process = Process(target=target)
- process.start()
- time_left = 15
- while time_left > 0:
- try:
- requests.get(f"http://localhost:{APP_SERVER_PORT}/healthz")
- break
- except requests.exceptions.ConnectionError:
- sleep(0.1)
- time_left -= 0.1
-
- # Test Upload File
- with open(__file__, "rb") as fo:
- files = {"uploaded_file": fo}
-
- response = requests.put(f"http://localhost:{APP_SERVER_PORT}/api/v1/upload_file/test", files=files)
- assert response.json() == "Successfully uploaded 'test' to the Drive"
-
- url = f"http://localhost:{APP_SERVER_PORT}/api/v1/request"
-
- N = 500
- coros = []
- for index in range(N):
- coros.append(async_request(url, InputRequestModel(index=index, name="hello")))
-
- t0 = time()
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
- results = loop.run_until_complete(asyncio.gather(*coros))
- response_time = time() - t0
- print(f"RPS: {N / response_time}")
- assert response_time < 10
- assert len(results) == N
- assert all(r.get("detail", None) == ("HERE" if i % 5 == 0 else None) for i, r in enumerate(results))
-
- response = requests.post(f"http://localhost:{APP_SERVER_PORT}/api/v1/handler")
- assert response.status_code == 200
-
- # Stop the Application
- with contextlib.suppress(Exception):
- response = requests.post(url, json=InputRequestModel(index=0, name="hello").dict())
-
- # Teardown
- time_left = 5
- while time_left > 0:
- if process.exitcode == 0:
- break
- sleep(0.1)
- time_left -= 0.1
- assert process.exitcode == 0
- process.kill()
-
-
-@pytest.mark.anyio()
-@mock.patch("lightning.app.core.api.UIRefresher", mock.MagicMock())
-async def test_get_annotations(tmpdir):
- cwd = os.getcwd()
- os.chdir(tmpdir)
-
- Path("lightning-annotations.json").write_text('[{"test": 3}]')
-
- try:
- app = AppStageTestingApp(FlowA(), log_level="debug")
- app._update_layout()
- app.stage = AppStage.BLOCKING
- change_state_queue = _MockQueue("change_state_queue")
- has_started_queue = _MockQueue("has_started_queue")
- api_response_queue = _MockQueue("api_response_queue")
- spec = extract_metadata_from_app(app)
- start_server(
- None,
- change_state_queue,
- api_response_queue,
- has_started_queue=has_started_queue,
- uvicorn_run=False,
- spec=spec,
- )
-
- async with AsyncClient(app=fastapi_service, base_url="http://test") as client:
- response = await client.get("/api/v1/annotations")
- assert response.json() == [{"test": 3}]
- finally:
- # Cleanup
- os.chdir(cwd)
diff --git a/tests/tests_app/core/test_lightning_app.py b/tests/tests_app/core/test_lightning_app.py
deleted file mode 100644
index 70426ee152bb4..0000000000000
--- a/tests/tests_app/core/test_lightning_app.py
+++ /dev/null
@@ -1,1215 +0,0 @@
-import contextlib
-import logging
-import os
-import pickle
-from re import escape
-from time import sleep, time
-from unittest import mock
-
-import pytest
-from deepdiff import Delta
-from lightning.app import CloudCompute, LightningApp, LightningFlow, LightningWork # F401
-from lightning.app.api.request_types import _DeltaRequest
-from lightning.app.core.constants import (
- FLOW_DURATION_SAMPLES,
- FLOW_DURATION_THRESHOLD,
- REDIS_QUEUES_READ_DEFAULT_TIMEOUT,
- STATE_UPDATE_TIMEOUT,
-)
-from lightning.app.core.queues import BaseQueue, MultiProcessQueue, RedisQueue
-from lightning.app.frontend import StreamlitFrontend
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.path import Path, _storage_root_dir
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.testing.testing import LightningTestApp
-from lightning.app.utilities.app_helpers import affiliation
-from lightning.app.utilities.enum import AppStage, WorkStageStatus, WorkStopReasons
-from lightning.app.utilities.packaging import cloud_compute
-from lightning.app.utilities.redis import check_if_redis_running
-from lightning.app.utilities.warnings import LightningFlowWarning
-from lightning_utilities.core.imports import RequirementCache
-from pympler import asizeof
-
-from tests_app import _PROJECT_ROOT
-
-_STREAMLIT_AVAILABLE = RequirementCache("streamlit")
-
-logger = logging.getLogger()
-
-
-def test_lightning_app_requires_root_run_method():
- """Test that a useful exception is raised if the root flow does not override the run method."""
- with pytest.raises(
- TypeError, match=escape("The root flow passed to `LightningApp` does not override the `run()` method")
- ):
- LightningApp(LightningFlow())
-
- class FlowWithoutRun(LightningFlow):
- pass
-
- with pytest.raises(
- TypeError, match=escape("The root flow passed to `LightningApp` does not override the `run()` method")
- ):
- LightningApp(FlowWithoutRun())
-
- class FlowWithRun(LightningFlow):
- def run(self):
- pass
-
- LightningApp(FlowWithRun()) # no error
-
-
-class B1(LightningFlow):
- def __init__(self):
- super().__init__()
-
- def run(self):
- pass
-
-
-class A1(LightningFlow):
- def __init__(self):
- super().__init__()
- self.b = B1()
-
- def run(self):
- pass
-
-
-class Work(LightningWork):
- def __init__(self, cache_calls: bool = True):
- super().__init__(cache_calls=cache_calls)
- self.counter = 0
- self.has_finished = False
-
- def run(self):
- self.counter = self.counter + 1
- if self.cache_calls or self.counter >= 3:
- self.has_finished = True
-
-
-class SimpleFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work_a = Work(cache_calls=True)
- self.work_b = Work(cache_calls=False)
-
- def run(self):
- if self.work_a.has_finished and self.work_b.has_finished:
- self.stop()
- self.work_a.run()
- self.work_b.run()
-
-
-def test_simple_app(tmpdir):
- comp = SimpleFlow()
- app = LightningApp(comp, log_level="debug")
- assert app.root == comp
- expected = {
- "app_state": mock.ANY,
- "vars": {"_layout": mock.ANY, "_paths": {}},
- "calls": {},
- "flows": {},
- "structures": {},
- "works": {
- "work_b": {
- "vars": {
- "has_finished": False,
- "counter": 0,
- "_cloud_compute": mock.ANY,
- "_host": mock.ANY,
- "_url": "",
- "_future_url": "",
- "_internal_ip": "",
- "_public_ip": "",
- "_paths": {},
- "_port": None,
- "_restarting": False,
- "_display_name": "",
- },
- "calls": {"latest_call_hash": None},
- "changes": {},
- },
- "work_a": {
- "vars": {
- "has_finished": False,
- "counter": 0,
- "_cloud_compute": mock.ANY,
- "_host": mock.ANY,
- "_url": "",
- "_future_url": "",
- "_internal_ip": "",
- "_public_ip": "",
- "_paths": {},
- "_port": None,
- "_restarting": False,
- "_display_name": "",
- },
- "calls": {"latest_call_hash": None},
- "changes": {},
- },
- },
- "changes": {},
- }
- assert app.state == expected
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- assert comp.work_a.has_finished
- assert comp.work_b.has_finished
- # possible the `work_a` takes for ever to
- # start and `work_b` has already completed multiple iterations.
- assert comp.work_a.counter == 1
- assert comp.work_b.counter >= 3
-
-
-class WorkCounter(LightningWork):
- def __init__(self):
- super().__init__()
- self.c = 0
-
- def run(self):
- self.c = 1
-
-
-class E(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_e = WorkCounter()
-
- def run(self):
- self.w_e.run()
-
-
-class D(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_d = WorkCounter()
- self.e = E()
-
- def run(self):
- self.w_d.run()
- self.e.run()
-
-
-class C(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_c = WorkCounter()
- self.d = D()
-
- def run(self):
- self.w_c.run()
- self.d.run()
-
-
-class B(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_b = WorkCounter()
- self.c = C()
-
- def run(self):
- self.w_b.run()
- self.c.run()
-
-
-class A(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_a = WorkCounter()
- self.b = B()
-
- def run(self):
- self.w_a.run()
- self.b.run()
- if self.b.c.d.e.w_e.c == 1:
- self.stop()
-
-
-def test_nested_component_names():
- root = A()
- assert root.name == "root"
- assert root.w_a.name == "root.w_a"
- assert root.b.name == "root.b"
- assert root.b.w_b.name == "root.b.w_b"
- assert root.b.c.name == "root.b.c"
- assert root.b.c.w_c.name == "root.b.c.w_c"
- assert root.b.c.d.name == "root.b.c.d"
- assert root.b.c.d.e.name == "root.b.c.d.e"
- assert root.b.c.d.e.w_e.name == "root.b.c.d.e.w_e"
- assert root.b.c.d.w_d.name == "root.b.c.d.w_d"
-
-
-def test_get_component_by_name():
- app = LightningApp(A())
- assert app.root in app.flows
- assert app.get_component_by_name("root") is app.root
- assert app.get_component_by_name("root.b") is app.root.b
- assert app.get_component_by_name("root.w_a") is app.root.w_a
- assert app.get_component_by_name("root.b.w_b") is app.root.b.w_b
- assert app.get_component_by_name("root.b.c.d.e") is app.root.b.c.d.e
-
-
-def test_get_component_by_name_raises():
- app = LightningApp(A())
-
- for name in ("", "ro", "roott"):
- with pytest.raises(ValueError, match=f"Invalid component name {name}."):
- app.get_component_by_name(name)
-
- with pytest.raises(AttributeError, match="Component 'root' has no child component with name ''"):
- app.get_component_by_name("root.")
-
- with pytest.raises(AttributeError, match="Component 'root' has no child component with name 'x'"):
- app.get_component_by_name("root.x")
-
- with pytest.raises(AttributeError, match="Component 'root.b' has no child component with name 'x'"):
- app.get_component_by_name("root.b.x")
-
- with pytest.raises(AttributeError, match="Component 'root.b.w_b' has no child component with name 'c'"):
- app.get_component_by_name("root.b.w_b.c")
-
-
-def test_nested_component():
- app = LightningApp(A(), log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.w_a.c == 1
- assert app.root.b.w_b.c == 1
- assert app.root.b.c.w_c.c == 1
- assert app.root.b.c.d.w_d.c == 1
- assert app.root.b.c.d.e.w_e.c == 1
-
-
-class WorkCCC(LightningWork):
- def run(self):
- pass
-
-
-class CC(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work_cc = WorkCCC()
-
- def run(self):
- pass
-
-
-class BB(LightningFlow):
- def __init__(self):
- super().__init__()
- self.c1 = CC()
- self.c2 = CC()
-
- def run(self):
- pass
-
-
-class AA(LightningFlow):
- def __init__(self):
- super().__init__()
- self.b = BB()
-
- def run(self):
- pass
-
-
-def test_component_affiliation():
- app = LightningApp(AA())
- a_affiliation = affiliation(app.root)
- assert a_affiliation == ()
- b_affiliation = affiliation(app.root.b)
- assert b_affiliation == ("b",)
- c1_affiliation = affiliation(app.root.b.c1)
- assert c1_affiliation == ("b", "c1")
- c2_affiliation = affiliation(app.root.b.c2)
- assert c2_affiliation == ("b", "c2")
- work_cc_affiliation = affiliation(app.root.b.c2.work_cc)
- assert work_cc_affiliation == ("b", "c2", "work_cc")
-
-
-class Work4(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.var_a = 0
- self.has_finished = False
-
- def run(self):
- self.var_a = 1
- sleep(2)
- # This would never been reached as the app would exit before
- self.has_finished = True
-
-
-class A4(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = Work4()
-
- def run(self):
- self.work.run()
- if self.work.var_a == 1:
- self.stop()
-
-
-@pytest.mark.parametrize("runtime_cls", [MultiProcessRuntime])
-def test_setattr_multiprocessing(runtime_cls, tmpdir):
- app = LightningApp(A4())
- runtime_cls(app, start_server=False).dispatch()
- assert app.root.work.var_a == 1
- assert not app.root.work.has_finished
-
-
-class CounterFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class SimpleApp2(LightningApp):
- def run_once(self):
- if self.root.counter == 5:
- self.stage = AppStage.RESTARTING
- return super().run_once()
-
- def _apply_restarting(self):
- super()._apply_restarting()
- assert self.stage == AppStage.BLOCKING
- return True
-
-
-def test_app_restarting_move_to_blocking(tmpdir):
- """Validates sending restarting move the app to blocking again."""
- app = SimpleApp2(CounterFlow(), log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class FlowWithFrontend(LightningFlow):
- def run(self):
- pass
-
- def configure_layout(self):
- return StreamlitFrontend(render_fn=lambda _: None)
-
-
-class AppWithFrontend(LightningApp):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.run_once_call_count = 0
-
- def run_once(self):
- # by the time run_once gets called the first time, the target_url for the frontend should be set
- # and be present in both the LightningApp.state and the LightningApp._original_state
- assert self.state["vars"]["_layout"]["target"].startswith("http://localhost")
- assert self._original_state["vars"]["_layout"]["target"].startswith("http://localhost")
- assert self.run_once_call_count or self.state == self._original_state
-
- self.run_once_call_count += 1
- if self.run_once_call_count == 3:
- return True, 0.0
- return super().run_once()
-
-
-@pytest.mark.skipif(not _STREAMLIT_AVAILABLE, reason="requires streamlit")
-@mock.patch("lightning.app.frontend.stream_lit.StreamlitFrontend.start_server")
-@mock.patch("lightning.app.frontend.stream_lit.StreamlitFrontend.stop_server")
-def test_app_starts_with_complete_state_copy(_, __):
- """Test that the LightningApp captures the initial state in a separate copy when _run() gets called."""
- app = AppWithFrontend(FlowWithFrontend(), log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.run_once_call_count == 3
-
-
-class EmptyFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- pass
-
-
-@pytest.mark.parametrize(
- ("queue_type_cls", "default_timeout"),
- [
- (MultiProcessQueue, STATE_UPDATE_TIMEOUT),
- pytest.param(
- RedisQueue,
- REDIS_QUEUES_READ_DEFAULT_TIMEOUT,
- marks=pytest.mark.skipif(not check_if_redis_running(), reason="Redis is not running"),
- ),
- ],
-)
-@pytest.mark.parametrize(
- ("sleep_time", "expect"),
- [
- (0, 9),
- pytest.param(9, 10.0, marks=pytest.mark.xfail(strict=False, reason="failing...")), # fixme
- ],
-)
-@pytest.mark.flaky(reruns=5)
-def test_lightning_app_aggregation_speed(default_timeout, queue_type_cls: BaseQueue, sleep_time, expect):
- """This test validates the `_collect_deltas_from_ui_and_work_queues` can aggregate multiple delta together in a
- time window."""
-
- class SlowQueue(queue_type_cls):
- def batch_get(self, timeout, count):
- out = super().get(timeout)
- sleep(sleep_time)
- return [out]
-
- app = LightningApp(EmptyFlow())
-
- app.delta_queue = SlowQueue("api_delta_queue", default_timeout)
- if queue_type_cls is RedisQueue:
- app.delta_queue.clear()
-
- def make_delta(i):
- return _DeltaRequest(Delta({"values_changed": {"root['vars']['counter']": {"new_value": i}}}))
-
- # flowed the queue with mocked delta
- for i in range(expect + 10):
- app.delta_queue.put(make_delta(i))
-
- # Wait for a bit because multiprocessing.Queue doesn't run in the same thread and takes some time for writes
- sleep(0.001)
-
- delta = app._collect_deltas_from_ui_and_work_queues()[-1]
- generated = delta.to_dict()["values_changed"]["root['vars']['counter']"]["new_value"]
- if sleep_time:
- assert generated == expect, (generated, expect)
- else:
- # validate the flow should have aggregated at least expect.
- assert generated > expect
-
-
-def test_lightning_app_aggregation_empty():
- """Verify the while loop exits before `state_accumulate_wait` is reached if no deltas are found."""
-
- class SlowQueue(MultiProcessQueue):
- def get(self, timeout):
- return super().get(timeout)
-
- app = LightningApp(EmptyFlow())
- app.delta_queue = SlowQueue("api_delta_queue", 0)
- t0 = time()
- assert app._collect_deltas_from_ui_and_work_queues() == []
- delta = time() - t0
- assert delta < app.state_accumulate_wait + 0.05, delta
-
-
-class SimpleFlow2(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- if self.counter < 2:
- self.counter += 1
-
-
-def test_maybe_apply_changes_from_flow():
- """This test validates the app `_updated` is set to True only if the state was changed in the flow."""
- app = LightningApp(SimpleFlow2())
- app.delta_queue = MultiProcessQueue("a", 0)
- assert app._has_updated
- app.maybe_apply_changes()
- app.root.run()
- app.maybe_apply_changes()
- assert app._has_updated
- app._has_updated = False
- app.root.run()
- app.maybe_apply_changes()
- assert app._has_updated
- app._has_updated = False
- app.root.run()
- app.maybe_apply_changes()
- assert not app._has_updated
-
-
-class SimpleWork(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False, parallel=True)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class FlowA(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work_a = SimpleWork()
- self.work_b = SimpleWork()
-
- def run(self):
- if self.work_a.counter == self.work_b.counter == 0:
- self.work_a.run()
- self.work_b.run()
-
-
-class SuccessException(Exception):
- pass
-
-
-class CheckpointLightningApp(LightningApp):
- def _dump_checkpoint(self):
- super()._dump_checkpoint()
- raise SuccessException
-
-
-@pytest.mark.flaky(reruns=3)
-def test_snap_shotting():
- with contextlib.suppress(SuccessException):
- app = CheckpointLightningApp(FlowA())
- app.checkpointing = True
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- checkpoint_dir = os.path.join(_storage_root_dir(), "checkpoints")
- checkpoints = os.listdir(checkpoint_dir)
- assert len(checkpoints) == 1
- with open(os.path.join(checkpoint_dir, checkpoints[0]), "rb") as f:
- state = pickle.load(f)
- assert state["works"]["work_a"]["vars"]["counter"] == 1
- assert state["works"]["work_b"]["vars"]["counter"] == 1
-
-
-class CounterWork(LightningWork):
- def __init__(self, parallel: bool, cache_calls: bool):
- super().__init__(parallel=parallel, cache_calls=cache_calls)
- self.counter = 0
-
- def run(self, counter=0):
- self.counter += 1
-
-
-class WaitForAllFlow(LightningFlow):
- def __init__(self, use_same_args):
- super().__init__()
- counter = 0
- self.use_same_args = use_same_args
- for parallel in [False, True]:
- for cache_calls in [False, True]:
- work = CounterWork(parallel=parallel, cache_calls=cache_calls)
- setattr(self, f"work_{counter}", work)
- counter += 1
- self.c = 0
-
- def run(self):
- next_c = self.c + 1
- for work in self.experimental_iterate(self.works(), run_once=False):
- if work.num_successes < (next_c):
- if not self.use_same_args:
- work.run(self.c)
- else:
- work.run(None)
-
- expected = 1 if self.use_same_args else next_c
-
- if not all(w.num_successes == (expected if w.cache_calls else next_c) for w in self.works()):
- return
-
- self.c += 1
- assert [w.counter for w in self.works()] == [self.c, expected, self.c, expected]
- if self.c > 3:
- self.stop()
-
-
-# TODO (tchaton) Resolve this test.
-@pytest.mark.skipif(True, reason="timeout with system crash")
-@pytest.mark.xfail(strict=False, reason="flaky test which never terminates")
-@pytest.mark.parametrize("runtime_cls", [MultiProcessRuntime])
-@pytest.mark.parametrize("use_same_args", [True])
-# todo: removed test_state_wait_for_all_all_works[False-MultiProcessRuntime] as it hangs
-def test_state_wait_for_all_all_works(tmpdir, runtime_cls, use_same_args):
- app = LightningApp(WaitForAllFlow(use_same_args))
- runtime_cls(app, start_server=False).dispatch()
-
-
-class CheckpointCounter(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class CheckpointFlow(LightningFlow):
- def __init__(self, work: CheckpointCounter, depth=0):
- super().__init__()
- self.depth = depth
-
- if depth == 0:
- self.counter = 0
-
- if depth >= 10:
- self.work = work
- else:
- self.flow = CheckpointFlow(work, depth + 1)
-
- def run(self):
- if self.works()[0].counter == 5:
- self.stop()
-
- if self.depth >= 10:
- self.work.run()
- else:
- self.flow.run()
-
-
-@pytest.mark.skipif(True, reason="reloading isn't properly supported")
-def test_lightning_app_checkpointing_with_nested_flows():
- work = CheckpointCounter()
- app = LightningApp(CheckpointFlow(work))
- app.checkpointing = True
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- assert app.root.flow.flow.flow.flow.flow.flow.flow.flow.flow.flow.work.counter == 5
-
- work = CheckpointCounter()
- app = LightningApp(CheckpointFlow(work))
- assert app.root.flow.flow.flow.flow.flow.flow.flow.flow.flow.flow.work.counter == 0
-
- app.load_state_dict_from_checkpoint_dir(app.checkpoint_dir)
- # The counter was increment to 6 after the latest checkpoints was created.
- assert app.root.flow.flow.flow.flow.flow.flow.flow.flow.flow.flow.work.counter == 5
-
-
-@pytest.mark.skipif(True, reason="depreceated")
-@pytest.mark.xfail(strict=False, reason="test is skipped because CI was blocking all the PRs.")
-def test_load_state_dict_from_checkpoint_dir(tmpdir):
- work = CheckpointCounter()
- app = LightningApp(CheckpointFlow(work))
-
- checkpoints = []
- num_checkpoints = 11
- # generate 11 checkpoints.
- for _ in range(num_checkpoints):
- checkpoints.append(app._dump_checkpoint())
- app.root.counter += 1
-
- app.load_state_dict_from_checkpoint_dir(app.checkpoint_dir)
- assert app.root.counter == (num_checkpoints - 1)
-
- for version in range(num_checkpoints):
- app.load_state_dict_from_checkpoint_dir(app.checkpoint_dir, version=version)
- assert app.root.counter == version
-
- with pytest.raises(FileNotFoundError, match="The provided directory"):
- app.load_state_dict_from_checkpoint_dir("./random_folder/")
-
- with pytest.raises(Exception, match="No checkpoints where found"):
- app.load_state_dict_from_checkpoint_dir(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/")))
-
- # delete 2 checkpoints
- os.remove(os.path.join(checkpoints[4]))
- os.remove(os.path.join(checkpoints[7]))
-
- app.load_state_dict_from_checkpoint_dir(app.checkpoint_dir)
- assert app.root.counter == (num_checkpoints - 1)
-
- app.load_state_dict_from_checkpoint_dir(app.checkpoint_dir, version=5)
- checkpoint_path = app._dump_checkpoint()
-
- assert os.path.basename(checkpoint_path).startswith("v_11")
-
-
-class PicklableObject:
- pass
-
-
-class PickleableReturnWork(LightningWork):
- def __init__(self):
- super().__init__()
-
- def run(self):
- return PicklableObject()
-
-
-class PickleableReturnFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = PickleableReturnWork()
-
- def run(self):
- self.work.run()
-
-
-def test_pickleable_return_from_work():
- """Test that any object that is pickleable can be returned from the run method in LightningWork."""
- with pytest.raises(SystemExit, match="1"):
- app = LightningApp(PickleableReturnFlow())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class WorkDD(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.total = 10
- self.counter = 1
-
- def run(self):
- should_wait = self.counter == 1
- start_counter = self.total - self.counter
- for _ in range(start_counter):
- if should_wait:
- sleep(0.5)
- self.counter += 1
-
-
-class FlowCCTolerance(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = WorkDD()
-
- def run(self):
- self.work.run()
- if self.work.counter == 10:
- self.stop()
-
-
-class FaultToleranceLightningTestApp(LightningTestApp):
- def on_after_run_once(self):
- if self.root.work.status.reason == WorkStopReasons.SIGTERM_SIGNAL_HANDLER:
- assert self.root.work.counter < 10
- self.restart_work("root.work")
- elif self.root.work.counter == 2:
- self.kill_work("root.work")
- return True, 0.0
- return super().on_after_run_once()
-
-
-# TODO (tchaton) Resolve this test with Resumable App.
-@_RunIf(skip_windows=True)
-def test_fault_tolerance_work():
- app = FaultToleranceLightningTestApp(FlowCCTolerance())
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.work.counter == 2
-
-
-class ProtectedAttributesWork(LightningWork):
- def __init__(self):
- super().__init__()
- # a public attribute, this should show up in the state
- self.done = False
- # a protected and a private attribute, these should NOT show up in the state
- self._protected = 1
- self.__private = 2
-
- def run(self):
- self.done = True
- self._protected = 10
- self.__private = 20
-
-
-class ProtectedAttributesFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- # a public attribute, this should show up in the state
- self.done = False
- # a protected and a private attribute, these should NOT show up in the state
- self._protected = 1
- self.__private = 2
-
- self.protected_work = ProtectedAttributesWork()
-
- def run(self):
- flow_variables = self.state_vars["vars"]
- assert "done" in flow_variables
- assert "_protected" not in flow_variables
- assert "__private" not in flow_variables
- self.done = True
-
- self.protected_work.run()
- if self.protected_work.done:
- work_variables = self.protected_work.state_vars["vars"]
- assert "done" in work_variables
- assert "_protected" not in work_variables
- assert "__private" not in work_variables
-
- # TODO: getattr and setattr access outside the Work should raise an error in the future
- _ = self.protected_work._protected
- self.protected_work._protected = 1
-
- if self.done and self.protected_work.done:
- self.stop()
-
-
-def test_protected_attributes_not_in_state():
- flow = ProtectedAttributesFlow()
- MultiProcessRuntime(LightningApp(flow), start_server=False).dispatch()
-
-
-class WorkExit(LightningWork):
- def __init__(self):
- super().__init__(raise_exception=False)
- self.counter = 0
-
- def run(self):
- self.counter += 1
- raise Exception("Hello")
-
-
-class FlowExit(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = WorkExit()
-
- def run(self):
- if self.work.counter == 1:
- self.stop()
- self.work.run()
-
-
-def test_lightning_app_exit():
- app = LightningApp(FlowExit())
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.work.status.stage == WorkStageStatus.STOPPED
-
-
-class CounterWork2(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class FlowStop(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = CounterWork2()
-
- def run(self):
- if self.w.status.stage == WorkStageStatus.STOPPED:
- self.stop()
- if self.w.counter == 1:
- self.w.stop()
- self.w.run()
-
-
-@_RunIf(skip_windows=True)
-def test_lightning_stop():
- app = LightningApp(FlowStop())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class SleepyFlow(LightningFlow):
- def __init__(self, sleep_interval, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.counter = 0
- self.sleep_interval = sleep_interval
-
- def run(self):
- if self.counter == 2 * FLOW_DURATION_SAMPLES:
- self.stop()
- sleep(self.sleep_interval)
- self.counter += 1
-
-
-class SleepyWork(LightningWork):
- def __init__(self, sleep_interval, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.sleep_interval = sleep_interval
-
- def run(self):
- sleep(self.sleep_interval)
-
-
-class SleepyFlowWithWork(LightningFlow):
- def __init__(self, sleep_interval, work_sleep_interval, parallel, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.counter = 0
- self.sleep_interval = sleep_interval
- self.work = SleepyWork(work_sleep_interval, parallel=parallel)
-
- def run(self):
- if self.counter == 2 * FLOW_DURATION_SAMPLES:
- self.stop()
- self.work.run()
- sleep(self.sleep_interval)
- self.counter += 1
-
-
-def test_slow_flow():
- app0 = LightningApp(SleepyFlow(sleep_interval=0.5 * FLOW_DURATION_THRESHOLD))
-
- MultiProcessRuntime(app0, start_server=False).dispatch()
-
- app1 = LightningApp(SleepyFlow(sleep_interval=2 * FLOW_DURATION_THRESHOLD))
-
- with pytest.warns(LightningFlowWarning):
- MultiProcessRuntime(app1, start_server=False).dispatch()
-
- app0 = LightningApp(
- SleepyFlowWithWork(
- sleep_interval=0.5 * FLOW_DURATION_THRESHOLD,
- work_sleep_interval=2 * FLOW_DURATION_THRESHOLD,
- parallel=False,
- )
- )
-
- MultiProcessRuntime(app0, start_server=False).dispatch()
-
- app1 = LightningApp(
- SleepyFlowWithWork(
- sleep_interval=0.5 * FLOW_DURATION_THRESHOLD, work_sleep_interval=2 * FLOW_DURATION_THRESHOLD, parallel=True
- )
- )
-
- MultiProcessRuntime(app1, start_server=False).dispatch()
-
-
-class SizeWork(LightningWork):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.counter = 0
-
- def run(self, signal: int):
- self.counter += 1
- assert len(self._calls) == 2
-
-
-class SizeFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work0 = SizeWork(parallel=True, cache_calls=True)
- self._state_sizes = {}
-
- def run(self):
- for idx in range(self.work0.counter + 2):
- self.work0.run(idx)
-
- self._state_sizes[self.work0.counter] = asizeof.asizeof(self.state)
-
- if self.work0.counter >= 20:
- self.stop()
-
-
-def test_state_size_constant_growth():
- app = LightningApp(SizeFlow())
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root._state_sizes[0] <= 8380
- assert app.root._state_sizes[20] <= 26999
-
-
-class FlowUpdated(LightningFlow):
- def run(self):
- logger.info("Hello World")
-
-
-class NonUpdatedLightningTestApp(LightningTestApp):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.counter = 0
-
- def on_after_run_once(self):
- self.counter += 1
- if not self._has_updated and self.counter > 2:
- return True
- return super().on_after_run_once()
-
-
-def test_non_updated_flow(caplog):
- """Validate that the app can run 3 times and calls the flow only once."""
- app = NonUpdatedLightningTestApp(FlowUpdated())
- runtime = MultiProcessRuntime(app, start_server=False)
- with caplog.at_level(logging.INFO):
- runtime.dispatch()
- assert caplog.messages == [
- "Hello World",
- "Your Lightning App is being stopped. This won't take long.",
- "Your Lightning App has been stopped successfully!",
- ]
- assert app.counter == 3
-
-
-def test_debug_mode_logging():
- """This test validates the DEBUG messages are collected when activated by the LightningApp(debug=True) and cleanup
- once finished."""
-
- from lightning.app.core.app import _console
-
- app = LightningApp(A4(), log_level="debug")
- assert _console.level == logging.DEBUG
- assert os.getenv("LIGHTNING_DEBUG") == "2"
-
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- assert os.getenv("LIGHTNING_DEBUG") is None
- assert _console.level == logging.INFO
-
- app = LightningApp(A4())
- assert _console.level == logging.INFO
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class WorkPath(LightningWork):
- def __init__(self):
- super().__init__()
- self.path = None
-
- def run(self):
- self.path = Path(__file__)
-
-
-class FlowPath(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkPath()
-
- def run(self):
- self.w.run()
-
-
-class TestLightningHasUpdatedApp(LightningApp):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.counter = 0
-
- def run_once(self):
- res = super().run_once()
-
- if self.root.w.has_succeeded:
- self.counter += 1
-
- # TODO: Resolve bug where it should work with self.counter == 2
- if self.counter > 5:
- assert not self._has_updated
- return True
- return res
-
-
-@pytest.mark.flaky(reruns=3)
-def test_lightning_app_has_updated():
- app = TestLightningHasUpdatedApp(FlowPath())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class WorkCC(LightningWork):
- def run(self):
- pass
-
-
-class FlowCC(LightningFlow):
- def __init__(self):
- super().__init__()
- self.cloud_compute = CloudCompute(name="gpu", _internal_id="a")
- self.work_a = WorkCC(cloud_compute=self.cloud_compute)
- self.work_b = WorkCC(cloud_compute=self.cloud_compute)
- self.work_c = WorkCC()
- assert self.work_a.cloud_compute._internal_id == self.work_b.cloud_compute._internal_id
-
- def run(self):
- self.work_d = WorkCC()
-
-
-class FlowWrapper(LightningFlow):
- def __init__(self, flow):
- super().__init__()
- self.w = flow
-
-
-def test_cloud_compute_binding():
- cloud_compute.ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER = True
-
- assert {} == cloud_compute._CLOUD_COMPUTE_STORE
- flow = FlowCC()
- assert len(cloud_compute._CLOUD_COMPUTE_STORE) == 2
- assert cloud_compute._CLOUD_COMPUTE_STORE["default"].component_names == ["root.work_c"]
- assert cloud_compute._CLOUD_COMPUTE_STORE["a"].component_names == ["root.work_a", "root.work_b"]
-
- wrapper = FlowWrapper(flow)
- assert cloud_compute._CLOUD_COMPUTE_STORE["default"].component_names == ["root.w.work_c"]
- assert cloud_compute._CLOUD_COMPUTE_STORE["a"].component_names == ["root.w.work_a", "root.w.work_b"]
-
- _ = FlowWrapper(wrapper)
- assert cloud_compute._CLOUD_COMPUTE_STORE["default"].component_names == ["root.w.w.work_c"]
- assert cloud_compute._CLOUD_COMPUTE_STORE["a"].component_names == ["root.w.w.work_a", "root.w.w.work_b"]
-
- assert flow.state["vars"]["cloud_compute"]["type"] == "__cloud_compute__"
- assert flow.work_a.state["vars"]["_cloud_compute"]["type"] == "__cloud_compute__"
- assert flow.work_b.state["vars"]["_cloud_compute"]["type"] == "__cloud_compute__"
- assert flow.work_c.state["vars"]["_cloud_compute"]["type"] == "__cloud_compute__"
- work_a_id = flow.work_a.state["vars"]["_cloud_compute"]["_internal_id"]
- work_b_id = flow.work_b.state["vars"]["_cloud_compute"]["_internal_id"]
- work_c_id = flow.work_c.state["vars"]["_cloud_compute"]["_internal_id"]
- assert work_a_id == work_b_id
- assert work_a_id != work_c_id
- assert work_c_id == "default"
-
- flow.work_a.cloud_compute = CloudCompute(name="something_else")
- assert cloud_compute._CLOUD_COMPUTE_STORE["a"].component_names == ["root.w.w.work_b"]
-
- flow.set_state(flow.state)
- assert isinstance(flow.cloud_compute, CloudCompute)
- assert isinstance(flow.work_a.cloud_compute, CloudCompute)
- assert isinstance(flow.work_c.cloud_compute, CloudCompute)
-
- cloud_compute.ENABLE_MULTIPLE_WORKS_IN_NON_DEFAULT_CONTAINER = False
-
- with pytest.raises(Exception, match="A Cloud Compute can be assigned only to a single Work"):
- FlowCC()
-
-
-class FlowValue(LightningFlow):
- def __init__(self):
- super().__init__()
- self._value = None
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- self._value = value
-
- def run(self):
- self.value = True
-
-
-def test_lightning_flow_properties():
- """Validates setting properties to the LightningFlow properly calls property.fset."""
- flow = FlowValue()
- assert flow._value is None
- flow.run()
- assert flow._value is True
-
-
-class SimpleWork2(LightningWork):
- def run(self):
- pass
-
-
-def test_lightning_work_stopped():
- app = LightningApp(SimpleWork2())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class FailedWork(LightningWork):
- def run(self):
- raise Exception
-
-
-class CheckErrorQueueLightningApp(LightningApp):
- def check_error_queue(self):
- super().check_error_queue()
-
-
-def test_error_queue_check(monkeypatch):
- import sys
-
- from lightning.app.core import app as app_module
-
- sys_mock = mock.MagicMock()
- monkeypatch.setattr(app_module, "CHECK_ERROR_QUEUE_INTERVAL", 0)
- monkeypatch.setattr(sys, "exit", sys_mock)
- app = LightningApp(FailedWork())
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.stage == AppStage.FAILED
- assert app._last_check_error_queue != 0.0
diff --git a/tests/tests_app/core/test_lightning_flow.py b/tests/tests_app/core/test_lightning_flow.py
deleted file mode 100644
index e7e8ef77603c1..0000000000000
--- a/tests/tests_app/core/test_lightning_flow.py
+++ /dev/null
@@ -1,965 +0,0 @@
-import contextlib
-import os
-import pickle
-from collections import Counter
-from copy import deepcopy
-from dataclasses import dataclass
-from functools import partial
-from time import time
-from unittest.mock import ANY
-
-import lightning.app
-import pytest
-from deepdiff import DeepDiff, Delta
-from lightning.app import CloudCompute, LightningApp
-from lightning.app.core.flow import LightningFlow, _RootFlow
-from lightning.app.core.work import LightningWork
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.path import Path, _storage_root_dir
-from lightning.app.structures import Dict as LDict
-from lightning.app.structures import List as LList
-from lightning.app.testing.helpers import EmptyFlow, EmptyWork, _MockQueue
-from lightning.app.utilities.app_helpers import (
- _delta_to_app_state_delta,
- _LightningAppRef,
- _load_state_dict,
- _state_dict,
-)
-from lightning.app.utilities.enum import CacheCallsKeys
-from lightning.app.utilities.exceptions import ExitAppException
-from lightning.app.utilities.imports import _IS_WINDOWS
-
-
-def test_empty_component():
- class A(LightningFlow):
- def run(self):
- pass
-
- empty_component = A()
- assert empty_component.state == {
- "vars": {"_layout": ANY, "_paths": {}},
- "calls": {},
- "flows": {},
- "structures": {},
- "changes": {},
- "works": {},
- }
-
-
-@dataclass
-class CustomDataclass:
- x: int = 1
- y: tuple = (3, 2, 1)
-
-
-@pytest.mark.parametrize("attribute", [{3, 2, 1}, lambda _: 5, CustomDataclass()])
-@pytest.mark.parametrize("cls", [LightningWork, LightningFlow])
-def test_unsupported_attribute_types(cls, attribute):
- class Component(cls):
- def __init__(self):
- super().__init__()
- self.x = attribute
-
- def run(self):
- pass
-
- with pytest.raises(AttributeError, match="Only JSON-serializable attributes are currently supported"):
- Component()
-
-
-@pytest.mark.parametrize(
- ("name", "value"),
- [
- ("x", 1),
- ("f", EmptyFlow()),
- ("w", EmptyWork()),
- ],
-)
-def test_unsupported_attribute_declaration_outside_init_or_run(name, value):
- """Test that LightningFlow attributes (with a few exceptions) are not allowed to be declared outside __init__."""
- flow = EmptyFlow()
- with pytest.raises(AttributeError, match=f"Cannot set attributes that were not defined in __init__: {name}"):
- setattr(flow, name, value)
- assert not hasattr(flow, name)
- assert name not in flow.state["vars"]
- assert name not in flow._works
- assert name not in flow._flows
-
- # no error for protected attributes, since they don't contribute to the state
- setattr(flow, "_" + name, value)
- assert hasattr(flow, "_" + name)
-
-
-@pytest.mark.parametrize(
- ("name", "value"),
- [
- ("x", 1),
- ("f", EmptyFlow()),
- ("w", EmptyWork()),
- ],
-)
-@pytest.mark.parametrize("defined", [False, True])
-def test_unsupported_attribute_declaration_inside_run(defined, name, value):
- """Test that LightningFlow attributes can set LightningFlow or LightningWork inside its run method, but everything
- else needs to be defined in the __init__ method."""
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- if defined:
- setattr(self, name, None)
-
- def run(self):
- if not defined and not isinstance(value, (LightningFlow, LightningWork)):
- with pytest.raises(
- AttributeError, match=f"Cannot set attributes that were not defined in __init__: {name}"
- ):
- setattr(self, name, value)
- assert name not in self.state["vars"]
- assert name not in self._works
- assert name not in self._flows
- else:
- setattr(self, name, value)
- if isinstance(value, LightningFlow):
- assert name in self._flows
- elif isinstance(value, LightningWork):
- assert name in self._works
- else:
- assert name in self.state["vars"]
-
- flow = Flow()
- flow.run()
-
-
-@pytest.mark.parametrize("value", [EmptyFlow(), EmptyWork()])
-def test_name_gets_removed_from_state_when_defined_as_flow_works(value):
- """Test that LightningFlow attributes are removed from the state."""
-
- class EmptyFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.value = None
-
- def run(self):
- self.value = value
-
- flow = EmptyFlow()
- flow.run()
- if isinstance(value, LightningFlow):
- assert "value" not in flow.state["vars"]
- assert "value" in flow._flows
- else:
- assert "value" not in flow.state["vars"]
- assert "value" in flow._works
-
-
-@pytest.mark.parametrize(
- ("name", "value"),
- [
- ("_name", "name"),
- ("_changes", {"change": 1}),
- ],
-)
-def test_supported_attribute_declaration_outside_init(name, value):
- """Test the custom LightningFlow setattr implementation for the few reserved attributes that are allowed to be set
- from outside __init__."""
- flow = EmptyFlow()
- setattr(flow, name, value)
- assert getattr(flow, name) == value
-
-
-def test_supported_attribute_declaration_inside_init():
- """Test that the custom LightningFlow setattr can identify the __init__ call in the stack frames above."""
-
- class Flow(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.directly_in_init = "init"
- self.method_under_init()
-
- def method_under_init(self):
- self.attribute = "test"
- self.subflow = EmptyFlow()
-
- flow = Flow()
- assert flow.directly_in_init == "init"
- assert flow.state["vars"]["directly_in_init"] == "init"
- assert flow.attribute == "test"
- assert flow.state["vars"]["attribute"] == "test"
- assert isinstance(flow.subflow, EmptyFlow)
- assert flow.state["flows"]["subflow"] == flow.subflow.state
-
-
-def test_setattr_outside_run_context():
- """Test that it is allowed to update attributes outside `run` as long as the attribute is already declared."""
-
- class Flow(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.attribute = ""
-
- def outside_run(self):
- # reading allowed, setting not allowed
- self.attribute = "allowed"
- return super().configure_layout()
-
- flow = Flow()
- flow.outside_run()
- assert flow.attribute == "allowed"
- assert flow.state["vars"]["attribute"] == "allowed"
-
-
-def _run_state_transformation(tmpdir, attribute, update_fn, inplace=False):
- """This helper function defines a flow, assignes an attribute and performs a transformation on the state."""
-
- class StateTransformationTest(LightningFlow):
- def __init__(self):
- super().__init__()
- self.x = attribute
- self.finished = False
-
- def run(self):
- if self.finished:
- self.stop()
-
- x = update_fn(self.x)
- if not inplace:
- self.x = x
- self.finished = True
-
- flow = StateTransformationTest()
- assert flow.x == attribute
- app = LightningApp(flow)
- MultiProcessRuntime(app, start_server=False).dispatch()
- return app.state["vars"]["x"]
-
-
-@pytest.mark.parametrize(
- ("attribute", "update_fn", "expected"),
- [
- (1, lambda x: x + 1, 2),
- (0.5, lambda x: x + 0.5, 1.0),
- (True, lambda x: not x, False),
- ("cocofruit", lambda x: x + "s", "cocofruits"),
- ({"a": 1, "b": 2}, lambda x: {"a": 1, "b": 3}, {"a": 1, "b": 3}),
- ([1, 2], lambda x: [1, 2, 3], [1, 2, 3]),
- ((4, 5), lambda x: (4, 5, 6), (4, 5, 6)),
- ],
-)
-def test_attribute_state_change(attribute, update_fn, expected, tmpdir):
- """Test that state changes get recored on all supported data types."""
- assert _run_state_transformation(tmpdir, attribute, update_fn, inplace=False) == expected
-
-
-def test_inplace_attribute_state_change(tmpdir):
- """Test that in-place modifications on containers get captured as a state change."""
-
- # inplace modification of a nested dict
- def transform(x):
- x["b"]["c"] += 1
-
- value = {"a": 1, "b": {"c": 2}}
- expected = {"a": 1, "b": {"c": 3}}
- assert _run_state_transformation(tmpdir, value, transform, inplace=True) == expected
-
- # inplace modification of nested list
- def transform(x):
- x[2].append(3.0)
-
- value = ["a", 1, [2.0]]
- expected = ["a", 1, [2.0, 3.0]]
- assert _run_state_transformation(tmpdir, value, transform, inplace=True) == expected
-
- # inplace modification of a custom dict
- def transform(x):
- x.update("baa")
-
- value = Counter("abab")
- expected = Counter(a=4, b=3)
- assert _run_state_transformation(tmpdir, value, transform, inplace=True) == expected
-
-
-def test_lightning_flow_and_work():
- class Work(LightningWork):
- def __init__(self, cache_calls: bool = True, port=None):
- super().__init__(cache_calls=cache_calls, port=port)
- self.counter = 0
-
- def run(self, *args, **kwargs):
- self.counter += 1
-
- class Flow_A(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
- self.work_a = Work(cache_calls=True, port=8000)
- self.work_b = Work(cache_calls=False, port=8001)
-
- def run(self):
- if self.counter < 5:
- self.work_a.run()
- self.work_b.run()
- self.counter += 1
- else:
- self.stop()
-
- flow_a = Flow_A()
- assert flow_a.named_works() == [("root.work_a", flow_a.work_a), ("root.work_b", flow_a.work_b)]
- assert flow_a.works() == [flow_a.work_a, flow_a.work_b]
- state = {
- "vars": {"counter": 0, "_layout": ANY, "_paths": {}},
- "calls": {},
- "flows": {},
- "structures": {},
- "works": {
- "work_b": {
- "vars": {
- "counter": 0,
- "_url": "",
- "_future_url": "",
- "_port": 8001,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- "calls": {CacheCallsKeys.LATEST_CALL_HASH: None},
- "changes": {},
- },
- "work_a": {
- "vars": {
- "counter": 0,
- "_url": "",
- "_future_url": "",
- "_port": 8000,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- "calls": {CacheCallsKeys.LATEST_CALL_HASH: None},
- "changes": {},
- },
- },
- "changes": {},
- }
- assert flow_a.state == state
- with contextlib.suppress(ExitAppException):
- while True:
- flow_a.run()
-
- state = {
- "vars": {"counter": 5, "_layout": ANY, "_paths": {}},
- "calls": {},
- "flows": {},
- "structures": {},
- "works": {
- "work_b": {
- "vars": {
- "counter": 5,
- "_url": "",
- "_future_url": "",
- "_port": 8001,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- "calls": {CacheCallsKeys.LATEST_CALL_HASH: None},
- "changes": {},
- },
- "work_a": {
- "vars": {
- "counter": 1,
- "_url": "",
- "_future_url": "",
- "_port": 8000,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- "calls": {
- CacheCallsKeys.LATEST_CALL_HASH: None,
- "fe3fa0f": {
- "ret": None,
- },
- },
- "changes": {},
- },
- },
- "changes": {},
- }
- assert flow_a.state == state
-
-
-def test_populate_changes():
- class WorkA(LightningWork):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- pass
-
- class A(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = WorkA()
-
- def run(self):
- pass
-
- flow_a = A()
- flow_state = flow_a.state
- work_state = flow_a.work.state
- flow_a.work.counter = 1
- work_state_2 = flow_a.work.state
- delta = Delta(DeepDiff(work_state, work_state_2, verbose_level=2))
- delta = _delta_to_app_state_delta(flow_a, flow_a.work, delta)
- new_flow_state = LightningApp.populate_changes(flow_state, flow_state + delta)
- flow_a.set_state(new_flow_state)
- assert flow_a.work.counter == 1
- assert new_flow_state["works"]["work"]["changes"] == {"counter": {"from": 0, "to": 1}}
- assert flow_a.work._changes == {"counter": {"from": 0, "to": 1}}
-
-
-def test_populate_changes_status_removed():
- """Regression test for https://github.com/Lightning-AI/lightning/issues/342."""
- last_state = {
- "vars": {},
- "calls": {},
- "flows": {},
- "works": {
- "work": {
- "vars": {},
- "calls": {
- CacheCallsKeys.LATEST_CALL_HASH: "run:fe3f",
- "run:fe3f": {
- "statuses": [
- {"stage": "requesting", "message": None, "reason": None, "timestamp": 1},
- {"stage": "starting", "message": None, "reason": None, "timestamp": 2},
- {"stage": "requesting", "message": None, "reason": None, "timestamp": 3},
- ],
- },
- },
- "changes": {},
- },
- },
- "changes": {},
- }
- new_state = deepcopy(last_state)
- call = new_state["works"]["work"]["calls"]["run:fe3f"]
- call["statuses"] = call["statuses"][:-1] # pretend that a status was removed from the list
- new_state_before = deepcopy(new_state)
- new_state = LightningApp.populate_changes(last_state, new_state)
- assert new_state == new_state_before
-
-
-class CFlow(LightningFlow):
- def __init__(self, run_once):
- super().__init__()
- self.looping = 0
- self.tracker = 0
- self.restarting = False
- self.run_once = run_once
-
- def run(self):
- for idx in self.experimental_iterate(range(0, 10), run_once=self.run_once):
- if not self.restarting and (idx + 1) == 5:
- _LightningAppRef.get_current()._dump_checkpoint()
- self.stop()
- self.tracker += 1
- self.looping += 1
- if self.looping == 2:
- self.stop()
-
-
-@pytest.mark.xfail(strict=False, reason="flaky")
-@pytest.mark.parametrize("run_once", [False, True])
-def test_lightning_flow_iterate(tmpdir, run_once):
- app = LightningApp(CFlow(run_once))
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.looping == 0
- assert app.root.tracker == 4
- call_hash = [v for v in app.root._calls if "experimental_iterate" in v][0]
- iterate_call = app.root._calls[call_hash]
- assert iterate_call["counter"] == 4
- assert not iterate_call["has_finished"]
-
- checkpoint_dir = os.path.join(_storage_root_dir(), "checkpoints")
- app = LightningApp(CFlow(run_once))
- app.load_state_dict_from_checkpoint_dir(checkpoint_dir)
- app.root.restarting = True
- assert app.root.looping == 0
- assert app.root.tracker == 4
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.looping == 2
- assert app.root.tracker == 10 if run_once else 20
- iterate_call = app.root._calls[call_hash]
- assert iterate_call["has_finished"]
-
-
-class FlowCounter(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- if self.counter >= 3:
- self.stop()
- self.counter += 1
-
-
-@pytest.mark.xfail(strict=False, reason="flaky")
-def test_lightning_flow_counter(tmpdir):
- app = LightningApp(FlowCounter())
- app.checkpointing = True
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.counter == 3
-
- checkpoint_dir = os.path.join(_storage_root_dir(), "checkpoints")
- checkpoints = os.listdir(checkpoint_dir)
- assert len(checkpoints) == 4
- for checkpoint in checkpoints:
- checkpoint_path = os.path.join(checkpoint_dir, checkpoint)
- with open(checkpoint_path, "rb") as f:
- app = LightningApp(FlowCounter())
- app.set_state(pickle.load(f))
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.counter == 3
-
-
-def test_flow_iterate_method():
- class Flow(LightningFlow):
- def run(self):
- pass
-
- flow = Flow()
- with pytest.raises(TypeError, match="An iterable should be provided"):
- next(flow.experimental_iterate(1))
-
-
-def test_flow_path_assignment():
- """Test that paths in the lit format lit:// get converted to a proper lightning.app.storage.Path object."""
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.no_path = "a/b/c"
- self.path = Path("lit://x/y/z")
- self.lit_path = "lit://x/y/z"
-
- flow = Flow()
- assert isinstance(flow.no_path, str)
- assert isinstance(flow.path, Path)
- assert isinstance(flow.lit_path, Path)
- assert flow.path == flow.lit_path
-
-
-@pytest.mark.skipif(_IS_WINDOWS, reason="timeout with system crash")
-@pytest.mark.xfail(strict=False, reason="Timeout") # fixme
-def test_flow_state_change_with_path():
- """Test that type changes to a Path attribute are properly reflected within the state."""
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.none_to_path = None
- self.path_to_none = Path()
- self.path_to_path = Path()
-
- def run(self):
- self.none_to_path = "lit://none/to/path"
- self.path_to_none = None
- self.path_to_path = "lit://path/to/path"
- self.stop()
-
- flow = Flow()
- MultiProcessRuntime(LightningApp(flow)).dispatch()
- assert flow.none_to_path == Path("lit://none/to/path")
- assert flow.path_to_none is None
- assert flow.path_to_path == Path("lit://path/to/path")
-
- assert "path_to_none" not in flow._paths
- assert "path_to_none" in flow._state
- assert flow._paths["none_to_path"] == Path("lit://none/to/path").to_dict()
- assert flow._paths["path_to_path"] == Path("lit://path/to/path").to_dict()
- assert flow.state["vars"]["none_to_path"] == Path("lit://none/to/path")
- assert flow.state["vars"]["path_to_none"] is None
- assert flow.state["vars"]["path_to_path"] == Path("lit://path/to/path")
-
-
-class FlowSchedule(LightningFlow):
- def __init__(self):
- super().__init__()
- self._last_times = []
- self.target = 3
- self.seconds = ",".join([str(v) for v in range(0, 60, self.target)])
-
- def run(self):
- if self.schedule(f"* * * * * {self.seconds}"):
- if len(self._last_times) < 3:
- self._last_times.append(time())
- else:
- # TODO: Resolve scheduling
- assert abs((time() - self._last_times[-1]) - self.target) < 20
- self.stop()
-
-
-def test_scheduling_api():
- app = LightningApp(FlowSchedule())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-def test_lightning_flow():
- class Flow(LightningFlow):
- def run(self):
- if self.schedule("midnight"):
- pass
- if self.schedule("hourly"):
- pass
- if self.schedule("@hourly"):
- pass
- if self.schedule("daily"):
- pass
- if self.schedule("weekly"):
- pass
- if self.schedule("monthly"):
- pass
- if self.schedule("yearly"):
- pass
- if self.schedule("annually"):
- pass
- assert len(self._calls["scheduling"]) == 8
-
- Flow().run()
-
-
-class WorkReload(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class FlowReload(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- if not getattr(self, "w", None):
- self.w = WorkReload()
-
- self.counter += 1
- self.w.run()
-
- def load_state_dict(self, flow_state, children_states, strict) -> None:
- self.w = WorkReload()
- super().load_state_dict(flow_state, children_states, strict=strict)
-
-
-class FlowReload2(LightningFlow):
- def __init__(self, random_value: str):
- super().__init__()
- self.random_value = random_value
- self.counter = 0
-
- def run(self):
- if not getattr(self, "w", None):
- self.w = WorkReload()
- self.w.run()
- self.counter += 1
-
- def load_state_dict(self, flow_state, children_states, strict) -> None:
- self.w = WorkReload()
- super().load_state_dict(flow_state, children_states, strict=strict)
-
-
-class RootFlowReload(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flow = FlowReload()
- self.counter = 0
-
- def run(self):
- if not getattr(self, "flow_2", None):
- self.flow_2 = FlowReload2("something")
- self.flow.run()
- self.flow_2.run()
- self.counter += 1
-
- def load_state_dict(self, flow_state, children_states, strict) -> None:
- self.flow_2 = FlowReload2(children_states["flow_2"]["vars"]["random_value"])
- super().load_state_dict(flow_state, children_states, strict=strict)
-
-
-class RootFlowReload2(RootFlowReload):
- def load_state_dict(self, flow_state, children_states, strict) -> None:
- LightningFlow.load_state_dict(self, flow_state, children_states, strict=strict)
-
-
-def test_lightning_flow_reload():
- flow = RootFlowReload()
-
- assert flow.counter == 0
- assert flow.flow.counter == 0
-
- flow.run()
-
- assert flow.flow.w.counter == 1
- assert flow.counter == 1
- assert flow.flow.counter == 1
- assert flow.flow_2.counter == 1
- assert flow.flow_2.w.counter == 1
-
- state = _state_dict(flow)
- flow = RootFlowReload()
- _load_state_dict(flow, state)
-
- assert flow.flow.w.counter == 1
- assert flow.counter == 1
- assert flow.flow.counter == 1
- assert flow.flow_2.counter == 1
- assert flow.flow_2.w.counter == 1
-
- flow.run()
-
- assert flow.flow.w.counter == 2
- assert flow.counter == 2
- assert flow.flow.counter == 2
- assert flow.flow_2.counter == 2
- assert flow.flow_2.w.counter == 2
-
- flow = RootFlowReload2()
- flow.run()
- state = _state_dict(flow)
- flow = RootFlowReload2()
- with pytest.raises(ValueError, match="The component flow_2 wasn't instantiated for the component root"):
- _load_state_dict(flow, state)
-
-
-class NestedFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flows_dict = LDict(**{"a": EmptyFlow()})
- self.flows_list = LList(*[EmptyFlow()])
- self.flow = EmptyFlow()
- assert list(self.flows) == ["root.flow", "root.flows_dict.a", "root.flows_list.0"]
- self.w = EmptyWork()
-
- def run(self):
- pass
-
-
-class FlowNested2(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flow3 = EmptyFlow()
- self.w = EmptyWork()
-
- def run(self):
- pass
-
-
-class FlowCollection(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flow = EmptyFlow()
- assert self.flow.name == "root.flow"
- self.flow2 = FlowNested2()
- assert list(self.flow2.flows) == ["root.flow2.flow3"]
- self.flows_dict = LDict(**{"a": NestedFlow()})
- assert list(self.flows_dict.flows) == [
- "root.flows_dict.a",
- "root.flows_dict.a.flow",
- "root.flows_dict.a.flows_dict.a",
- "root.flows_dict.a.flows_list.0",
- ]
- self.flows_list = LList(*[NestedFlow()])
- assert list(self.flows_list.flows) == [
- "root.flows_list.0",
- "root.flows_list.0.flow",
- "root.flows_list.0.flows_dict.a",
- "root.flows_list.0.flows_list.0",
- ]
- self.w = EmptyWork()
-
- def run(self):
- pass
-
-
-def test_lightning_flow_flows_and_works():
- flow = FlowCollection()
- app = LightningApp(flow)
-
- assert list(app.root.flows.keys()) == [
- "root.flow",
- "root.flow2",
- "root.flow2.flow3",
- "root.flows_dict.a",
- "root.flows_dict.a.flow",
- "root.flows_dict.a.flows_dict.a",
- "root.flows_dict.a.flows_list.0",
- "root.flows_list.0",
- "root.flows_list.0.flow",
- "root.flows_list.0.flows_dict.a",
- "root.flows_list.0.flows_list.0",
- ]
-
- assert [w[0] for w in app.root.named_works()] == [
- "root.w",
- "root.flow2.w",
- "root.flows_dict.a.w",
- "root.flows_list.0.w",
- ]
-
-
-class WorkReady(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.ready = False
-
- def run(self):
- self.ready = True
-
-
-class FlowReady(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkReady()
-
- @property
- def ready(self) -> bool:
- return self.w.has_succeeded
-
- def run(self):
- self.w.run()
-
- if self.ready:
- self.stop()
-
-
-class RootFlowReady(_RootFlow):
- def __init__(self):
- super().__init__(WorkReady())
-
-
-@pytest.mark.parametrize("flow", [FlowReady, RootFlowReady])
-def test_flow_ready(flow):
- """This test validates that the app status queue is populated correctly."""
- mock_queue = _MockQueue("api_publish_state_queue")
-
- def run_patch(method):
- app.should_publish_changes_to_api = True
- app.api_publish_state_queue = mock_queue
- method()
-
- state = {"done": False}
-
- def lagged_run_once(method):
- """Ensure that the full loop is run after the app exits."""
- new_done = method()
- if state["done"]:
- return True
- state["done"] = new_done
- return False
-
- app = LightningApp(flow())
- app._run = partial(run_patch, method=app._run)
- app.run_once = partial(lagged_run_once, method=app.run_once)
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- _, first_status = mock_queue.get()
- assert not first_status.is_ui_ready
-
- _, last_status = mock_queue.get()
- while len(mock_queue) > 0:
- _, last_status = mock_queue.get()
- assert last_status.is_ui_ready
-
-
-def test_structures_register_work_cloudcompute():
- class MyDummyWork(LightningWork):
- def run(self):
- return
-
- class MyDummyFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_list = LList(*[MyDummyWork(cloud_compute=CloudCompute("gpu")) for i in range(5)])
- self.w_dict = LDict(**{str(i): MyDummyWork(cloud_compute=CloudCompute("gpu")) for i in range(5)})
-
- def run(self):
- for w in self.w_list:
- w.run()
-
- for w in self.w_dict.values():
- w.run()
-
- MyDummyFlow()
- assert len(lightning.app.utilities.packaging.cloud_compute._CLOUD_COMPUTE_STORE) == 10
- for v in lightning.app.utilities.packaging.cloud_compute._CLOUD_COMPUTE_STORE.values():
- assert len(v.component_names) == 1
- assert v.component_names[0][:-1] in ("root.w_list.", "root.w_dict.")
- assert v.component_names[0][-1].isdigit()
-
-
-def test_deprecation_warning_exit():
- with pytest.raises(ExitAppException), pytest.warns(DeprecationWarning, match="*Use LightningFlow.stop instead"):
- RootFlowReady()._exit()
diff --git a/tests/tests_app/core/test_lightning_work.py b/tests/tests_app/core/test_lightning_work.py
deleted file mode 100644
index d3af9d5516d4a..0000000000000
--- a/tests/tests_app/core/test_lightning_work.py
+++ /dev/null
@@ -1,420 +0,0 @@
-import contextlib
-from queue import Empty
-from re import escape
-from unittest.mock import MagicMock, Mock
-
-import pytest
-from lightning.app import LightningApp
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.work import LightningWork
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.path import Path
-from lightning.app.testing.helpers import EmptyFlow, EmptyWork, _MockQueue
-from lightning.app.testing.testing import LightningTestApp
-from lightning.app.utilities.enum import WorkStageStatus, make_status
-from lightning.app.utilities.exceptions import LightningWorkException
-from lightning.app.utilities.imports import _IS_WINDOWS
-from lightning.app.utilities.packaging.build_config import BuildConfig
-from lightning.app.utilities.proxies import ProxyWorkRun, WorkRunner
-
-
-def test_lightning_work_run_method_required():
- """Test that a helpful exception is raised when the user did not implement the `LightningWork.run()` method."""
- with pytest.raises(TypeError, match=escape("The work `LightningWork` is missing the `run()` method")):
- LightningWork()
-
- class WorkWithoutRun(LightningWork):
- def __init__(self):
- super().__init__()
- self.started = False
-
- with pytest.raises(TypeError, match=escape("The work `WorkWithoutRun` is missing the `run()` method")):
- WorkWithoutRun()
-
- class WorkWithRun(WorkWithoutRun):
- def run(self, *args, **kwargs):
- self.started = True
-
- work = WorkWithRun()
- work.run()
- assert work.started
-
-
-def test_lightning_work_no_children_allowed():
- """Test that a LightningWork can't have any children (work or flow)."""
-
- class ChildWork(EmptyWork):
- pass
-
- class ParentWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.work_b = ChildWork()
-
- def run(self, *args, **kwargs):
- pass
-
- with pytest.raises(LightningWorkException, match="isn't allowed to take any children such as"):
- ParentWork()
-
- class ParentWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.flow = LightningFlow()
-
- def run(self, *args, **kwargs):
- pass
-
- with pytest.raises(LightningWorkException, match="LightningFlow"):
- ParentWork()
-
-
-def test_forgot_to_call_init():
- """This test validates the error message for user registering state without calling __init__ is comprehensible."""
-
- class W(LightningWork):
- def __init__(self):
- self.var_a = None
-
- def run(self):
- pass
-
- with pytest.raises(AttributeError, match="Did you forget to call"):
- W()
-
-
-@pytest.mark.parametrize(
- ("name", "value"),
- [
- ("x", 1),
- ("f", EmptyFlow()),
- ("w", EmptyWork()),
- ("run", lambda _: _),
- ],
-)
-def test_unsupported_attribute_declaration_outside_init(name, value):
- """Test that LightningWork attributes (with a few exceptions) are not allowed to be set outside __init__."""
- flow = EmptyFlow()
- with pytest.raises(AttributeError, match=f"Cannot set attributes that were not defined in __init__: {name}"):
- setattr(flow, name, value)
- assert name == "run" or not hasattr(flow, name)
-
-
-@pytest.mark.parametrize(
- ("name", "value"),
- [
- ("_name", "name"),
- ("_changes", {"change": 1}),
- ("run", ProxyWorkRun(work_run=Mock(), work_name="any", work=Mock(), caller_queue=Mock())),
- ],
-)
-def test_supported_attribute_declaration_outside_init(name, value):
- """Test the custom LightningWork setattr implementation for the few reserved attributes that are allowed to be set
- from outside __init__."""
- flow = EmptyWork()
- setattr(flow, name, value)
- assert getattr(flow, name) == value
-
-
-def test_supported_attribute_declaration_inside_init():
- """Test that the custom LightningWork setattr can identify the __init__ call in the stack frames above."""
-
- class Work(EmptyWork):
- def __init__(self):
- super().__init__()
- self.directly_in_init = "init"
- self.method_under_init()
-
- def method_under_init(self):
- self.attribute = "test"
-
- work = Work()
- assert work.directly_in_init == "init"
- assert work.attribute == "test"
-
-
-@pytest.mark.parametrize("replacement", [EmptyFlow(), EmptyWork(), None])
-def test_fixing_flows_and_works(replacement):
- class FlowFixed(LightningFlow):
- def run(self):
- self.empty_flow = EmptyFlow()
- self.empty_flow = replacement
-
- with pytest.raises(AttributeError, match="Cannot set attributes as"):
- FlowFixed().run()
-
-
-@pytest.mark.parametrize("enable_exception", [False, True])
-@pytest.mark.parametrize("raise_exception", [False, True])
-def test_lightning_status(enable_exception, raise_exception):
- class Work(EmptyWork):
- def __init__(self, raise_exception, enable_exception=True):
- super().__init__(raise_exception=raise_exception)
- self.enable_exception = enable_exception
- self.dummy_path = Path("test")
-
- def run(self):
- if self.enable_exception:
- raise Exception("Custom Exception")
-
- work = Work(raise_exception, enable_exception=enable_exception)
- work._name = "root.w"
- assert work.status.stage == WorkStageStatus.NOT_STARTED
- caller_queue = _MockQueue("caller_queue")
- delta_queue = _MockQueue("delta_queue")
- readiness_queue = _MockQueue("readiness_queue")
- error_queue = _MockQueue("error_queue")
- request_queue = _MockQueue("request_queue")
- response_queue = _MockQueue("response_queue")
- copy_request_queue = _MockQueue("copy_request_queue")
- copy_response_queue = _MockQueue("copy_response_queue")
- call_hash = "fe3fa0f"
- work._calls[call_hash] = {
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "run_started_counter": 1,
- "statuses": [],
- }
- caller_queue.put({
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "state": work.state,
- })
- work_runner = WorkRunner(
- work,
- work.name,
- caller_queue,
- delta_queue,
- readiness_queue,
- error_queue,
- request_queue,
- response_queue,
- copy_request_queue,
- copy_response_queue,
- )
- with contextlib.suppress(Exception, Empty):
- work_runner()
-
- res = delta_queue._queue[1].delta.to_dict()["iterable_item_added"]
- L = len(delta_queue._queue) - 1
- if enable_exception:
- exception_cls = Exception if raise_exception else Empty
- assert isinstance(error_queue._queue[-1], exception_cls)
- res_end = delta_queue._queue[L].delta.to_dict()["iterable_item_added"]
- res_end[f"root['calls']['{call_hash}']['statuses'][1]"]["stage"] == "failed"
- res_end[f"root['calls']['{call_hash}']['statuses'][1]"]["message"] == "Custom Exception"
- else:
- assert res[f"root['calls']['{call_hash}']['statuses'][0]"]["stage"] == "running"
- key = f"root['calls']['{call_hash}']['statuses'][1]"
- while L >= 0:
- res_end = delta_queue._queue[L].delta.to_dict()["iterable_item_added"]
- if key in res_end and res_end[key]["stage"] == "succeeded":
- break
- L -= 1
-
- # Stop blocking and let the thread join
- work_runner.copier.join()
-
-
-def test_lightning_work_url():
- class ExposedWork(LightningWork):
- def run(self):
- pass
-
- work = ExposedWork(port=8000)
- work._name = "root.work"
- assert work.state["vars"]["_url"] == ""
-
-
-def test_work_path_assignment():
- """Test that paths in the lit format lit:// get converted to a proper lightning.app.storage.Path object."""
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.no_path = "a/b/c"
- self.path = Path("lit://x/y/z")
- self.lit_path = "lit://x/y/z"
-
- def run(self):
- pass
-
- work = Work()
- assert isinstance(work.no_path, str)
- assert isinstance(work.path, Path)
- assert isinstance(work.lit_path, Path)
- assert work.path == work.lit_path
-
-
-@pytest.mark.skipif(_IS_WINDOWS, reason="strange TimeOut exception")
-@pytest.mark.xfail(strict=False, reason="Timeout") # fixme
-def test_work_state_change_with_path():
- """Test that type changes to a Path attribute are properly reflected within the state."""
-
- class Work(LightningFlow):
- def __init__(self):
- super().__init__()
- self.none_to_path = None
- self.path_to_none = Path()
- self.path_to_path = Path()
-
- def run(self):
- self.none_to_path = "lit://none/to/path"
- self.path_to_none = None
- self.path_to_path = "lit://path/to/path"
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = Work()
-
- def run(self):
- self.work.run()
- self.stop()
-
- flow = Flow()
- MultiProcessRuntime(LightningApp(flow)).dispatch()
- assert flow.work.none_to_path == Path("lit://none/to/path")
- assert flow.work.path_to_none is None
- assert flow.work.path_to_path == Path("lit://path/to/path")
-
- assert "path_to_none" not in flow.work._paths
- assert "path_to_none" in flow.work._state
- assert flow.work._paths["none_to_path"] == Path("lit://none/to/path").to_dict()
- assert flow.work._paths["path_to_path"] == Path("lit://path/to/path").to_dict()
- assert flow.work.state["vars"]["none_to_path"] == Path("lit://none/to/path")
- assert flow.work.state["vars"]["path_to_none"] is None
- assert flow.work.state["vars"]["path_to_path"] == Path("lit://path/to/path")
-
-
-def test_lightning_work_calls():
- class W(LightningWork):
- def run(self, *args, **kwargs):
- pass
-
- w = W()
- assert len(w._calls) == 1
- w.run(1, [2], (3, 4), {"1": "3"})
- assert len(w._calls) == 2
- assert w._calls["0d824f7"] == {"ret": None}
-
-
-def test_work_cloud_build_config_provided():
- assert isinstance(LightningWork.cloud_build_config, property)
- assert LightningWork.cloud_build_config.fset is not None
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.cloud_build_config = BuildConfig(image="ghcr.io/gridai/base-images:v1.8-cpu")
-
- def run(self, *args, **kwargs):
- pass
-
- w = Work()
- w.run()
-
-
-def test_work_local_build_config_provided():
- assert isinstance(LightningWork.local_build_config, property)
- assert LightningWork.local_build_config.fset is not None
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.local_build_config = BuildConfig(image="ghcr.io/gridai/base-images:v1.8-cpu")
-
- def run(self, *args, **kwargs):
- pass
-
- w = Work()
- w.run()
-
-
-class WorkCounter(LightningWork):
- def run(self):
- pass
-
-
-class LightningTestAppWithWork(LightningTestApp):
- def on_before_run_once(self):
- if self.root.work.has_succeeded:
- return True
- return super().on_before_run_once()
-
-
-def test_lightning_app_with_work():
- app = LightningTestAppWithWork(WorkCounter())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class WorkStart(LightningWork):
- def __init__(self, cache_calls, parallel):
- super().__init__(cache_calls=cache_calls, parallel=parallel)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class FlowStart(LightningFlow):
- def __init__(self, cache_calls, parallel):
- super().__init__()
- self.w = WorkStart(cache_calls, parallel)
- self.finish = False
-
- def run(self):
- if self.finish:
- self.stop()
- if self.w.status.stage == WorkStageStatus.STOPPED:
- with pytest.raises(Exception, match="A work can be started only once for now."):
- self.w.start()
- self.finish = True
- if self.w.status.stage == WorkStageStatus.NOT_STARTED:
- self.w.start()
- if self.w.status.stage == WorkStageStatus.STARTED:
- self.w.run()
- if self.w.counter == 1:
- self.w.stop()
-
-
-@pytest.mark.parametrize("cache_calls", [False, True])
-@pytest.mark.parametrize("parallel", [False, True])
-def test_lightning_app_work_start(cache_calls, parallel):
- app = LightningApp(FlowStart(cache_calls, parallel))
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-def test_lightning_work_delete():
- work = WorkCounter()
-
- with pytest.raises(Exception, match="Can't delete the work"):
- work.delete()
-
- mock = MagicMock()
- work._backend = mock
- work.delete()
- assert work == mock.delete_work._mock_call_args_list[0].args[1]
-
-
-class WorkDisplay(LightningWork):
- def __init__(self):
- super().__init__()
-
- def run(self):
- pass
-
-
-def test_lightning_work_display_name():
- work = WorkDisplay()
- assert work.state_vars["vars"]["_display_name"] == ""
- work.display_name = "Hello"
- assert work.state_vars["vars"]["_display_name"] == "Hello"
-
- work._calls["latest_call_hash"] = "test"
- work._calls["test"] = {"statuses": [make_status(WorkStageStatus.PENDING)]}
- with pytest.raises(RuntimeError, match="The display name can be set only before the work has started."):
- work.display_name = "HELLO"
- work.display_name = "Hello"
diff --git a/tests/tests_app/core/test_queues.py b/tests/tests_app/core/test_queues.py
deleted file mode 100644
index 328302838ba98..0000000000000
--- a/tests/tests_app/core/test_queues.py
+++ /dev/null
@@ -1,264 +0,0 @@
-import base64
-import multiprocessing
-import pickle
-import queue
-import time
-from unittest import mock
-
-import pytest
-from lightning.app import LightningFlow
-from lightning.app.core import queues
-from lightning.app.core.constants import STATE_UPDATE_TIMEOUT
-from lightning.app.core.queues import (
- READINESS_QUEUE_CONSTANT,
- BaseQueue,
- HTTPQueue,
- QueuingSystem,
- RateLimitedQueue,
- RedisQueue,
-)
-from lightning.app.utilities.imports import _is_redis_available
-from lightning.app.utilities.redis import check_if_redis_running
-
-
-@pytest.mark.skipif(not check_if_redis_running(), reason="Redis is not running")
-@pytest.mark.parametrize("queue_type", [QueuingSystem.REDIS, QueuingSystem.MULTIPROCESS])
-def test_queue_api(queue_type, monkeypatch):
- """Test the Queue API.
-
- This test run all the Queue implementation but we monkeypatch the Redis Queues to avoid external interaction
-
- """
- import redis
-
- blpop_out = (b"entry-id", pickle.dumps("test_entry"))
-
- monkeypatch.setattr(redis.Redis, "blpop", lambda *args, **kwargs: blpop_out)
- monkeypatch.setattr(redis.Redis, "rpush", lambda *args, **kwargs: None)
- monkeypatch.setattr(redis.Redis, "set", lambda *args, **kwargs: None)
- monkeypatch.setattr(redis.Redis, "get", lambda *args, **kwargs: None)
-
- test_queue = queue_type.get_readiness_queue()
- assert test_queue.name == READINESS_QUEUE_CONSTANT
- assert isinstance(test_queue, BaseQueue)
- test_queue.put("test_entry")
- assert test_queue.get() == "test_entry"
-
-
-@pytest.mark.skipif(not check_if_redis_running(), reason="Redis is not running")
-def test_redis_queue():
- queue_id = int(time.time())
- queue1 = QueuingSystem.REDIS.get_readiness_queue(queue_id=str(queue_id))
- queue2 = QueuingSystem.REDIS.get_readiness_queue(queue_id=str(queue_id + 1))
- queue1.put("test_entry1")
- queue2.put("test_entry2")
- assert queue1.get() == "test_entry1"
- assert queue2.get() == "test_entry2"
- with pytest.raises(queue.Empty):
- queue2.get(timeout=1)
- queue1.put("test_entry1")
- assert queue1.length() == 1
- queue1.clear()
- with pytest.raises(queue.Empty):
- queue1.get(timeout=1)
-
-
-@pytest.mark.skipif(not check_if_redis_running(), reason="Redis is not running")
-def test_redis_health_check_success():
- redis_queue = QueuingSystem.REDIS.get_readiness_queue()
- assert redis_queue.is_running
-
- redis_queue = RedisQueue(name="test_queue", default_timeout=1)
- assert redis_queue.is_running
-
-
-@pytest.mark.skipif(not _is_redis_available(), reason="redis is required for this test.")
-@pytest.mark.skipif(check_if_redis_running(), reason="This is testing the failure case when redis is not running")
-def test_redis_health_check_failure():
- redis_queue = RedisQueue(name="test_queue", default_timeout=1)
- assert not redis_queue.is_running
-
-
-@pytest.mark.skipif(not _is_redis_available(), reason="redis isn't installed.")
-def test_redis_credential(monkeypatch):
- monkeypatch.setattr(queues, "REDIS_HOST", "test-host")
- monkeypatch.setattr(queues, "REDIS_PORT", "test-port")
- monkeypatch.setattr(queues, "REDIS_PASSWORD", "test-password")
- redis_queue = QueuingSystem.REDIS.get_readiness_queue()
- assert redis_queue.redis.connection_pool.connection_kwargs["host"] == "test-host"
- assert redis_queue.redis.connection_pool.connection_kwargs["port"] == "test-port"
- assert redis_queue.redis.connection_pool.connection_kwargs["password"] == "test-password"
-
-
-@pytest.mark.skipif(not _is_redis_available(), reason="redis isn't installed.")
-@mock.patch("lightning.app.core.queues.redis.Redis")
-def test_redis_queue_read_timeout(redis_mock):
- redis_mock.return_value.blpop.return_value = (b"READINESS_QUEUE", pickle.dumps("test_entry"))
- redis_queue = QueuingSystem.REDIS.get_readiness_queue()
-
- # default timeout
- assert redis_queue.get(timeout=0) == "test_entry"
- assert redis_mock.return_value.blpop.call_args_list[0] == mock.call(["READINESS_QUEUE"], timeout=0.005)
-
- # custom timeout
- assert redis_queue.get(timeout=2) == "test_entry"
- assert redis_mock.return_value.blpop.call_args_list[1] == mock.call(["READINESS_QUEUE"], timeout=2)
-
- # blocking timeout
- assert redis_queue.get() == "test_entry"
- assert redis_mock.return_value.blpop.call_args_list[2] == mock.call(["READINESS_QUEUE"], timeout=0)
-
-
-@pytest.mark.parametrize(
- ("queue_type", "queue_process_mock"),
- [(QueuingSystem.MULTIPROCESS, multiprocessing)],
-)
-def test_process_queue_read_timeout(queue_type, queue_process_mock, monkeypatch):
- context = mock.MagicMock()
- queue_mocked = mock.MagicMock()
- context.Queue = queue_mocked
- monkeypatch.setattr(queue_process_mock, "get_context", mock.MagicMock(return_value=context))
- my_queue = queue_type.get_readiness_queue()
-
- # default timeout
- my_queue.get(timeout=0)
- assert queue_mocked.return_value.get.call_args_list[0] == mock.call(timeout=0.001, block=False)
-
- # custom timeout
- my_queue.get(timeout=2)
- assert queue_mocked.return_value.get.call_args_list[1] == mock.call(timeout=2, block=False)
-
- # blocking timeout
- my_queue.get()
- assert queue_mocked.return_value.get.call_args_list[2] == mock.call(timeout=None, block=True)
-
-
-@pytest.mark.skipif(not check_if_redis_running(), reason="Redis is not running")
-@mock.patch("lightning.app.core.queues.WARNING_QUEUE_SIZE", 2)
-def test_redis_queue_warning():
- my_queue = QueuingSystem.REDIS.get_api_delta_queue(queue_id="test_redis_queue_warning")
- my_queue.clear()
- with pytest.warns(UserWarning, match="is larger than the"):
- my_queue.put(None)
- my_queue.put(None)
- my_queue.put(None)
-
-
-@pytest.mark.skipif(not check_if_redis_running(), reason="Redis is not running")
-@mock.patch("lightning.app.core.queues.redis.Redis")
-def test_redis_raises_error_if_failing(redis_mock):
- import redis
-
- my_queue = QueuingSystem.REDIS.get_api_delta_queue(queue_id="test_redis_queue_warning")
- redis_mock.return_value.rpush.side_effect = redis.exceptions.ConnectionError("EROOOR")
- redis_mock.return_value.llen.side_effect = redis.exceptions.ConnectionError("EROOOR")
-
- with pytest.raises(ConnectionError, match="Your app failed because it couldn't connect to Redis."):
- redis_mock.return_value.blpop.side_effect = redis.exceptions.ConnectionError("EROOOR")
- my_queue.get()
-
- with pytest.raises(ConnectionError, match="Your app failed because it couldn't connect to Redis."):
- redis_mock.return_value.rpush.side_effect = redis.exceptions.ConnectionError("EROOOR")
- redis_mock.return_value.llen.return_value = 1
- my_queue.put(1)
-
- with pytest.raises(ConnectionError, match="Your app failed because it couldn't connect to Redis."):
- redis_mock.return_value.llen.side_effect = redis.exceptions.ConnectionError("EROOOR")
- my_queue.length()
-
-
-def test_http_queue_failure_on_queue_name():
- test_queue = HTTPQueue("test", STATE_UPDATE_TIMEOUT)
- with pytest.raises(ValueError, match="App ID couldn't be extracted"):
- test_queue.put("test")
-
- with pytest.raises(ValueError, match="App ID couldn't be extracted"):
- test_queue.get()
-
- with pytest.raises(ValueError, match="App ID couldn't be extracted"):
- test_queue.length()
-
-
-def test_http_queue_put(monkeypatch):
- monkeypatch.setattr(queues, "HTTP_QUEUE_TOKEN", "test-token")
- test_queue = HTTPQueue("WORK_QUEUE", STATE_UPDATE_TIMEOUT)
-
- response = mock.MagicMock()
- response.status_code = 201
- client = mock.MagicMock()
-
- client.post.return_value = response
- test_queue.client = client
-
- test_obj = LightningFlow()
-
- test_queue.put(test_obj)
-
-
-def test_http_queue_get(monkeypatch):
- monkeypatch.setattr(queues, "HTTP_QUEUE_TOKEN", "test-token")
- test_queue = HTTPQueue("WORK_QUEUE", STATE_UPDATE_TIMEOUT)
- response = mock.MagicMock()
- response.content = pickle.dumps("test")
- client = mock.MagicMock()
- client.post.return_value = response
- test_queue.client = client
- assert test_queue.get() == "test"
-
-
-def test_http_queue_batch_get(monkeypatch):
- monkeypatch.setattr(queues, "HTTP_QUEUE_TOKEN", "test-token")
- test_queue = HTTPQueue("WORK_QUEUE", STATE_UPDATE_TIMEOUT)
- response = mock.MagicMock()
- response.json.return_value = [
- base64.b64encode(pickle.dumps("test")).decode("utf-8"),
- base64.b64encode(pickle.dumps("test2")).decode("utf-8"),
- ]
- client = mock.MagicMock()
- client.post.return_value = response
- test_queue.client = client
- assert test_queue.batch_get() == ["test", "test2"]
-
-
-def test_unreachable_queue(monkeypatch):
- monkeypatch.setattr(queues, "HTTP_QUEUE_TOKEN", "test-token")
-
- test_queue = HTTPQueue("WORK_QUEUE", STATE_UPDATE_TIMEOUT)
-
- resp1 = mock.MagicMock()
- resp1.status_code = 204
-
- resp2 = mock.MagicMock()
- resp2.status_code = 201
-
- test_queue.client = mock.MagicMock()
- test_queue.client.post = mock.Mock(side_effect=[resp1, resp1, resp2])
-
- with pytest.raises(queue.Empty):
- test_queue._get()
-
- # Test backoff on queue.put
- test_queue.put("foo")
- assert test_queue.client.post.call_count == 3
-
-
-@mock.patch("lightning.app.core.queues.time.sleep")
-def test_rate_limited_queue(mock_sleep):
- sleeps = []
- mock_sleep.side_effect = lambda sleep_time: sleeps.append(sleep_time)
-
- mock_queue = mock.MagicMock()
-
- mock_queue.name = "inner_queue"
- mock_queue.default_timeout = 10.0
-
- rate_limited_queue = RateLimitedQueue(mock_queue, requests_per_second=1)
-
- assert rate_limited_queue.name == "inner_queue"
- assert rate_limited_queue.default_timeout == 10.0
-
- timeout = time.perf_counter() + 1
- while time.perf_counter() + sum(sleeps) < timeout:
- rate_limited_queue.get()
-
- assert mock_queue.get.call_count == 2
diff --git a/tests/tests_app/frontend/__init__.py b/tests/tests_app/frontend/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/frontend/conftest.py b/tests/tests_app/frontend/conftest.py
deleted file mode 100644
index 9b1d9f174396d..0000000000000
--- a/tests/tests_app/frontend/conftest.py
+++ /dev/null
@@ -1,74 +0,0 @@
-"""Test configuration."""
-
-# pylint: disable=protected-access
-from unittest import mock
-
-import pytest
-
-FLOW_SUB = "lit_flow"
-FLOW = f"root.{FLOW_SUB}"
-PORT = 61896
-
-FLOW_STATE = {
- "vars": {
- "_paths": {},
- "_layout": {"target": f"http://localhost:{PORT}/{FLOW}"},
- },
- "calls": {},
- "flows": {},
- "works": {},
- "structures": {},
- "changes": {},
-}
-
-APP_STATE = {
- "vars": {"_paths": {}, "_layout": [{"name": "home", "content": FLOW}]},
- "calls": {},
- "flows": {
- FLOW_SUB: FLOW_STATE,
- },
- "works": {},
- "structures": {},
- "changes": {},
- "app_state": {"stage": "running"},
-}
-
-
-def _request_state(self):
- _state = APP_STATE
- self._store_state(_state)
-
-
-@pytest.fixture()
-def flow():
- return FLOW
-
-
-@pytest.fixture(autouse=True, scope="module")
-def mock_request_state():
- """Avoid requests to the api."""
- with mock.patch("lightning.app.utilities.state.AppState._request_state", _request_state):
- yield
-
-
-def do_nothing():
- """Be lazy!"""
-
-
-@pytest.fixture(autouse=True, scope="module")
-def mock_start_websocket():
- """Avoid starting the websocket."""
- with mock.patch("lightning.app.frontend.panel.app_state_comm._start_websocket", do_nothing):
- yield
-
-
-@pytest.fixture()
-def app_state_state():
- """Returns an AppState dict."""
- return APP_STATE.copy()
-
-
-@pytest.fixture()
-def flow_state_state():
- """Returns an AppState dict scoped to the flow."""
- return FLOW_STATE.copy()
diff --git a/tests/tests_app/frontend/just_py/test_just_py.py b/tests/tests_app/frontend/just_py/test_just_py.py
deleted file mode 100644
index f273e64d5f30a..0000000000000
--- a/tests/tests_app/frontend/just_py/test_just_py.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import os
-import os.path as osp
-import sys
-from typing import Callable
-from unittest.mock import MagicMock
-
-import lightning.app
-from lightning.app.frontend import JustPyFrontend
-from lightning.app.frontend.just_py import just_py
-from lightning.app.frontend.just_py.just_py_base import _main, _webpage
-
-
-def render_fn(get_state: Callable) -> Callable:
- return _webpage
-
-
-def test_justpy_frontend(monkeypatch):
- justpy = MagicMock()
- popen = MagicMock()
- monkeypatch.setitem(sys.modules, "justpy", justpy)
- monkeypatch.setattr(just_py, "Popen", popen)
-
- frontend = JustPyFrontend(render_fn=render_fn)
- flow = MagicMock()
- flow.name = "c"
- frontend.flow = flow
- frontend.start_server("a", 90)
-
- path = osp.join(osp.dirname(lightning.app.frontend.just_py.__file__), "just_py_base.py")
-
- assert popen._mock_call_args[0][0] == f"{sys.executable} {path}"
- env = popen._mock_call_args[1]["env"]
- assert env["LIGHTNING_FLOW_NAME"] == "c"
- assert env["LIGHTNING_RENDER_FUNCTION"] == "render_fn"
- assert env["LIGHTNING_HOST"] == "a"
- assert env["LIGHTNING_PORT"] == "90"
-
- monkeypatch.setattr(os, "environ", env)
-
- _main()
-
- assert justpy.app._mock_mock_calls[0].args[0] == "/c"
- assert justpy.app._mock_mock_calls[0].args[1] == _webpage
-
- assert justpy.justpy._mock_mock_calls[0].args[0] == _webpage
- assert justpy.justpy._mock_mock_calls[0].kwargs == {"host": "a", "port": 90}
diff --git a/tests/tests_app/frontend/panel/__init__.py b/tests/tests_app/frontend/panel/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/frontend/panel/app_panel.py b/tests/tests_app/frontend/panel/app_panel.py
deleted file mode 100644
index 3d8c056cc4492..0000000000000
--- a/tests/tests_app/frontend/panel/app_panel.py
+++ /dev/null
@@ -1,4 +0,0 @@
-if __name__ == "__main__":
- import panel as pn
-
- pn.pane.Markdown("# Panel App").servable()
diff --git a/tests/tests_app/frontend/panel/test_app_state_comm.py b/tests/tests_app/frontend/panel/test_app_state_comm.py
deleted file mode 100644
index a2501db24fde1..0000000000000
--- a/tests/tests_app/frontend/panel/test_app_state_comm.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""The watch_app_state function enables us to trigger a callback function whenever the App state changes."""
-
-import os
-from unittest import mock
-
-from lightning.app.core.constants import APP_SERVER_PORT
-from lightning.app.frontend.panel.app_state_comm import _get_ws_url, _run_callbacks, _watch_app_state
-
-FLOW_SUB = "lit_flow"
-FLOW = f"root.{FLOW_SUB}"
-
-
-def do_nothing():
- """Be lazy!"""
-
-
-def test_get_ws_url_when_local():
- """The websocket uses port APP_SERVER_PORT when local."""
- assert _get_ws_url() == f"ws://localhost:{APP_SERVER_PORT}/api/v1/ws"
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_APP_STATE_URL": "some_url"})
-def test_get_ws_url_when_cloud():
- """The websocket uses port 8080 when LIGHTNING_APP_STATE_URL is set."""
- assert _get_ws_url() == "ws://localhost:8080/api/v1/ws"
-
-
-@mock.patch.dict(os.environ, {"LIGHTNING_FLOW_NAME": "FLOW"})
-def test_watch_app_state():
- """We can watch the App state and a callback function will be run when it changes."""
- callback = mock.MagicMock()
- # When
- _watch_app_state(callback)
-
- # Here we would like to send messages using the web socket
- # For testing the web socket is not started. See conftest.py
- # So we need to manually trigger _run_callbacks here
- _run_callbacks()
- # Then
- callback.assert_called_once()
diff --git a/tests/tests_app/frontend/panel/test_app_state_watcher.py b/tests/tests_app/frontend/panel/test_app_state_watcher.py
deleted file mode 100644
index f12b989654141..0000000000000
--- a/tests/tests_app/frontend/panel/test_app_state_watcher.py
+++ /dev/null
@@ -1,95 +0,0 @@
-"""The AppStateWatcher enables a Frontend to.
-
-- subscribe to App state changes.
-- to access and change the App state.
-
-This is particularly useful for the PanelFrontend, but can be used by other Frontends too.
-
-"""
-
-# pylint: disable=protected-access
-import os
-from unittest import mock
-
-import pytest
-from lightning.app.frontend.panel.app_state_watcher import AppStateWatcher
-from lightning.app.utilities.state import AppState
-from lightning_utilities.core.imports import RequirementCache
-
-_PARAM_AVAILABLE = RequirementCache("param")
-
-FLOW_SUB = "lit_flow"
-FLOW = f"root.{FLOW_SUB}"
-PORT = 61896
-
-
-@pytest.fixture(autouse=True)
-def mock_settings_env_vars():
- """Set the LIGHTNING environment variables."""
- with mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_FLOW_NAME": FLOW,
- "LIGHTNING_RENDER_ADDRESS": "localhost",
- "LIGHTNING_RENDER_PORT": f"{PORT}",
- },
- ):
- yield
-
-
-@pytest.mark.skipif(not _PARAM_AVAILABLE, reason="requires param")
-def test_init(flow_state_state: dict):
- """We can instantiate the AppStateWatcher.
-
- - the .state is set
- - the .state is scoped to the flow state
-
- """
- # When
- app = AppStateWatcher()
- # Needed as AppStateWatcher is singleton and might have been
- # instantiated and the state changed in other tests
- app._update_flow_state()
-
- # Then
- assert isinstance(app.state, AppState)
- assert app.state._state == flow_state_state
-
-
-@pytest.mark.skipif(not _PARAM_AVAILABLE, reason="requires param")
-def test_update_flow_state(flow_state_state: dict):
- """We can update the state.
-
- - the .state is scoped to the flow state
-
- """
- app = AppStateWatcher()
- org_state = app.state
- app._update_flow_state()
- assert app.state is not org_state
- assert app.state._state == flow_state_state
-
-
-@pytest.mark.skipif(not _PARAM_AVAILABLE, reason="requires param")
-def test_is_singleton():
- """The AppStateWatcher is a singleton for efficiency reasons.
-
- Its key that __new__ and __init__ of AppStateWatcher is only called once. See
- https://github.com/holoviz/param/issues/643
-
- """
- # When
- app1 = AppStateWatcher()
- name1 = app1.name
- state1 = app1.state
-
- app2 = AppStateWatcher()
- name2 = app2.name
- state2 = app2.state
-
- # Then
- assert app1 is app2
- assert name1 == name2
- assert app1.name == name2
- assert state1 is state2
- assert app1.state is state2
diff --git a/tests/tests_app/frontend/panel/test_panel_frontend.py b/tests/tests_app/frontend/panel/test_panel_frontend.py
deleted file mode 100644
index 4b6aa41a12104..0000000000000
--- a/tests/tests_app/frontend/panel/test_panel_frontend.py
+++ /dev/null
@@ -1,168 +0,0 @@
-"""The PanelFrontend wraps your Panel code in your LightningFlow."""
-
-# pylint: disable=protected-access, too-few-public-methods
-import os
-import runpy
-import sys
-from unittest import mock
-from unittest.mock import Mock
-
-import pytest
-from lightning.app import LightningFlow
-from lightning.app.frontend.panel import PanelFrontend, panel_serve_render_fn
-from lightning.app.frontend.panel.panel_frontend import _has_panel_autoreload
-from lightning.app.utilities.state import AppState
-
-
-@pytest.mark.skipif(True, reason="broken")
-def test_stop_server_not_running():
- """If the server is not running but stopped an Exception should be raised."""
- frontend = PanelFrontend(entry_point=Mock())
- with pytest.raises(RuntimeError, match="Server is not running."):
- frontend.stop_server()
-
-
-def _noop_render_fn(_):
- pass
-
-
-class MockFlow(LightningFlow):
- """Test Flow."""
-
- @property
- def name(self):
- """Return name."""
- return "root.my.flow"
-
- def run(self): # pylint: disable=arguments-differ
- """Be lazy!"""
-
-
-@mock.patch("lightning.app.frontend.panel.panel_frontend.subprocess")
-@pytest.mark.skipif(True, reason="broken")
-def test_panel_frontend_start_stop_server(subprocess_mock):
- """Test that `PanelFrontend.start_server()` invokes subprocess.Popen with the right parameters."""
- # Given
- frontend = PanelFrontend(entry_point=_noop_render_fn)
- frontend.flow = MockFlow()
- # When
- frontend.start_server(host="hostname", port=1111)
- # Then
- subprocess_mock.Popen.assert_called_once()
-
- env_variables = subprocess_mock.method_calls[0].kwargs["env"]
- call_args = subprocess_mock.method_calls[0].args[0]
- assert call_args == [
- sys.executable,
- "-m",
- "panel",
- "serve",
- panel_serve_render_fn.__file__,
- "--port",
- "1111",
- "--address",
- "hostname",
- "--prefix",
- "root.my.flow",
- "--allow-websocket-origin",
- "*",
- ]
-
- assert env_variables["LIGHTNING_FLOW_NAME"] == "root.my.flow"
- assert env_variables["LIGHTNING_RENDER_ADDRESS"] == "hostname"
- assert env_variables["LIGHTNING_RENDER_FUNCTION"] == "_noop_render_fn"
- assert env_variables["LIGHTNING_RENDER_MODULE_FILE"] == __file__
- assert env_variables["LIGHTNING_RENDER_PORT"] == "1111"
-
- assert "LIGHTNING_FLOW_NAME" not in os.environ
- assert "LIGHTNING_RENDER_FUNCTION" not in os.environ
- assert "LIGHTNING_RENDER_MODULE_FILE" not in os.environ
- assert "LIGHTNING_RENDER_MODULE_PORT" not in os.environ
- assert "LIGHTNING_RENDER_MODULE_ADDRESS" not in os.environ
- # When
- frontend.stop_server()
- # Then
- subprocess_mock.Popen().kill.assert_called_once()
-
-
-def _call_me(state):
- assert isinstance(state, AppState)
- print(state)
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_FLOW_NAME": "root",
- "LIGHTNING_RENDER_FUNCTION": "_call_me",
- "LIGHTNING_RENDER_MODULE_FILE": __file__,
- "LIGHTNING_RENDER_ADDRESS": "127.0.0.1",
- "LIGHTNING_RENDER_PORT": "61896",
- },
-)
-def test_panel_wrapper_calls_entry_point(*_):
- """Run the panel_serve_entry_point."""
- runpy.run_module("lightning.app.frontend.panel.panel_serve_render_fn")
-
-
-@pytest.mark.skipif(True, reason="broken")
-def test_method_exception():
- """The PanelFrontend does not support entry_point being a method and should raise an Exception."""
-
- class _DummyClass:
- def _render_fn(self):
- pass
-
- with pytest.raises(TypeError, match="being a method"):
- PanelFrontend(entry_point=_DummyClass()._render_fn)
-
-
-@pytest.mark.skipif(True, reason="broken")
-def test_open_close_log_files():
- """We can open and close the log files."""
- frontend = PanelFrontend(_noop_render_fn)
- assert not frontend._log_files
- # When
- frontend._open_log_files()
- # Then
- stdout = frontend._log_files["stdout"]
- stderr = frontend._log_files["stderr"]
- assert not stdout.closed
- assert not stderr.closed
-
- # When
- frontend._close_log_files()
- # Then
- assert not frontend._log_files
- assert stdout.closed
- assert stderr.closed
-
- # We can close even if not open
- frontend._close_log_files()
-
-
-@pytest.mark.parametrize(
- ("value", "expected"),
- [
- ("Yes", True),
- ("yes", True),
- ("YES", True),
- ("Y", True),
- ("y", True),
- ("True", True),
- ("true", True),
- ("TRUE", True),
- ("No", False),
- ("no", False),
- ("NO", False),
- ("N", False),
- ("n", False),
- ("False", False),
- ("false", False),
- ("FALSE", False),
- ],
-)
-def test_has_panel_autoreload(value, expected):
- """We can get and set autoreload using the environment variable PANEL_AUTORELOAD."""
- with mock.patch.dict(os.environ, {"PANEL_AUTORELOAD": value}):
- assert _has_panel_autoreload() == expected
diff --git a/tests/tests_app/frontend/panel/test_panel_serve_render_fn.py b/tests/tests_app/frontend/panel/test_panel_serve_render_fn.py
deleted file mode 100644
index 6605373b2f3b6..0000000000000
--- a/tests/tests_app/frontend/panel/test_panel_serve_render_fn.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""The panel_serve_render_fn_or_file file gets run by Python to launch a Panel Server with Lightning.
-
-These tests are for serving a render_fn function.
-
-"""
-
-import inspect
-import os
-from unittest import mock
-
-import pytest
-from lightning.app.frontend.panel.app_state_watcher import AppStateWatcher
-from lightning.app.frontend.panel.panel_serve_render_fn import _get_render_fn, _get_render_fn_from_environment
-from lightning_utilities.core.imports import RequirementCache
-
-_PARAM_AVAILABLE = RequirementCache("param")
-
-
-@pytest.fixture(autouse=True)
-def _mock_settings_env_vars():
- with mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_FLOW_NAME": "root.lit_flow",
- "LIGHTNING_RENDER_ADDRESS": "localhost",
- "LIGHTNING_RENDER_MODULE_FILE": __file__,
- "LIGHTNING_RENDER_PORT": "61896",
- },
- ):
- yield
-
-
-def render_fn(app):
- """Test render_fn function with app args."""
- return app
-
-
-@pytest.mark.skipif(not _PARAM_AVAILABLE, reason="requires param")
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_RENDER_FUNCTION": "render_fn",
- },
-)
-def test_get_view_fn_args():
- """We have a helper get_view_fn function that create a function for our view.
-
- If the render_fn provides an argument an AppStateWatcher is provided as argument
-
- """
- result = _get_render_fn()
- assert isinstance(result(), AppStateWatcher)
-
-
-def render_fn_no_args():
- """Test function with no arguments."""
- return "no_args"
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_RENDER_FUNCTION": "render_fn_no_args",
- },
-)
-def test_get_view_fn_no_args():
- """We have a helper get_view_fn function that create a function for our view.
-
- If the render_fn provides an argument an AppStateWatcher is provided as argument
-
- """
- result = _get_render_fn()
- assert result() == "no_args"
-
-
-def render_fn_2():
- """Do nothing."""
-
-
-def test_get_render_fn_from_environment():
- """We have a method to get the render_fn from the environment."""
- # When
- result = _get_render_fn_from_environment("render_fn_2", __file__)
- # Then
- assert result.__name__ == render_fn_2.__name__
- assert inspect.getmodule(result).__file__ == __file__
diff --git a/tests/tests_app/frontend/test_stream_lit.py b/tests/tests_app/frontend/test_stream_lit.py
deleted file mode 100644
index 76a3252f8f832..0000000000000
--- a/tests/tests_app/frontend/test_stream_lit.py
+++ /dev/null
@@ -1,101 +0,0 @@
-import os
-import runpy
-import sys
-from unittest import mock
-from unittest.mock import ANY, Mock
-
-import pytest
-from lightning.app import LightningFlow
-from lightning.app.frontend.stream_lit import StreamlitFrontend
-from lightning.app.utilities.state import AppState
-from lightning_utilities.core.imports import RequirementCache
-
-_STREAMLIT_AVAILABLE = RequirementCache("streamlit")
-
-
-@pytest.mark.skipif(not _STREAMLIT_AVAILABLE, reason="requires streamlit")
-def test_stop_server_not_running():
- frontend = StreamlitFrontend(render_fn=Mock())
- with pytest.raises(RuntimeError, match="Server is not running."):
- frontend.stop_server()
-
-
-def _noop_render_fn(_):
- pass
-
-
-class MockFlow(LightningFlow):
- @property
- def name(self):
- return "root.my.flow"
-
- def run(self):
- pass
-
-
-@pytest.mark.skipif(not _STREAMLIT_AVAILABLE, reason="requires streamlit")
-@mock.patch("lightning.app.frontend.stream_lit.subprocess")
-def test_streamlit_frontend_start_stop_server(subprocess_mock):
- """Test that `StreamlitFrontend.start_server()` invokes subprocess.Popen with the right parameters."""
- frontend = StreamlitFrontend(render_fn=_noop_render_fn)
- frontend.flow = MockFlow()
- frontend.start_server(host="hostname", port=1111)
- subprocess_mock.Popen.assert_called_once()
-
- env_variables = subprocess_mock.method_calls[0].kwargs["env"]
- call_args = subprocess_mock.method_calls[0].args[0]
- assert call_args == [
- sys.executable,
- "-m",
- "streamlit",
- "run",
- ANY,
- "--server.address",
- "hostname",
- "--server.port",
- "1111",
- "--server.baseUrlPath",
- "root.my.flow",
- "--server.headless",
- "true",
- "--server.enableXsrfProtection",
- "false",
- ]
-
- assert env_variables["LIGHTNING_FLOW_NAME"] == "root.my.flow"
- assert env_variables["LIGHTNING_RENDER_FUNCTION"] == "_noop_render_fn"
- assert env_variables["LIGHTNING_RENDER_MODULE_FILE"] == __file__
-
- assert "LIGHTNING_FLOW_NAME" not in os.environ
- assert "LIGHTNING_RENDER_FUNCTION" not in os.environ
- assert "LIGHTNING_RENDER_MODULE_FILE" not in os.environ
-
- frontend.stop_server()
- subprocess_mock.Popen().kill.assert_called_once()
-
-
-def _streamlit_call_me(state):
- assert isinstance(state, AppState)
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_FLOW_NAME": "root",
- "LIGHTNING_RENDER_FUNCTION": "_streamlit_call_me",
- "LIGHTNING_RENDER_MODULE_FILE": __file__,
- },
-)
-def test_streamlit_wrapper_calls_render_fn(*_):
- runpy.run_module("lightning.app.frontend.streamlit_base")
- # TODO: find a way to assert that _streamlit_call_me got called
-
-
-@pytest.mark.skipif(not _STREAMLIT_AVAILABLE, reason="requires streamlit")
-def test_method_exception():
- class A:
- def render_fn(self):
- pass
-
- with pytest.raises(TypeError, match="being a method"):
- StreamlitFrontend(render_fn=A().render_fn)
diff --git a/tests/tests_app/frontend/test_utils.py b/tests/tests_app/frontend/test_utils.py
deleted file mode 100644
index 367b95e32bcca..0000000000000
--- a/tests/tests_app/frontend/test_utils.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""We have some utility functions that can be used across frontends."""
-
-from lightning.app.frontend.utils import _get_flow_state, _get_frontend_environment
-from lightning.app.utilities.state import AppState
-
-
-def test_get_flow_state(flow_state_state: dict, flow):
- """We have a method to get an AppState scoped to the Flow state."""
- # When
- flow_state = _get_flow_state(flow)
- # Then
- assert isinstance(flow_state, AppState)
- assert flow_state._state == flow_state_state # pylint: disable=protected-access
-
-
-def some_fn(_):
- """Be lazy!"""
-
-
-def test_get_frontend_environment_fn():
- """We have a utility function to get the frontend render_fn environment."""
- # When
- env = _get_frontend_environment(flow="root.lit_frontend", render_fn_or_file=some_fn, host="myhost", port=1234)
- # Then
- assert env["LIGHTNING_FLOW_NAME"] == "root.lit_frontend"
- assert env["LIGHTNING_RENDER_ADDRESS"] == "myhost"
- assert env["LIGHTNING_RENDER_FUNCTION"] == "some_fn"
- assert env["LIGHTNING_RENDER_MODULE_FILE"] == __file__
- assert env["LIGHTNING_RENDER_PORT"] == "1234"
-
-
-def test_get_frontend_environment_file():
- """We have a utility function to get the frontend render_fn environment."""
- # When
- env = _get_frontend_environment(
- flow="root.lit_frontend", render_fn_or_file="app_panel.py", host="myhost", port=1234
- )
- # Then
- assert env["LIGHTNING_FLOW_NAME"] == "root.lit_frontend"
- assert env["LIGHTNING_RENDER_ADDRESS"] == "myhost"
- assert env["LIGHTNING_RENDER_FILE"] == "app_panel.py"
- assert env["LIGHTNING_RENDER_PORT"] == "1234"
diff --git a/tests/tests_app/frontend/test_web.py b/tests/tests_app/frontend/test_web.py
deleted file mode 100644
index 9e990ec911295..0000000000000
--- a/tests/tests_app/frontend/test_web.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import os
-from unittest import mock
-from unittest.mock import ANY, MagicMock
-
-import lightning.app
-import pytest
-from lightning.app import LightningFlow
-from lightning.app.frontend.web import StaticWebFrontend, _healthz
-from lightning.app.storage.path import _storage_root_dir
-
-
-def test_stop_server_not_running():
- frontend = StaticWebFrontend(serve_dir=".")
- with pytest.raises(RuntimeError, match="Server is not running."):
- frontend.stop_server()
-
-
-class MockFlow(LightningFlow):
- @property
- def name(self):
- return "root.my.flow"
-
- def run(self):
- pass
-
-
-@mock.patch("lightning.app.frontend.web.mp.Process")
-def test_start_stop_server_through_frontend(process_mock):
- frontend = StaticWebFrontend(serve_dir=".")
- frontend.flow = MockFlow()
- frontend.start_server("localhost", 5000)
- log_file_root = _storage_root_dir()
- process_mock.assert_called_once_with(
- target=lightning.app.frontend.web._start_server,
- kwargs={
- "host": "localhost",
- "port": 5000,
- "serve_dir": ".",
- "path": "/root.my.flow",
- "log_file": os.path.join(log_file_root, "frontend", "logs.log"),
- "root_path": "",
- },
- )
- process_mock().start.assert_called_once()
- frontend.stop_server()
- process_mock().kill.assert_called_once()
-
-
-@mock.patch("lightning.app.frontend.web.uvicorn")
-@pytest.mark.parametrize("root_path", ["", "/base"])
-def test_start_server_through_function(uvicorn_mock, tmpdir, monkeypatch, root_path):
- FastAPIMock = MagicMock()
- FastAPIMock.mount = MagicMock()
- FastAPIGetDecoratorMock = MagicMock()
- FastAPIMock.get.return_value = FastAPIGetDecoratorMock
- monkeypatch.setattr(lightning.app.frontend.web, "FastAPI", MagicMock(return_value=FastAPIMock))
-
- lightning.app.frontend.web._start_server(
- serve_dir=tmpdir, host="myhost", port=1000, path="/test-flow", root_path=root_path
- )
- uvicorn_mock.run.assert_called_once_with(app=ANY, host="myhost", port=1000, log_config=ANY, root_path=root_path)
-
- FastAPIMock.mount.assert_called_once_with(root_path or "/test-flow", ANY, name="static")
- FastAPIMock.get.assert_called_once_with("/test-flow/healthz", status_code=200)
-
- FastAPIGetDecoratorMock.assert_called_once_with(_healthz)
-
- # path has default value "/"
- FastAPIMock.mount = MagicMock()
- lightning.app.frontend.web._start_server(serve_dir=tmpdir, host="myhost", port=1000, root_path=root_path)
- FastAPIMock.mount.assert_called_once_with(root_path or "/", ANY, name="static")
-
-
-def test_healthz():
- assert _healthz() == {"status": "ok"}
-
-
-@mock.patch("lightning.app.frontend.web.uvicorn")
-def test_start_server_find_free_port(uvicorn_mock, tmpdir):
- lightning.app.frontend.web._start_server(serve_dir=tmpdir, host="myhost")
- assert uvicorn_mock.run.call_args_list[0].kwargs["port"] > 0
diff --git a/tests/tests_app/frontend/utilities/__init__.py b/tests/tests_app/frontend/utilities/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/helpers/__init__.py b/tests/tests_app/helpers/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/launcher/test_lightning_backend.py b/tests/tests_app/launcher/test_lightning_backend.py
deleted file mode 100644
index 9138408b6e53a..0000000000000
--- a/tests/tests_app/launcher/test_lightning_backend.py
+++ /dev/null
@@ -1,807 +0,0 @@
-import json
-import os
-from copy import copy
-from datetime import datetime
-from unittest import mock
-from unittest.mock import ANY, MagicMock, Mock
-
-import pytest
-from lightning.app import BuildConfig, CloudCompute, LightningWork
-from lightning.app.launcher.lightning_backend import CloudBackend
-from lightning.app.storage import Drive, Mount
-from lightning.app.testing.helpers import EmptyWork
-from lightning.app.utilities.enum import WorkFailureReasons, WorkStageStatus
-from lightning.app.utilities.exceptions import LightningPlatformException
-from lightning_cloud.openapi import Body5, V1DriveType, V1LightningworkState, V1SourceType
-from lightning_cloud.openapi.rest import ApiException
-
-
-class WorkWithDrive(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.drive = None
-
- def run(self):
- pass
-
-
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_no_update_when_no_works(client_mock):
- cloud_backend = CloudBackend("")
- cloud_backend._get_cloud_work_specs = Mock()
- client_mock.assert_called_once()
- cloud_backend.update_work_statuses(works=[])
- cloud_backend._get_cloud_work_specs.assert_not_called()
-
-
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_no_update_when_all_work_has_started(client_mock):
- cloud_backend = CloudBackend("")
- cloud_backend._get_cloud_work_specs = MagicMock()
- client_mock.assert_called_once()
- started_mock = MagicMock()
- started_mock.has_started = True
-
- # all works have started
- works = [started_mock, started_mock]
- cloud_backend.update_work_statuses(works=works)
- cloud_backend._get_cloud_work_specs.assert_called_once()
-
-
-@mock.patch("lightning.app.launcher.lightning_backend.monotonic")
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_no_update_within_interval(client_mock, monotonic_mock):
- cloud_backend = CloudBackend("", status_update_interval=2)
- cloud_backend._get_cloud_work_specs = Mock()
- client_mock.assert_called_once()
- cloud_backend._last_time_updated = 1
- monotonic_mock.return_value = 2
-
- stopped_mock = Mock()
- stopped_mock.has_started = False
-
- # not all works have started
- works = [stopped_mock, stopped_mock]
-
- cloud_backend.update_work_statuses(works=works)
- cloud_backend._get_cloud_work_specs.assert_not_called()
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.monotonic")
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_update_within_interval(client_mock, monotonic_mock):
- cloud_backend = CloudBackend("", status_update_interval=2)
- cloud_backend._last_time_updated = 1
- # pretend a lot of time has passed since the last update
- monotonic_mock.return_value = 8
-
- stopped_mock1 = Mock()
- stopped_mock1.has_started = False
- stopped_mock1.name = "root.mock1"
- stopped_mock2 = Mock()
- stopped_mock2.has_started = False
- stopped_mock2.name = "root.mock2"
-
- spec1 = Mock()
- spec1.name = "root.mock1"
- spec2 = Mock()
- spec2.name = "root.mock2"
-
- # not all works have started
- works = [stopped_mock1, stopped_mock2]
-
- cloud_backend.update_work_statuses(works=works)
- client_mock().lightningwork_service_list_lightningwork.assert_called_with(project_id="project_id", app_id="app_id")
-
- # TODO: assert calls on the work mocks
- # ...
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_stop_all_works(mock_client):
- work_a = EmptyWork()
- work_a._name = "root.work_a"
- work_a._calls = {
- "latest_call_hash": "some_call_hash",
- "some_call_hash": {
- "statuses": [
- {
- "stage": WorkStageStatus.FAILED,
- "timestamp": int(datetime.now().timestamp()),
- "reason": WorkFailureReasons.USER_EXCEPTION,
- },
- ]
- },
- }
-
- work_b = EmptyWork()
- work_b._name = "root.work_b"
- work_b._calls = {
- "latest_call_hash": "some_call_hash",
- "some_call_hash": {
- "statuses": [{"stage": WorkStageStatus.RUNNING, "timestamp": int(datetime.now().timestamp()), "reason": ""}]
- },
- }
-
- cloud_backend = CloudBackend("")
-
- spec1 = Mock()
- spec1.name = "root.work_a"
- spec1.spec.desired_state = V1LightningworkState.STOPPED
- spec1.status.phase = V1LightningworkState.FAILED
- spec2 = Mock()
- spec2.name = "root.work_b"
- spec2.spec.desired_state = V1LightningworkState.RUNNING
-
- class BackendMock:
- def __init__(self):
- self.called = 0
-
- def _get_cloud_work_specs(self, *_):
- value = [spec1, spec2] if not self.called else []
- self.called += 1
- return value
-
- cloud_backend._get_cloud_work_specs = BackendMock()._get_cloud_work_specs
-
- def lightningwork_service_batch_update_lightningworks(*args, **kwargs):
- spec2.spec.desired_state = V1LightningworkState.STOPPED
-
- mock_client().lightningwork_service_batch_update_lightningworks = lightningwork_service_batch_update_lightningworks
-
- cloud_backend.stop_all_works([work_a, work_b])
- assert spec1.spec.desired_state == V1LightningworkState.STOPPED
- assert spec2.spec.desired_state == V1LightningworkState.STOPPED
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_stop_work(mock_client):
- work = EmptyWork()
- work._name = "root.work"
- work._calls = {
- "latest_call_hash": "some_call_hash",
- "some_call_hash": {
- "statuses": [
- {
- "stage": WorkStageStatus.RUNNING,
- "timestamp": int(datetime.now().timestamp()),
- "reason": "",
- },
- ]
- },
- }
-
- cloud_backend = CloudBackend("")
- spec1 = Mock()
- spec1.name = "root.work"
- spec1.spec.desired_state = V1LightningworkState.RUNNING
-
- spec2 = Mock()
- spec2.name = "root.work_b"
- spec2.spec.desired_state = V1LightningworkState.RUNNING
-
- class BackendMock:
- def __init__(self):
- self.called = 0
-
- def _get_cloud_work_specs(self, *_):
- value = [spec1, spec2] if not self.called else []
- self.called += 1
- return value
-
- cloud_backend._get_cloud_work_specs = BackendMock()._get_cloud_work_specs
- cloud_backend.stop_work(MagicMock(), work)
-
- mock_client().lightningwork_service_update_lightningwork.assert_called_with(
- project_id="project_id",
- id=ANY,
- spec_lightningapp_instance_id="app_id",
- body=ANY,
- )
- assert spec1.spec.desired_state == V1LightningworkState.STOPPED
- assert spec2.spec.desired_state == V1LightningworkState.RUNNING
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_create_work_where_work_does_not_exists(mock_client):
- cloud_backend = CloudBackend("")
- non_matching_spec = Mock()
- app = MagicMock()
- work = EmptyWork(port=1111)
- work._name = "name"
-
- def lightningwork_service_create_lightningwork(
- project_id: str = None,
- spec_lightningapp_instance_id: str = None,
- body: "Body5" = None,
- ):
- assert project_id == "project_id"
- assert spec_lightningapp_instance_id == "app_id"
- assert len(body.spec.network_config) == 1
- assert body.spec.network_config[0].port == 1111
- assert not body.spec.network_config[0].host
- body.spec.network_config[0].host = "x.lightning.ai"
- return body
-
- response_mock = Mock()
- response_mock.lightningworks = [non_matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
- mock_client().lightningwork_service_create_lightningwork = lightningwork_service_create_lightningwork
-
- cloud_backend.create_work(app, work)
- assert work._future_url == "https://x.lightning.ai"
- app.work_queues["name"].put.assert_called_once_with(work)
-
- # testing whether the exception is raised correctly when the backend throws on work creation
- http_resp = MagicMock()
- error_message = "exception generated from test_create_work_where_work_does_not_exists test case"
- http_resp.data = json.dumps({"message": error_message})
- mock_client().lightningwork_service_create_lightningwork = MagicMock()
- mock_client().lightningwork_service_create_lightningwork.side_effect = ApiException(http_resp=http_resp)
- with pytest.raises(LightningPlatformException, match=error_message):
- cloud_backend.create_work(app, work)
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_create_work_with_drives_where_work_does_not_exists(mock_client, tmpdir):
- cloud_backend = CloudBackend("")
- non_matching_spec = Mock()
- app = MagicMock()
-
- mocked_drive = MagicMock(spec=Drive)
- setattr(mocked_drive, "id", "foobar")
- setattr(mocked_drive, "protocol", "lit://")
- setattr(mocked_drive, "component_name", "test-work")
- setattr(mocked_drive, "allow_duplicates", False)
- setattr(mocked_drive, "root_folder", tmpdir)
- # deepcopy on a MagicMock instance will return an empty magicmock instance. To
- # overcome this we set the __deepcopy__ method `return_value` to equal what
- # should be the results of the deepcopy operation (an instance of the original class)
- mocked_drive.__deepcopy__.return_value = copy(mocked_drive)
-
- work = WorkWithDrive(port=1111)
- work._name = "test-work-name"
- work.drive = mocked_drive
-
- def lightningwork_service_create_lightningwork(
- project_id: str = None,
- spec_lightningapp_instance_id: str = None,
- body: "Body5" = None,
- ):
- assert project_id == "project_id"
- assert spec_lightningapp_instance_id == "app_id"
- assert len(body.spec.network_config) == 1
- assert body.spec.network_config[0].port == 1111
- assert not body.spec.network_config[0].host
- body.spec.network_config[0].host = "x.lightning.ai"
- assert len(body.spec.drives) == 1
- assert body.spec.drives[0].drive.spec.drive_type == V1DriveType.NO_MOUNT_S3
- assert body.spec.drives[0].drive.spec.source_type == V1SourceType.S3
- assert body.spec.drives[0].drive.spec.source == "lit://foobar"
- assert body.spec.drives[0].drive.metadata.name == "test-work-name.drive"
- for v in body.spec.drives[0].drive.status.to_dict().values():
- assert v is None
-
- return body
-
- response_mock = Mock()
- response_mock.lightningworks = [non_matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
- mock_client().lightningwork_service_create_lightningwork = lightningwork_service_create_lightningwork
-
- cloud_backend.create_work(app, work)
- assert work._future_url == "https://x.lightning.ai"
- app.work_queues["test-work-name"].put.assert_called_once_with(work)
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- "LIGHTNING_PROXY_SCHEME": "http",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_create_work_proxy_http(mock_client, tmpdir):
- cloud_backend = CloudBackend("")
- non_matching_spec = Mock()
- app = MagicMock()
-
- mocked_drive = MagicMock(spec=Drive)
- setattr(mocked_drive, "id", "foobar")
- setattr(mocked_drive, "protocol", "lit://")
- setattr(mocked_drive, "component_name", "test-work")
- setattr(mocked_drive, "allow_duplicates", False)
- setattr(mocked_drive, "root_folder", tmpdir)
- # deepcopy on a MagicMock instance will return an empty magicmock instance. To
- # overcome this we set the __deepcopy__ method `return_value` to equal what
- # should be the results of the deepcopy operation (an instance of the original class)
- mocked_drive.__deepcopy__.return_value = copy(mocked_drive)
-
- work = WorkWithDrive(port=1111)
- work._name = "test-work-name"
- work.drive = mocked_drive
-
- def lightningwork_service_create_lightningwork(
- project_id: str = None,
- spec_lightningapp_instance_id: str = None,
- body: "Body5" = None,
- ):
- assert project_id == "project_id"
- assert spec_lightningapp_instance_id == "app_id"
- assert len(body.spec.network_config) == 1
- assert body.spec.network_config[0].port == 1111
- assert not body.spec.network_config[0].host
- body.spec.network_config[0].host = "x.lightning.ai"
- assert len(body.spec.drives) == 1
- assert body.spec.drives[0].drive.spec.drive_type == V1DriveType.NO_MOUNT_S3
- assert body.spec.drives[0].drive.spec.source_type == V1SourceType.S3
- assert body.spec.drives[0].drive.spec.source == "lit://foobar"
- assert body.spec.drives[0].drive.metadata.name == "test-work-name.drive"
- for v in body.spec.drives[0].drive.status.to_dict().values():
- assert v is None
-
- return body
-
- response_mock = Mock()
- response_mock.lightningworks = [non_matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
- mock_client().lightningwork_service_create_lightningwork = lightningwork_service_create_lightningwork
-
- cloud_backend.create_work(app, work)
- assert work._future_url == "http://x.lightning.ai"
- app.work_queues["test-work-name"].put.assert_called_once_with(work)
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_update_work_with_changed_compute_config_with_mounts(mock_client):
- cloud_backend = CloudBackend("")
- matching_spec = Mock()
- app = MagicMock()
- work = EmptyWork(cloud_compute=CloudCompute("default"), cloud_build_config=BuildConfig(image="image1"))
- work._name = "work_name"
-
- matching_spec.spec = cloud_backend._work_to_spec(work)
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- matching_spec.name = "work_name"
-
- response_mock = Mock()
- response_mock.lightningworks = [matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
-
- cloud_backend.create_work(app, work)
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.desired_state
- == V1LightningworkState.RUNNING
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.user_requested_compute_config.name
- == "cpu-small"
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.build_spec.image
- == "image1"
- )
-
- # resetting the values changed in the previous step
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- cloud_backend.client.lightningwork_service_update_lightningwork.reset_mock()
-
- # new work with same name but different compute config
- mount = Mount(source="s3://foo/", mount_path="/foo")
- work = EmptyWork(cloud_compute=CloudCompute("gpu", mounts=mount), cloud_build_config=BuildConfig(image="image2"))
- work._name = "work_name"
- cloud_backend.create_work(app, work)
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.desired_state
- == V1LightningworkState.RUNNING
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.user_requested_compute_config.name
- == "gpu"
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs["body"]
- .spec.drives[0]
- .mount_location
- == "/foo"
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs["body"]
- .spec.drives[0]
- .drive.spec.source
- == "s3://foo/"
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.build_spec.image
- == "image2"
- )
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_create_work_where_work_already_exists(mock_client):
- cloud_backend = CloudBackend("")
- matching_spec = Mock()
- app = MagicMock()
- work = EmptyWork(port=1111)
- work._name = "work_name"
- work._backend = cloud_backend
-
- matching_spec.spec = cloud_backend._work_to_spec(work)
- matching_spec.spec.network_config[0].host = "x.lightning.ai"
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- matching_spec.name = "work_name"
-
- response_mock = Mock()
- response_mock.lightningworks = [matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
-
- cloud_backend.create_work(app, work)
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.desired_state
- == V1LightningworkState.RUNNING
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs["body"]
- .spec.network_config[0]
- .port
- == 1111
- )
- assert work._future_url == "https://x.lightning.ai"
- app.work_queues["work_name"].put.assert_called_once_with(work)
-
- # resetting the values changed in the previous step
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- cloud_backend.client.lightningwork_service_update_lightningwork.reset_mock()
- app.work_queues["work_name"].put.reset_mock()
-
- # changing the port
- work._port = 2222
- cloud_backend.create_work(app, work)
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs["body"]
- .spec.network_config[0]
- .port
- == 2222
- )
- app.work_queues["work_name"].put.assert_called_once_with(work)
-
- # testing whether the exception is raised correctly when the backend throws on work creation
- # resetting the values changed in the previous step
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- http_resp = MagicMock()
- error_message = "exception generated from test_create_work_where_work_already_exists test case"
- http_resp.data = json.dumps({"message": error_message})
- mock_client().lightningwork_service_update_lightningwork = MagicMock()
- mock_client().lightningwork_service_update_lightningwork.side_effect = ApiException(http_resp=http_resp)
- with pytest.raises(LightningPlatformException, match=error_message):
- cloud_backend.create_work(app, work)
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_create_work_will_have_none_backend(mockclient):
- def queue_put_mock(work):
- # because we remove backend before pushing to queue
- assert work._backend is None
-
- cloud_backend = CloudBackend("")
- app = MagicMock()
- work = EmptyWork()
- # attaching backend - this will be removed by the queue
- work._backend = cloud_backend
- app.work_queues["work_name"].put = queue_put_mock
- cloud_backend.create_work(app, work)
- # make sure the work still have the backend attached
- assert work._backend == cloud_backend
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_update_work_with_changed_compute_config_and_build_spec(mock_client):
- cloud_backend = CloudBackend("")
- matching_spec = Mock()
- app = MagicMock()
- work = EmptyWork(cloud_compute=CloudCompute("default"), cloud_build_config=BuildConfig(image="image1"))
- work._name = "work_name"
-
- matching_spec.spec = cloud_backend._work_to_spec(work)
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- matching_spec.name = "work_name"
-
- response_mock = Mock()
- response_mock.lightningworks = [matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
-
- cloud_backend.create_work(app, work)
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.desired_state
- == V1LightningworkState.RUNNING
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.user_requested_compute_config.name
- == "cpu-small"
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.build_spec.image
- == "image1"
- )
-
- # resetting the values changed in the previous step
- matching_spec.spec.desired_state = V1LightningworkState.STOPPED
- cloud_backend.client.lightningwork_service_update_lightningwork.reset_mock()
-
- # new work with same name but different compute config
- work = EmptyWork(cloud_compute=CloudCompute("gpu"), cloud_build_config=BuildConfig(image="image2"))
- work._name = "work_name"
- cloud_backend.create_work(app, work)
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.desired_state
- == V1LightningworkState.RUNNING
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.user_requested_compute_config.name
- == "gpu"
- )
- assert (
- cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args.kwargs[
- "body"
- ].spec.build_spec.image
- == "image2"
- )
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_update_work_with_changed_spec_while_work_running(mock_client):
- cloud_backend = CloudBackend("")
- matching_spec = Mock()
- app = MagicMock()
- work = EmptyWork(cloud_compute=CloudCompute("default"), cloud_build_config=BuildConfig(image="image1"))
- work._name = "work_name"
-
- matching_spec.spec = cloud_backend._work_to_spec(work)
- matching_spec.spec.desired_state = V1LightningworkState.RUNNING
- matching_spec.name = "work_name"
-
- response_mock = Mock()
- response_mock.lightningworks = [matching_spec]
- mock_client().lightningwork_service_list_lightningwork.return_value = response_mock
-
- cloud_backend.create_work(app, work)
-
- # asserting the method is not called
- cloud_backend.client.lightningwork_service_update_lightningwork.assert_not_called()
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_update_lightning_app_frontend_new_frontends(mock_client):
- cloud_backend = CloudBackend("")
- cloud_backend.client = mock_client
- mocked_app = MagicMock()
- mocked_app.frontends.keys.return_value = ["frontend2", "frontend1"]
- app_instance_mock = MagicMock()
- app_instance_mock.spec.flow_servers = []
- update_lightning_app_instance_mock = MagicMock()
- mock_client.lightningapp_instance_service_get_lightningapp_instance.return_value = app_instance_mock
- mock_client.lightningapp_instance_service_update_lightningapp_instance.return_value = (
- update_lightning_app_instance_mock
- )
- cloud_backend.update_lightning_app_frontend(mocked_app)
- assert mock_client.lightningapp_instance_service_update_lightningapp_instance.call_count == 1
-
- # frontends should be sorted
- assert (
- mock_client.lightningapp_instance_service_update_lightningapp_instance.call_args.kwargs["body"]
- .spec.flow_servers[0]
- .name
- == "frontend1"
- )
- assert (
- mock_client.lightningapp_instance_service_update_lightningapp_instance.call_args.kwargs["body"]
- .spec.flow_servers[1]
- .name
- == "frontend2"
- )
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_update_lightning_app_frontend_existing_frontends(mock_client):
- cloud_backend = CloudBackend("")
- cloud_backend.client = mock_client
- mocked_app = MagicMock()
- mocked_app.frontends.keys.return_value = ["frontend2", "frontend1"]
- app_instance_mock = MagicMock()
- app_instance_mock.spec.flow_servers = ["frontend2", "frontend1"]
- update_lightning_app_instance_mock = MagicMock()
- mock_client.lightningapp_instance_service_get_lightningapp_instance.return_value = app_instance_mock
- mock_client.lightningapp_instance_service_update_lightningapp_instance.return_value = (
- update_lightning_app_instance_mock
- )
- cloud_backend.update_lightning_app_frontend(mocked_app)
-
- # the app spec already has the frontends, so no update should be called
- assert mock_client.lightningapp_instance_service_update_lightningapp_instance.call_count == 0
- assert mock_client.lightningapp_instance_service_update_lightningapp_instance.call_count == 0
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.utilities.network.create_swagger_client", MagicMock())
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_stop_app(mock_client):
- cloud_backend = CloudBackend("")
- external_spec = MagicMock()
- mock_client.lightningapp_instance_service_get_lightningapp_instance.return_value = external_spec
- cloud_backend.client = mock_client
- mocked_app = MagicMock()
- cloud_backend.stop_app(mocked_app)
- spec = mock_client.lightningapp_instance_service_update_lightningapp_instance._mock_call_args.kwargs["body"].spec
- assert spec.desired_state == "LIGHTNINGAPP_INSTANCE_STATE_STOPPED"
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_failed_works_during_pending(client_mock):
- cloud_backend = CloudBackend("")
- cloud_work = MagicMock()
- cloud_work.name = "a"
- cloud_work.status.phase = V1LightningworkState.FAILED
- cloud_backend._get_cloud_work_specs = MagicMock(return_value=[cloud_work])
-
- local_work = MagicMock()
- local_work.status.stage = "pending"
- local_work.name = "a"
- local_work._raise_exception = True
-
- with pytest.raises(Exception, match="The work a failed during pending phase."):
- # all works have started
- cloud_backend.update_work_statuses(works=[local_work])
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_CLOUD_PROJECT_ID": "project_id",
- "LIGHTNING_CLOUD_APP_ID": "app_id",
- },
-)
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_work_delete(client_mock):
- cloud_backend = CloudBackend("")
- cloud_work = MagicMock()
- cloud_work.name = "a"
- cloud_work.status.phase = V1LightningworkState.RUNNING
- cloud_backend._get_cloud_work_specs = MagicMock(return_value=[cloud_work])
-
- local_work = MagicMock()
- local_work.status.stage = "running"
- local_work.name = "a"
- local_work._raise_exception = True
- cloud_backend.delete_work(None, local_work)
- call = cloud_backend.client.lightningwork_service_update_lightningwork._mock_call_args_list[0]
- assert call.kwargs["body"].spec.desired_state == V1LightningworkState.DELETED
diff --git a/tests/tests_app/launcher/test_lightning_hydrid.py b/tests/tests_app/launcher/test_lightning_hydrid.py
deleted file mode 100644
index 695d3216a2316..0000000000000
--- a/tests/tests_app/launcher/test_lightning_hydrid.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from unittest import mock
-
-from lightning.app import CloudCompute
-from lightning.app.launcher.lightning_hybrid_backend import CloudHybridBackend
-
-
-@mock.patch("lightning.app.launcher.lightning_backend.LightningClient")
-def test_backend_selection(client_mock):
- cloud_backend = CloudHybridBackend("", queue_id="")
- work = mock.MagicMock()
- work.cloud_compute = CloudCompute()
- assert cloud_backend._get_backend(work) == cloud_backend.backends["multiprocess"]
- work.cloud_compute = CloudCompute("gpu")
- assert cloud_backend._get_backend(work) == cloud_backend.backends["cloud"]
diff --git a/tests/tests_app/launcher/test_running_flow.py b/tests/tests_app/launcher/test_running_flow.py
deleted file mode 100644
index 945f6076d8899..0000000000000
--- a/tests/tests_app/launcher/test_running_flow.py
+++ /dev/null
@@ -1,133 +0,0 @@
-import logging
-import os
-import signal
-import sys
-from unittest import mock
-from unittest.mock import MagicMock, Mock
-
-import pytest
-import requests
-from lightning.app.launcher import launcher, lightning_backend
-from lightning.app.utilities.app_helpers import convert_print_to_logger_info
-from lightning.app.utilities.enum import AppStage
-from lightning.app.utilities.exceptions import ExitAppException
-
-
-def _make_mocked_network_config(key, host):
- network_config = Mock()
- network_config.name = key
- network_config.host = host
- return network_config
-
-
-@mock.patch("lightning.app.core.queues.QueuingSystem", mock.MagicMock())
-@mock.patch("lightning.app.launcher.launcher.check_if_redis_running", MagicMock(return_value=True))
-def test_running_flow(monkeypatch):
- app = MagicMock()
- flow = MagicMock()
- work = MagicMock()
- work.run.__name__ = "run"
- flow._layout = {}
- flow.name = "flowname"
- work.name = "workname"
- app.flows = [flow]
- flow.works.return_value = [work]
-
- def load_app_from_file(file):
- assert file == "file.py"
- return app
-
- class BackendMock:
- def __init__(self, return_value):
- self.called = 0
- self.return_value = return_value
-
- def _get_cloud_work_specs(self, *_):
- value = self.return_value if not self.called else []
- self.called += 1
- return value
-
- cloud_work_spec = Mock()
- cloud_work_spec.name = "workname"
- cloud_work_spec.spec.network_config = [
- _make_mocked_network_config("key1", "x.lightning.ai"),
- ]
- monkeypatch.setattr(launcher, "load_app_from_file", load_app_from_file)
- monkeypatch.setattr(launcher, "start_server", MagicMock())
- monkeypatch.setattr(lightning_backend, "LightningClient", MagicMock())
- lightning_backend.CloudBackend._get_cloud_work_specs = BackendMock(
- return_value=[cloud_work_spec]
- )._get_cloud_work_specs
- monkeypatch.setattr(lightning_backend.CloudBackend, "_get_project_id", MagicMock())
- monkeypatch.setattr(lightning_backend.CloudBackend, "_get_app_id", MagicMock())
- queue_system = MagicMock()
- queue_system.REDIS = MagicMock()
- monkeypatch.setattr(launcher, "QueuingSystem", queue_system)
- monkeypatch.setattr(launcher, "StorageOrchestrator", MagicMock())
-
- response = MagicMock()
- response.status_code = 200
- monkeypatch.setattr(requests, "get", MagicMock(return_value=response))
-
- # # testing with correct base URL
- # with pytest.raises(SystemExit, match="0"):
- # launcher.run_lightning_flow("file.py", queue_id="", base_url="http://localhost:8080")
- # assert flow._layout["target"] == "http://localhost:8080/flowname/"
-
- # app._run.assert_called_once()
-
- # # testing with invalid base URL
- # with pytest.raises(ValueError, match="Base URL doesn't have a valid scheme"):
- # launcher.run_lightning_flow("file.py", queue_id="", base_url="localhost:8080")
-
- app.flows = []
-
- def run_patch():
- raise Exception
-
- app._run = run_patch
-
- with pytest.raises(SystemExit, match="1"):
- launcher.run_lightning_flow("file.py", queue_id="", base_url="localhost:8080")
-
- def run_patch():
- app.stage = AppStage.FAILED
-
- app._run = run_patch
-
- with pytest.raises(SystemExit, match="1"):
- launcher.run_lightning_flow("file.py", queue_id="", base_url="localhost:8080")
-
- def run_patch():
- raise ExitAppException
-
- if sys.platform == "win32":
- return
-
- app.stage = AppStage.STOPPING
-
- app._run = run_patch
- with pytest.raises(SystemExit, match="0"):
- launcher.run_lightning_flow("file.py", queue_id="", base_url="localhost:8080")
-
- def run_method():
- os.kill(os.getpid(), signal.SIGTERM)
-
- app._run = run_method
- monkeypatch.setattr(lightning_backend.CloudBackend, "resolve_url", MagicMock())
- with pytest.raises(SystemExit, match="0"):
- launcher.run_lightning_flow("file.py", queue_id="", base_url="localhost:8080")
- assert app.stage == AppStage.STOPPING
-
-
-def test_replace_print_to_info(caplog, monkeypatch):
- monkeypatch.setattr("lightning.app._logger", logging.getLogger())
-
- @convert_print_to_logger_info
- def fn_captured(value):
- print(value)
-
- with caplog.at_level(logging.INFO):
- fn_captured(1)
-
- assert caplog.messages == ["1"]
diff --git a/tests/tests_app/plugin/__init__.py b/tests/tests_app/plugin/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/plugin/test_plugin.py b/tests/tests_app/plugin/test_plugin.py
deleted file mode 100644
index c4867af04dd2b..0000000000000
--- a/tests/tests_app/plugin/test_plugin.py
+++ /dev/null
@@ -1,221 +0,0 @@
-import io
-import json
-import sys
-import tarfile
-from dataclasses import dataclass
-from pathlib import Path
-from unittest import mock
-
-import pytest
-from fastapi import status
-from fastapi.testclient import TestClient
-from lightning.app.plugin.plugin import _Run, _start_plugin_server
-from lightning_cloud.openapi import Externalv1LightningappInstance
-
-
-@pytest.fixture()
-@mock.patch("lightning.app.plugin.plugin.uvicorn")
-def mock_plugin_server(mock_uvicorn) -> TestClient:
- """This fixture returns a `TestClient` for the plugin server."""
- test_client = {}
-
- def create_test_client(app, **_):
- test_client["client"] = TestClient(app)
-
- mock_uvicorn.run.side_effect = create_test_client
-
- _start_plugin_server(8888)
-
- return test_client["client"]
-
-
-@dataclass
-class _MockResponse:
- content: bytes
-
- def raise_for_status(self):
- pass
-
-
-def mock_requests_get(valid_url, return_value):
- """Used to replace `requests.get` with a function that returns the given value for the given valid URL and raises
- otherwise."""
-
- def inner(url):
- if url == valid_url:
- return _MockResponse(return_value)
- raise RuntimeError
-
- return inner
-
-
-def as_tar_bytes(file_name, content):
- """Utility to encode the given string as a gzipped tar and return the bytes."""
- tar_fileobj = io.BytesIO()
- with tarfile.open(fileobj=tar_fileobj, mode="w|gz") as tar:
- content = content.encode("utf-8")
- tf = tarfile.TarInfo(file_name)
- tf.size = len(content)
- tar.addfile(tf, io.BytesIO(content))
- tar_fileobj.seek(0)
- return tar_fileobj.read()
-
-
-_plugin_with_internal_error = """
-from lightning.app.plugin.plugin import LightningPlugin
-
-class TestPlugin(LightningPlugin):
- def run(self):
- raise RuntimeError("Internal Error")
-
-plugin = TestPlugin()
-"""
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="the plugin server is only intended to run on linux.")
-@pytest.mark.parametrize(
- ("body", "message", "tar_file_name", "content"),
- [
- (
- _Run(
- plugin_entrypoint="test",
- source_code_url="this_url_does_not_exist",
- project_id="any",
- cloudspace_id="any",
- cluster_id="any",
- plugin_arguments={},
- source_app="any",
- keep_machines_after_stop=False,
- ),
- "Error downloading plugin source:",
- None,
- b"",
- ),
- (
- _Run(
- plugin_entrypoint="test",
- source_code_url="http://test.tar.gz",
- project_id="any",
- cloudspace_id="any",
- cluster_id="any",
- plugin_arguments={},
- source_app="any",
- keep_machines_after_stop=False,
- ),
- "Error extracting plugin source:",
- None,
- b"this is not a tar",
- ),
- (
- _Run(
- plugin_entrypoint="plugin.py",
- source_code_url="http://test.tar.gz",
- project_id="any",
- cloudspace_id="any",
- cluster_id="any",
- plugin_arguments={},
- source_app="any",
- keep_machines_after_stop=False,
- ),
- "Error loading plugin:",
- "plugin.py",
- "this is not a plugin",
- ),
- (
- _Run(
- plugin_entrypoint="plugin.py",
- source_code_url="http://test.tar.gz",
- project_id="any",
- cloudspace_id="any",
- cluster_id="any",
- plugin_arguments={},
- source_app="any",
- keep_machines_after_stop=False,
- ),
- "Error running plugin:",
- "plugin.py",
- _plugin_with_internal_error,
- ),
- ],
-)
-@mock.patch("lightning.app.plugin.plugin.requests")
-def test_run_errors(mock_requests, mock_plugin_server, body, message, tar_file_name, content):
- if tar_file_name is not None:
- content = as_tar_bytes(tar_file_name, content)
-
- mock_requests.get.side_effect = mock_requests_get("http://test.tar.gz", content)
-
- response = mock_plugin_server.post("/v1/runs", json=body.dict(exclude_none=True))
-
- assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
- assert message in response.text
-
-
-_plugin_with_job_run = """
-from lightning.app.plugin.plugin import LightningPlugin
-
-class TestPlugin(LightningPlugin):
- def run(self, name, entrypoint):
- return self.run_job(name, entrypoint)
-
-plugin = TestPlugin()
-"""
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="the plugin server is only intended to run on linux.")
-@mock.patch("lightning.app.runners.backends.cloud.CloudBackend")
-@mock.patch("lightning.app.runners.cloud.CloudRuntime")
-@mock.patch("lightning.app.plugin.plugin.requests")
-def test_run_job(mock_requests, mock_cloud_runtime, mock_cloud_backend, mock_plugin_server):
- """Tests that running a job from a plugin calls the correct `CloudRuntime` methods with the correct arguments."""
- content = as_tar_bytes("plugin.py", _plugin_with_job_run)
- mock_requests.get.side_effect = mock_requests_get("http://test.tar.gz", content)
-
- body = _Run(
- plugin_entrypoint="plugin.py",
- source_code_url="http://test.tar.gz",
- project_id="test_project_id",
- cloudspace_id="test_cloudspace_id",
- cluster_id="test_cluster_id",
- plugin_arguments={"name": "test_name", "entrypoint": "test_entrypoint"},
- source_app="test_source_app",
- keep_machines_after_stop=True,
- )
-
- mock_app = mock.MagicMock()
- mock_cloud_runtime.load_app_from_file.return_value = mock_app
- mock_cloud_runtime.return_value.cloudspace_dispatch.return_value = Externalv1LightningappInstance(
- id="created_app_id"
- )
-
- response = mock_plugin_server.post("/v1/runs", json=body.dict(exclude_none=True))
-
- assert response.status_code == status.HTTP_200_OK, response.json()
- assert json.loads(response.text)["id"] == "created_app_id"
-
- mock_cloud_runtime.load_app_from_file.assert_called_once()
- assert "test_entrypoint" in mock_cloud_runtime.load_app_from_file.call_args[0][0]
-
- mock_cloud_runtime.assert_called_once_with(
- app=mock_app,
- entrypoint=Path("test_entrypoint"),
- start_server=True,
- env_vars={},
- secrets={},
- run_app_comment_commands=True,
- backend=mock.ANY,
- )
-
- mock_cloud_runtime().cloudspace_dispatch.assert_called_once_with(
- project_id=body.project_id,
- cloudspace_id=body.cloudspace_id,
- name="test_name",
- cluster_id=body.cluster_id,
- source_app=body.source_app,
- keep_machines_after_stop=body.keep_machines_after_stop,
- )
-
-
-def test_healthz(mock_plugin_server):
- """Smoke test for the healthz endpoint."""
- assert mock_plugin_server.get("/healthz").status_code == 200
diff --git a/tests/tests_app/runners/__init__.py b/tests/tests_app/runners/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/runners/backends/__init__.py b/tests/tests_app/runners/backends/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/runners/backends/test_mp_process.py b/tests/tests_app/runners/backends/test_mp_process.py
deleted file mode 100644
index ae22a9133124f..0000000000000
--- a/tests/tests_app/runners/backends/test_mp_process.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from unittest import mock
-from unittest.mock import MagicMock, Mock
-
-from lightning.app import LightningApp, LightningWork
-from lightning.app.runners.backends import MultiProcessingBackend
-
-
-@mock.patch("lightning.app.core.app.AppStatus")
-@mock.patch("lightning.app.runners.backends.mp_process.multiprocessing")
-def test_backend_create_work_with_set_start_method(multiprocessing_mock, *_):
- backend = MultiProcessingBackend(entrypoint_file="fake.py")
- work = Mock(spec=LightningWork)
- work._start_method = "test_start_method"
-
- app = LightningApp(work)
- app.caller_queues = MagicMock()
- app.delta_queue = MagicMock()
- app.readiness_queue = MagicMock()
- app.error_queue = MagicMock()
- app.request_queues = MagicMock()
- app.response_queues = MagicMock()
- app.copy_request_queues = MagicMock()
- app.copy_response_queues = MagicMock()
- app.flow_to_work_delta_queues = MagicMock()
-
- backend.create_work(app=app, work=work)
- multiprocessing_mock.get_context.assert_called_with("test_start_method")
- multiprocessing_mock.get_context().Process().start.assert_called_once()
diff --git a/tests/tests_app/runners/test_cloud.py b/tests/tests_app/runners/test_cloud.py
deleted file mode 100644
index b33f906f96d8d..0000000000000
--- a/tests/tests_app/runners/test_cloud.py
+++ /dev/null
@@ -1,2025 +0,0 @@
-import logging
-import os
-import pathlib
-import sys
-from copy import copy
-from pathlib import Path
-from unittest import mock
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app import BuildConfig, LightningApp, LightningFlow, LightningWork
-from lightning.app.runners import CloudRuntime, backends, cloud
-from lightning.app.source_code.copytree import _copytree, _parse_lightningignore
-from lightning.app.source_code.local import LocalSourceCodeDir
-from lightning.app.storage import Drive, Mount
-from lightning.app.testing.helpers import EmptyWork
-from lightning.app.utilities.cloud import _get_project
-from lightning.app.utilities.dependency_caching import get_hash
-from lightning.app.utilities.packaging.cloud_compute import CloudCompute
-from lightning_cloud.openapi import (
- CloudspaceIdRunsBody,
- Externalv1Cluster,
- Externalv1LightningappInstance,
- Gridv1ImageSpec,
- IdGetBody,
- ProjectIdProjectclustersbindingsBody,
- V1BuildSpec,
- V1CloudSpace,
- V1CloudSpaceInstanceConfig,
- V1ClusterSpec,
- V1ClusterType,
- V1DataConnectionMount,
- V1DependencyFileInfo,
- V1Drive,
- V1DriveSpec,
- V1DriveStatus,
- V1DriveType,
- V1EnvVar,
- V1GetUserResponse,
- V1LightningappInstanceSpec,
- V1LightningappInstanceState,
- V1LightningappInstanceStatus,
- V1LightningAuth,
- V1LightningBasicAuth,
- V1LightningRun,
- V1LightningworkDrives,
- V1LightningworkSpec,
- V1ListCloudSpacesResponse,
- V1ListClustersResponse,
- V1ListLightningappInstancesResponse,
- V1ListMembershipsResponse,
- V1ListProjectClusterBindingsResponse,
- V1Membership,
- V1Metadata,
- V1NetworkConfig,
- V1PackageManager,
- V1ProjectClusterBinding,
- V1PythonDependencyInfo,
- V1QueueServerType,
- V1SourceType,
- V1UserFeatures,
- V1UserRequestedComputeConfig,
- V1UserRequestedFlowComputeConfig,
- V1Work,
-)
-
-
-class MyWork(LightningWork):
- def run(self):
- print("my run")
-
-
-class WorkWithSingleDrive(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.drive = None
-
- def run(self):
- pass
-
-
-class WorkWithTwoDrives(LightningWork):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.lit_drive_1 = None
- self.lit_drive_2 = None
-
- def run(self):
- pass
-
-
-def get_cloud_runtime_request_body(**kwargs) -> "CloudspaceIdRunsBody":
- default_request_body = {
- "app_entrypoint_file": mock.ANY,
- "enable_app_server": True,
- "is_headless": True,
- "should_mount_cloudspace_content": False,
- "flow_servers": [],
- "image_spec": None,
- "works": [],
- "local_source": True,
- "dependency_cache_key": mock.ANY,
- "user_requested_flow_compute_config": V1UserRequestedFlowComputeConfig(
- name="flow-lite",
- spot=False,
- shm_size=0,
- ),
- }
-
- if kwargs.get("user_requested_flow_compute_config") is not None:
- default_request_body["user_requested_flow_compute_config"] = kwargs["user_requested_flow_compute_config"]
-
- return CloudspaceIdRunsBody(**default_request_body)
-
-
-@pytest.fixture()
-def cloud_backend(monkeypatch):
- cloud_backend = mock.MagicMock()
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- return cloud_backend
-
-
-@pytest.fixture()
-def project_id():
- return "test-project-id"
-
-
-DEFAULT_CLUSTER = "litng-ai-03"
-
-
-class TestAppCreationClient:
- """Testing the calls made using GridRestClient to create the app."""
-
- def test_run_on_deleted_cluster(self, cloud_backend):
- app_name = "test-app"
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="Default Project", project_id=project_id)]
- )
-
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([
- Externalv1Cluster(id=DEFAULT_CLUSTER)
- ])
- cloud_backend.client = mock_client
-
- app = mock.MagicMock()
- app.flows = []
- app.frontend = {}
-
- existing_instance = MagicMock()
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- existing_instance.spec.cluster_id = DEFAULT_CLUSTER
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[existing_instance])
- )
-
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=Path("entrypoint.py"))
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- with pytest.raises(ValueError, match="that cluster doesn't exist"):
- cloud_runtime.dispatch(name=app_name, cluster_id="unknown-cluster")
-
- @pytest.mark.parametrize(
- ("old_cluster", "new_cluster"),
- [
- ("test", "other"),
- ("test", "test"),
- (None, None),
- (None, "litng-ai-03"),
- ("litng-ai-03", None),
- ],
- )
- def test_new_instance_on_different_cluster(self, tmpdir, cloud_backend, project_id, old_cluster, new_cluster):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- app_name = "test-app"
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="Default Project", project_id=project_id)]
- )
- mock_client.lightningapp_v2_service_create_lightningapp_release.return_value = V1LightningRun(
- cluster_id=new_cluster
- )
-
- # Note:
- # backend converts "None" cluster to "litng-ai-03"
- # dispatch should receive None, but API calls should return "litng-ai-03"
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([
- Externalv1Cluster(id=old_cluster or DEFAULT_CLUSTER),
- Externalv1Cluster(id=new_cluster or DEFAULT_CLUSTER),
- ])
-
- mock_client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[
- V1ProjectClusterBinding(cluster_id=old_cluster or DEFAULT_CLUSTER),
- V1ProjectClusterBinding(cluster_id=new_cluster or DEFAULT_CLUSTER),
- ]
- )
-
- # Mock all clusters as global clusters
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
-
- cloud_backend.client = mock_client
-
- app = mock.MagicMock()
- app.flows = []
- app.frontend = {}
-
- existing_app = MagicMock()
- existing_app.name = app_name
- existing_app.id = "test-id"
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=[existing_app]
- )
-
- existing_instance = MagicMock()
- existing_instance.name = app_name
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- existing_instance.spec.cluster_id = old_cluster or DEFAULT_CLUSTER
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[existing_instance])
- )
-
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- # This is the main assertion:
- # we have an existing instance on `cluster-001`
- # but we want to run this app on `cluster-002`
- cloud_runtime.dispatch(name=app_name, cluster_id=new_cluster)
-
- if new_cluster != old_cluster and None not in (old_cluster, new_cluster):
- # If we switched cluster, check that a new name was used which starts with the old name
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once()
- args = mock_client.cloud_space_service_create_lightning_run_instance.call_args
- assert args[1]["body"].name != app_name
- assert args[1]["body"].name.startswith(app_name)
- assert args[1]["body"].cluster_id == new_cluster
-
- def test_running_deleted_app(self, tmpdir, cloud_backend, project_id):
- """Deleted apps show up in list apps but not in list instances.
-
- This tests that we don't try to reacreate a previously deleted app.
-
- """
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- app_name = "test-app"
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="Default Project", project_id=project_id)]
- )
- mock_client.lightningapp_v2_service_create_lightningapp_release.return_value = V1LightningRun(
- cluster_id=DEFAULT_CLUSTER
- )
-
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([
- Externalv1Cluster(id=DEFAULT_CLUSTER)
- ])
-
- mock_client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[V1ProjectClusterBinding(cluster_id=DEFAULT_CLUSTER)]
- )
-
- # Mock all clusters as global clusters
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
-
- cloud_backend.client = mock_client
-
- app = mock.MagicMock()
- app.flows = []
- app.frontend = {}
-
- existing_app = MagicMock()
- existing_app.name = app_name
- existing_app.id = "test-id"
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=[existing_app]
- )
-
- # Simulate the app as deleted so no instance to return
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
-
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- cloud_runtime.dispatch(name=app_name)
-
- # Check that a new name was used which starts with and does not equal the old name
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once()
- args = mock_client.cloud_space_service_create_lightning_run_instance.call_args
- assert args[1]["body"].name != app_name
- assert args[1]["body"].name.startswith(app_name)
-
- @pytest.mark.parametrize("flow_cloud_compute", [None, CloudCompute(name="t2.medium")])
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- def test_run_with_default_flow_compute_config(self, tmpdir, monkeypatch, flow_cloud_compute):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.lightningapp_v2_service_create_lightningapp_release.return_value = V1LightningRun(cluster_id="test")
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
-
- dummy_flow = mock.MagicMock()
- monkeypatch.setattr(dummy_flow, "run", lambda *args, **kwargs: None)
- if flow_cloud_compute is None:
- app = LightningApp(dummy_flow)
- else:
- app = LightningApp(dummy_flow, flow_cloud_compute=flow_cloud_compute)
-
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- cloud_runtime.dispatch()
-
- user_requested_flow_compute_config = None
- if flow_cloud_compute is not None:
- user_requested_flow_compute_config = V1UserRequestedFlowComputeConfig(
- name=flow_cloud_compute.name, spot=False, shm_size=0
- )
-
- body = get_cloud_runtime_request_body(user_requested_flow_compute_config=user_requested_flow_compute_config)
- cloud_runtime.backend.client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=body
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- def test_run_on_byoc_cluster(self, tmpdir, monkeypatch):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="Default Project", project_id="default-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(cluster_id="test1234")
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([
- Externalv1Cluster(id="test1234")
- ])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
- app.flows = []
- app.frontend = {}
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- cloud_runtime.dispatch(cluster_id="test1234")
- body = CloudspaceIdRunsBody(
- cluster_id="test1234",
- app_entrypoint_file=mock.ANY,
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- image_spec=None,
- works=[],
- local_source=True,
- dependency_cache_key=mock.ANY,
- user_requested_flow_compute_config=mock.ANY,
- )
- cloud_runtime.backend.client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="default-project-id", cloudspace_id=mock.ANY, body=body
- )
- cloud_runtime.backend.client.projects_service_create_project_cluster_binding.assert_called_once_with(
- project_id="default-project-id",
- body=ProjectIdProjectclustersbindingsBody(cluster_id="test1234"),
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- def test_requirements_file(self, tmpdir, monkeypatch):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun()
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
- app.flows = []
- app.frontend = {}
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- # Without requirements file
- cloud_runtime.dispatch()
- body = CloudspaceIdRunsBody(
- app_entrypoint_file=mock.ANY,
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- image_spec=None,
- works=[],
- local_source=True,
- dependency_cache_key=mock.ANY,
- user_requested_flow_compute_config=mock.ANY,
- )
- cloud_runtime.backend.client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=body
- )
-
- # with requirements file
- requirements = Path(tmpdir) / "requirements.txt"
- requirements.touch()
-
- cloud_runtime.dispatch(no_cache=True)
- body.image_spec = Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(package_manager=V1PackageManager.PIP, path="requirements.txt")
- )
- cloud_runtime.backend.client.cloud_space_service_create_lightning_run.assert_called_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=body
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- def test_basic_auth_enabled(self, tmpdir, monkeypatch):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun()
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
- app.flows = []
- app.frontend = {}
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
- # Set cloud_runtime.enable_basic_auth to be not empty:
- cloud_runtime.enable_basic_auth = "username:password"
-
- cloud_runtime.dispatch()
- mock_client = cloud_runtime.backend.client
-
- body = CloudspaceIdRunsBody(
- app_entrypoint_file=mock.ANY,
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- image_spec=None,
- works=[],
- local_source=True,
- dependency_cache_key=mock.ANY,
- user_requested_flow_compute_config=mock.ANY,
- )
-
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=body
- )
-
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id",
- cloudspace_id=mock.ANY,
- id=mock.ANY,
- body=IdGetBody(
- desired_state=mock.ANY,
- name=mock.ANY,
- env=mock.ANY,
- queue_server_type=mock.ANY,
- auth=V1LightningAuth(basic=V1LightningBasicAuth(username="username", password="password")),
- ),
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- def test_no_cache(self, tmpdir, monkeypatch):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
- requirements = Path(tmpdir) / "requirements.txt"
- requirements.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(cluster_id="test")
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- monkeypatch.setattr(cloud, "get_hash", lambda *args, **kwargs: "dummy-hash")
- app = mock.MagicMock()
- app.flows = []
- app.frontend = {}
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- # testing with no-cache False
- cloud_runtime.dispatch(no_cache=False)
- _, _, kwargs = cloud_runtime.backend.client.cloud_space_service_create_lightning_run.mock_calls[0]
- body = kwargs["body"]
- assert body.dependency_cache_key == "dummy-hash"
-
- # testing with no-cache True
- mock_client.reset_mock()
- cloud_runtime.dispatch(no_cache=True)
- _, _, kwargs = cloud_runtime.backend.client.cloud_space_service_create_lightning_run.mock_calls[0]
- body = kwargs["body"]
- assert body.dependency_cache_key is None
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- @pytest.mark.parametrize(
- ("lightningapps", "start_with_flow"),
- [([], False), ([MagicMock()], False), ([MagicMock()], True)],
- )
- def test_call_with_work_app(self, lightningapps, start_with_flow, monkeypatch, tmpdir):
- source_code_root_dir = Path(tmpdir / "src").absolute()
- source_code_root_dir.mkdir()
- Path(source_code_root_dir / ".lightning").write_text("name: myapp")
- requirements_file = Path(source_code_root_dir / "requirements.txt")
- Path(requirements_file).touch()
- (source_code_root_dir / "entrypoint.py").touch()
-
- mock_client = mock.MagicMock()
- if lightningapps:
- lightningapps[0].name = "myapp"
- lightningapps[0].status.phase = V1LightningappInstanceState.STOPPED
- lightningapps[0].spec.cluster_id = "test"
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=lightningapps
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=lightningapps)
- )
- mock_client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[V1ProjectClusterBinding(cluster_id="test")]
- )
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = V1LightningRun()
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = MagicMock()
- existing_instance = MagicMock()
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- mock_client.lightningapp_service_get_lightningapp = MagicMock(return_value=existing_instance)
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
-
- work = MyWork(start_with_flow=start_with_flow, cloud_compute=CloudCompute("custom"))
- work._name = "test-work"
- work._cloud_build_config.build_commands = lambda: ["echo 'start'"]
- work._cloud_build_config.requirements = ["torch==1.0.0", "numpy==1.0.0"]
- work._cloud_build_config.image = "random_base_public_image"
- work._cloud_compute.disk_size = 0
- work._port = 8080
-
- app.works = [work]
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=(source_code_root_dir / "entrypoint.py"))
- monkeypatch.setattr(
- "lightning.app.runners.cloud._get_project",
- lambda _, project_id: V1Membership(name="test-project", project_id="test-project-id"),
- )
- cloud_runtime.dispatch()
-
- if lightningapps:
- expected_body = CloudspaceIdRunsBody(
- description=None,
- local_source=True,
- app_entrypoint_file="entrypoint.py",
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- dependency_cache_key=get_hash(requirements_file),
- user_requested_flow_compute_config=mock.ANY,
- cluster_id="test",
- image_spec=Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(
- package_manager=V1PackageManager.PIP, path="requirements.txt"
- )
- ),
- )
-
- if start_with_flow:
- expected_body.works = [
- V1Work(
- name="test-work",
- display_name="",
- spec=V1LightningworkSpec(
- build_spec=V1BuildSpec(
- commands=["echo 'start'"],
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages="torch==1.0.0\nnumpy==1.0.0"
- ),
- image="random_base_public_image",
- ),
- drives=[],
- user_requested_compute_config=V1UserRequestedComputeConfig(
- name="custom",
- count=1,
- disk_size=0,
- shm_size=0,
- spot=False,
- ),
- network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
- data_connection_mounts=[],
- ),
- )
- ]
- else:
- expected_body.works = []
-
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
-
- # running dispatch with disabled dependency cache
- mock_client.reset_mock()
- monkeypatch.setattr(cloud, "DISABLE_DEPENDENCY_CACHE", True)
- expected_body.dependency_cache_key = None
- cloud_runtime.dispatch()
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
- else:
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, id=mock.ANY, body=mock.ANY
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- @pytest.mark.parametrize("lightningapps", [[], [MagicMock()]])
- def test_call_with_queue_server_type_specified(self, tmpdir, lightningapps, monkeypatch):
- entrypoint = Path(tmpdir) / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun()
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.flows = []
- app.frontend = {}
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- cloud_runtime._check_uploaded_folder = mock.MagicMock()
-
- cloud_runtime.dispatch()
-
- # calling with no env variable set
- body = IdGetBody(
- desired_state=V1LightningappInstanceState.STOPPED,
- env=[],
- name=mock.ANY,
- queue_server_type=V1QueueServerType.UNSPECIFIED,
- )
- client = cloud_runtime.backend.client
- client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, id=mock.ANY, body=body
- )
-
- # calling with env variable set to http
- monkeypatch.setitem(os.environ, "LIGHTNING_CLOUD_QUEUE_TYPE", "http")
- cloud_runtime.backend.client.reset_mock()
- cloud_runtime.dispatch()
- body = IdGetBody(
- desired_state=V1LightningappInstanceState.STOPPED,
- env=mock.ANY,
- name=mock.ANY,
- queue_server_type=V1QueueServerType.HTTP,
- )
- client = cloud_runtime.backend.client
- client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, id=mock.ANY, body=body
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- @pytest.mark.parametrize("lightningapps", [[], [MagicMock()]])
- def test_call_with_work_app_and_attached_drives(self, lightningapps, monkeypatch, tmpdir):
- source_code_root_dir = Path(tmpdir / "src").absolute()
- source_code_root_dir.mkdir()
- Path(source_code_root_dir / ".lightning").write_text("name: myapp")
- requirements_file = Path(source_code_root_dir / "requirements.txt")
- Path(requirements_file).touch()
- (source_code_root_dir / "entrypoint.py").touch()
-
- mock_client = mock.MagicMock()
- if lightningapps:
- lightningapps[0].name = "myapp"
- lightningapps[0].status.phase = V1LightningappInstanceState.STOPPED
- lightningapps[0].spec.cluster_id = "test"
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=lightningapps
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=lightningapps)
- )
- mock_client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[V1ProjectClusterBinding(cluster_id="test")]
- )
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = V1LightningRun()
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- lit_app_instance = MagicMock()
- mock_client.cloud_space_service_create_lightning_run_instance = MagicMock(return_value=lit_app_instance)
- existing_instance = MagicMock()
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- mock_client.lightningapp_service_get_lightningapp = MagicMock(return_value=existing_instance)
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
-
- mocked_drive = MagicMock(spec=Drive)
- setattr(mocked_drive, "id", "foobar")
- setattr(mocked_drive, "protocol", "lit://")
- setattr(mocked_drive, "component_name", "test-work")
- setattr(mocked_drive, "allow_duplicates", False)
- setattr(mocked_drive, "root_folder", tmpdir)
- # deepcopy on a MagicMock instance will return an empty magicmock instance. To
- # overcome this we set the __deepcopy__ method `return_value` to equal what
- # should be the results of the deepcopy operation (an instance of the original class)
- mocked_drive.__deepcopy__.return_value = copy(mocked_drive)
-
- work = WorkWithSingleDrive(cloud_compute=CloudCompute("custom"))
- monkeypatch.setattr(work, "drive", mocked_drive)
- monkeypatch.setattr(work, "_state", {"_port", "drive"})
- monkeypatch.setattr(work, "_name", "test-work")
- monkeypatch.setattr(work._cloud_build_config, "build_commands", lambda: ["echo 'start'"])
- monkeypatch.setattr(work._cloud_build_config, "requirements", ["torch==1.0.0", "numpy==1.0.0"])
- monkeypatch.setattr(work._cloud_build_config, "image", "random_base_public_image")
- monkeypatch.setattr(work._cloud_compute, "disk_size", 0)
- monkeypatch.setattr(work, "_port", 8080)
-
- app.works = [work]
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=(source_code_root_dir / "entrypoint.py"))
- monkeypatch.setattr(
- "lightning.app.runners.cloud._get_project",
- lambda _, project_id: V1Membership(name="test-project", project_id="test-project-id"),
- )
- cloud_runtime.dispatch()
-
- if lightningapps:
- expected_body = CloudspaceIdRunsBody(
- description=None,
- local_source=True,
- app_entrypoint_file="entrypoint.py",
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- dependency_cache_key=get_hash(requirements_file),
- user_requested_flow_compute_config=mock.ANY,
- cluster_id="test",
- image_spec=Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(
- package_manager=V1PackageManager.PIP, path="requirements.txt"
- )
- ),
- works=[
- V1Work(
- name="test-work",
- display_name="",
- spec=V1LightningworkSpec(
- build_spec=V1BuildSpec(
- commands=["echo 'start'"],
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages="torch==1.0.0\nnumpy==1.0.0"
- ),
- image="random_base_public_image",
- ),
- drives=[
- V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name="test-work.drive",
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.NO_MOUNT_S3,
- source_type=V1SourceType.S3,
- source="lit://foobar",
- ),
- status=V1DriveStatus(),
- ),
- ),
- ],
- user_requested_compute_config=V1UserRequestedComputeConfig(
- name="custom", count=1, disk_size=0, shm_size=0, spot=False
- ),
- network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
- data_connection_mounts=[],
- ),
- )
- ],
- )
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
-
- # running dispatch with disabled dependency cache
- mock_client.reset_mock()
- monkeypatch.setattr(cloud, "DISABLE_DEPENDENCY_CACHE", True)
- expected_body.dependency_cache_key = None
- cloud_runtime.dispatch()
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
- else:
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, id=mock.ANY, body=mock.ANY
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- @mock.patch("lightning.app.core.constants.ENABLE_APP_COMMENT_COMMAND_EXECUTION", True)
- @pytest.mark.parametrize("lightningapps", [[], [MagicMock()]])
- def test_call_with_work_app_and_app_comment_command_execution_set(self, lightningapps, monkeypatch, tmpdir):
- source_code_root_dir = Path(tmpdir / "src").absolute()
- source_code_root_dir.mkdir()
- Path(source_code_root_dir / ".lightning").write_text("name: myapp")
- requirements_file = Path(source_code_root_dir / "requirements.txt")
- Path(requirements_file).touch()
- (source_code_root_dir / "entrypoint.py").touch()
-
- mock_client = mock.MagicMock()
- if lightningapps:
- lightningapps[0].name = "myapp"
- lightningapps[0].status.phase = V1LightningappInstanceState.STOPPED
- lightningapps[0].spec.cluster_id = "test"
- mock_client.projects_service_list_project_cluster_bindings.return_value = (
- V1ListProjectClusterBindingsResponse(clusters=[V1ProjectClusterBinding(cluster_id="test")])
- )
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=lightningapps
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=lightningapps)
- )
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = V1LightningRun()
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- lit_app_instance = MagicMock()
- mock_client.cloud_space_service_create_lightning_run_instance = MagicMock(return_value=lit_app_instance)
- existing_instance = MagicMock()
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- mock_client.lightningapp_service_get_lightningapp = MagicMock(return_value=existing_instance)
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
-
- work = MyWork(cloud_compute=CloudCompute("custom"))
- work._state = {"_port"}
- work._name = "test-work"
- work._cloud_build_config.build_commands = lambda: ["echo 'start'"]
- work._cloud_build_config.requirements = ["torch==1.0.0", "numpy==1.0.0"]
- work._cloud_build_config.image = "random_base_public_image"
- work._cloud_compute.disk_size = 0
- work._port = 8080
-
- app.works = [work]
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=(source_code_root_dir / "entrypoint.py"))
- monkeypatch.setattr(
- "lightning.app.runners.cloud._get_project",
- lambda _, project_id: V1Membership(name="test-project", project_id="test-project-id"),
- )
- cloud_runtime.run_app_comment_commands = True
- cloud_runtime.dispatch()
-
- if lightningapps:
- expected_body = CloudspaceIdRunsBody(
- description=None,
- local_source=True,
- app_entrypoint_file="entrypoint.py",
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- dependency_cache_key=get_hash(requirements_file),
- user_requested_flow_compute_config=mock.ANY,
- cluster_id="test",
- image_spec=Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(
- package_manager=V1PackageManager.PIP, path="requirements.txt"
- )
- ),
- works=[
- V1Work(
- name="test-work",
- display_name="",
- spec=V1LightningworkSpec(
- build_spec=V1BuildSpec(
- commands=["echo 'start'"],
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages="torch==1.0.0\nnumpy==1.0.0"
- ),
- image="random_base_public_image",
- ),
- drives=[],
- user_requested_compute_config=V1UserRequestedComputeConfig(
- name="custom", count=1, disk_size=0, shm_size=0, spot=mock.ANY
- ),
- network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
- cluster_id=mock.ANY,
- data_connection_mounts=[],
- ),
- )
- ],
- )
-
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
-
- # running dispatch with disabled dependency cache
- mock_client.reset_mock()
- monkeypatch.setattr(cloud, "DISABLE_DEPENDENCY_CACHE", True)
- expected_body.dependency_cache_key = None
- cloud_runtime.dispatch()
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
- else:
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id",
- cloudspace_id=mock.ANY,
- id=mock.ANY,
- body=IdGetBody(
- desired_state=V1LightningappInstanceState.STOPPED,
- name=mock.ANY,
- env=[V1EnvVar(name="ENABLE_APP_COMMENT_COMMAND_EXECUTION", value="1")],
- queue_server_type=mock.ANY,
- ),
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- @pytest.mark.parametrize("lightningapps", [[], [MagicMock()]])
- def test_call_with_work_app_and_multiple_attached_drives(self, lightningapps, monkeypatch, tmpdir):
- source_code_root_dir = Path(tmpdir / "src").absolute()
- source_code_root_dir.mkdir()
- Path(source_code_root_dir / ".lightning").write_text("name: myapp")
- requirements_file = Path(source_code_root_dir / "requirements.txt")
- Path(requirements_file).touch()
- (source_code_root_dir / "entrypoint.py").touch()
-
- mock_client = mock.MagicMock()
- if lightningapps:
- lightningapps[0].name = "myapp"
- lightningapps[0].status.phase = V1LightningappInstanceState.STOPPED
- lightningapps[0].spec.cluster_id = "test"
- mock_client.projects_service_list_project_cluster_bindings.return_value = (
- V1ListProjectClusterBindingsResponse(
- clusters=[
- V1ProjectClusterBinding(cluster_id="test"),
- ]
- )
- )
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=lightningapps
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=lightningapps)
- )
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = V1LightningRun(cluster_id="test")
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- lit_app_instance = MagicMock()
- mock_client.cloud_space_service_create_lightning_run_instance = MagicMock(return_value=lit_app_instance)
- existing_instance = MagicMock()
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- mock_client.lightningapp_service_get_lightningapp = MagicMock(return_value=existing_instance)
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
-
- mocked_lit_drive = MagicMock(spec=Drive)
- setattr(mocked_lit_drive, "id", "foobar")
- setattr(mocked_lit_drive, "protocol", "lit://")
- setattr(mocked_lit_drive, "component_name", "test-work")
- setattr(mocked_lit_drive, "allow_duplicates", False)
- setattr(mocked_lit_drive, "root_folder", tmpdir)
- # deepcopy on a MagicMock instance will return an empty magicmock instance. To
- # overcome this we set the __deepcopy__ method `return_value` to equal what
- # should be the results of the deepcopy operation (an instance of the original class)
- mocked_lit_drive.__deepcopy__.return_value = copy(mocked_lit_drive)
-
- work = WorkWithTwoDrives(cloud_compute=CloudCompute("custom"))
- work.lit_drive_1 = mocked_lit_drive
- work.lit_drive_2 = mocked_lit_drive
- work._state = {"_port", "_name", "lit_drive_1", "lit_drive_2"}
- work._name = "test-work"
- work._cloud_build_config.build_commands = lambda: ["echo 'start'"]
- work._cloud_build_config.requirements = ["torch==1.0.0", "numpy==1.0.0"]
- work._cloud_build_config.image = "random_base_public_image"
- work._cloud_compute.disk_size = 0
- work._port = 8080
-
- app.works = [work]
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=(source_code_root_dir / "entrypoint.py"))
- monkeypatch.setattr(
- "lightning.app.runners.cloud._get_project",
- lambda _, project_id: V1Membership(name="test-project", project_id="test-project-id"),
- )
- cloud_runtime.dispatch()
-
- if lightningapps:
- lit_drive_1_spec = V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name="test-work.lit_drive_1",
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.NO_MOUNT_S3,
- source_type=V1SourceType.S3,
- source="lit://foobar",
- ),
- status=V1DriveStatus(),
- ),
- )
- lit_drive_2_spec = V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name="test-work.lit_drive_2",
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.NO_MOUNT_S3,
- source_type=V1SourceType.S3,
- source="lit://foobar",
- ),
- status=V1DriveStatus(),
- ),
- )
-
- # order of drives in the spec is non-deterministic, so there are two options
- # depending for the expected body value on which drive is ordered in the list first.
-
- expected_body_option_1 = CloudspaceIdRunsBody(
- description=None,
- local_source=True,
- app_entrypoint_file="entrypoint.py",
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- dependency_cache_key=get_hash(requirements_file),
- user_requested_flow_compute_config=mock.ANY,
- cluster_id="test",
- image_spec=Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(
- package_manager=V1PackageManager.PIP, path="requirements.txt"
- )
- ),
- works=[
- V1Work(
- name="test-work",
- display_name="",
- spec=V1LightningworkSpec(
- build_spec=V1BuildSpec(
- commands=["echo 'start'"],
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages="torch==1.0.0\nnumpy==1.0.0"
- ),
- image="random_base_public_image",
- ),
- drives=[lit_drive_2_spec, lit_drive_1_spec],
- user_requested_compute_config=V1UserRequestedComputeConfig(
- name="custom",
- count=1,
- disk_size=0,
- shm_size=0,
- spot=False,
- ),
- network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
- data_connection_mounts=[],
- ),
- )
- ],
- )
-
- expected_body_option_2 = CloudspaceIdRunsBody(
- description=None,
- local_source=True,
- app_entrypoint_file="entrypoint.py",
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- dependency_cache_key=get_hash(requirements_file),
- user_requested_flow_compute_config=mock.ANY,
- cluster_id="test",
- image_spec=Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(
- package_manager=V1PackageManager.PIP, path="requirements.txt"
- )
- ),
- works=[
- V1Work(
- name="test-work",
- display_name="",
- spec=V1LightningworkSpec(
- build_spec=V1BuildSpec(
- commands=["echo 'start'"],
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages="torch==1.0.0\nnumpy==1.0.0"
- ),
- image="random_base_public_image",
- ),
- drives=[lit_drive_1_spec, lit_drive_2_spec],
- user_requested_compute_config=V1UserRequestedComputeConfig(
- name="custom",
- count=1,
- disk_size=0,
- shm_size=0,
- spot=False,
- ),
- network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
- data_connection_mounts=[],
- ),
- )
- ],
- )
-
- # try both options for the expected body to avoid false
- # positive test failures depending on system randomness
-
- expected_body = expected_body_option_1
- try:
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
- except Exception:
- expected_body = expected_body_option_2
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
-
- # running dispatch with disabled dependency cache
- mock_client.reset_mock()
- monkeypatch.setattr(cloud, "DISABLE_DEPENDENCY_CACHE", True)
- expected_body.dependency_cache_key = None
- cloud_runtime.dispatch()
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
- else:
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, id=mock.ANY, body=mock.ANY
- )
-
- @mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
- @pytest.mark.parametrize("lightningapps", [[], [MagicMock()]])
- def test_call_with_work_app_and_attached_mount_and_drive(self, lightningapps, monkeypatch, tmpdir):
- source_code_root_dir = Path(tmpdir / "src").absolute()
- source_code_root_dir.mkdir()
- Path(source_code_root_dir / ".lightning").write_text("name: myapp")
- requirements_file = Path(source_code_root_dir / "requirements.txt")
- Path(requirements_file).touch()
- (source_code_root_dir / "entrypoint.py").touch()
-
- mock_client = mock.MagicMock()
- if lightningapps:
- lightningapps[0].name = "myapp"
- lightningapps[0].status.phase = V1LightningappInstanceState.STOPPED
- lightningapps[0].spec.cluster_id = "test"
- mock_client.projects_service_list_project_cluster_bindings.return_value = (
- V1ListProjectClusterBindingsResponse(clusters=[V1ProjectClusterBinding(cluster_id="test")])
- )
- mock_client.cluster_service_get_cluster.side_effect = lambda cluster_id: Externalv1Cluster(
- id=cluster_id, spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL)
- )
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=lightningapps
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=lightningapps)
- )
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = V1LightningRun(cluster_id="test")
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- lit_app_instance = MagicMock()
- mock_client.cloud_space_service_create_lightning_run_instance = MagicMock(return_value=lit_app_instance)
- existing_instance = MagicMock()
- existing_instance.status.phase = V1LightningappInstanceState.STOPPED
- existing_instance.spec.cluster_id = None
- mock_client.lightningapp_service_get_lightningapp = MagicMock(return_value=existing_instance)
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock.MagicMock())
- monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
- app = mock.MagicMock()
- app.is_headless = False
-
- mocked_drive = MagicMock(spec=Drive)
- setattr(mocked_drive, "id", "foobar")
- setattr(mocked_drive, "protocol", "lit://")
- setattr(mocked_drive, "component_name", "test-work")
- setattr(mocked_drive, "allow_duplicates", False)
- setattr(mocked_drive, "root_folder", tmpdir)
- # deepcopy on a MagicMock instance will return an empty magicmock instance. To
- # overcome this we set the __deepcopy__ method `return_value` to equal what
- # should be the results of the deepcopy operation (an instance of the original class)
- mocked_drive.__deepcopy__.return_value = copy(mocked_drive)
-
- mocked_mount = MagicMock(spec=Mount)
- setattr(mocked_mount, "source", "s3://foo/")
- setattr(mocked_mount, "mount_path", "/content/foo")
- setattr(mocked_mount, "protocol", "s3://")
-
- work = WorkWithSingleDrive(cloud_compute=CloudCompute("custom"))
- monkeypatch.setattr(work, "drive", mocked_drive)
- monkeypatch.setattr(work, "_state", {"_port", "drive"})
- monkeypatch.setattr(work, "_name", "test-work")
- monkeypatch.setattr(work._cloud_build_config, "build_commands", lambda: ["echo 'start'"])
- monkeypatch.setattr(work._cloud_build_config, "requirements", ["torch==1.0.0", "numpy==1.0.0"])
- monkeypatch.setattr(work._cloud_build_config, "image", "random_base_public_image")
- monkeypatch.setattr(work._cloud_compute, "disk_size", 0)
- monkeypatch.setattr(work._cloud_compute, "mounts", mocked_mount)
- monkeypatch.setattr(work, "_port", 8080)
-
- app.works = [work]
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=(source_code_root_dir / "entrypoint.py"))
- monkeypatch.setattr(
- "lightning.app.runners.cloud._get_project",
- lambda _, project_id: V1Membership(name="test-project", project_id="test-project-id"),
- )
- cloud_runtime.dispatch()
-
- if lightningapps:
- expected_body = CloudspaceIdRunsBody(
- description=None,
- local_source=True,
- app_entrypoint_file="entrypoint.py",
- enable_app_server=True,
- is_headless=False,
- should_mount_cloudspace_content=False,
- flow_servers=[],
- dependency_cache_key=get_hash(requirements_file),
- image_spec=Gridv1ImageSpec(
- dependency_file_info=V1DependencyFileInfo(
- package_manager=V1PackageManager.PIP, path="requirements.txt"
- )
- ),
- user_requested_flow_compute_config=mock.ANY,
- cluster_id="test",
- works=[
- V1Work(
- name="test-work",
- display_name="",
- spec=V1LightningworkSpec(
- build_spec=V1BuildSpec(
- commands=["echo 'start'"],
- python_dependencies=V1PythonDependencyInfo(
- package_manager=V1PackageManager.PIP, packages="torch==1.0.0\nnumpy==1.0.0"
- ),
- image="random_base_public_image",
- ),
- drives=[
- V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name="test-work.drive",
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.NO_MOUNT_S3,
- source_type=V1SourceType.S3,
- source="lit://foobar",
- ),
- status=V1DriveStatus(),
- ),
- ),
- V1LightningworkDrives(
- drive=V1Drive(
- metadata=V1Metadata(
- name="test-work",
- ),
- spec=V1DriveSpec(
- drive_type=V1DriveType.INDEXED_S3,
- source_type=V1SourceType.S3,
- source="s3://foo/",
- ),
- status=V1DriveStatus(),
- ),
- mount_location="/content/foo",
- ),
- ],
- user_requested_compute_config=V1UserRequestedComputeConfig(
- name="custom",
- count=1,
- disk_size=0,
- shm_size=0,
- spot=False,
- ),
- network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
- data_connection_mounts=[],
- ),
- )
- ],
- )
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
-
- # running dispatch with disabled dependency cache
- mock_client.reset_mock()
- monkeypatch.setattr(cloud, "DISABLE_DEPENDENCY_CACHE", True)
- expected_body.dependency_cache_key = None
- cloud_runtime.dispatch()
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, body=expected_body
- )
- else:
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="test-project-id", cloudspace_id=mock.ANY, id=mock.ANY, body=mock.ANY
- )
-
-
-class TestOpen:
- def test_open(self, monkeypatch):
- """Tests that the open method calls the expected API endpoints."""
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = V1GetUserResponse(
- username="tester", features=V1UserFeatures(code_tab=True)
- )
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
-
- mock_client.cloud_space_service_create_cloud_space.return_value = V1CloudSpace(id="cloudspace_id")
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(id="run_id")
-
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- mock_local_source = mock.MagicMock()
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock_local_source)
-
- cloud_runtime = cloud.CloudRuntime(entrypoint=Path("."))
-
- cloud_runtime.open("test_space")
-
- mock_client.cloud_space_service_create_cloud_space.assert_called_once_with(
- project_id="test-project-id", body=mock.ANY
- )
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id="cloudspace_id", body=mock.ANY
- )
-
- assert mock_client.cloud_space_service_create_cloud_space.call_args.kwargs["body"].name == "test_space"
-
- @pytest.mark.parametrize(
- ("path", "expected_root", "entries", "expected_filtered_entries"),
- [(".", ".", ["a.py", "b.ipynb"], ["a.py", "b.ipynb"]), ("a.py", ".", ["a.py", "b.ipynb"], ["a.py"])],
- )
- def test_open_repo(self, tmpdir, monkeypatch, path, expected_root, entries, expected_filtered_entries):
- """Tests that the local source code repo is set up with the correct path and ignore functions."""
- tmpdir = Path(tmpdir)
- for entry in entries:
- (tmpdir / entry).touch()
-
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = V1GetUserResponse(
- username="tester", features=V1UserFeatures(code_tab=True)
- )
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.lightningapp_v2_service_create_lightningapp_release.return_value = V1LightningRun(cluster_id="test")
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([Externalv1Cluster(id="test")])
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- mock_local_source = mock.MagicMock()
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock_local_source)
-
- cloud_runtime = cloud.CloudRuntime(entrypoint=tmpdir / path)
-
- cloud_runtime.open("test_space")
-
- mock_local_source.assert_called_once()
- repo_call = mock_local_source.call_args
-
- assert repo_call.kwargs["path"] == (tmpdir / expected_root).absolute()
- ignore_functions = repo_call.kwargs["ignore_functions"]
- if len(ignore_functions) > 0:
- filtered = ignore_functions[0]("", [tmpdir / entry for entry in entries])
- else:
- filtered = [tmpdir / entry for entry in entries]
-
- filtered = [entry.absolute() for entry in filtered]
- expected_filtered_entries = [(tmpdir / entry).absolute() for entry in expected_filtered_entries]
- assert filtered == expected_filtered_entries
-
- def test_reopen(self, monkeypatch, capsys):
- """Tests that the open method calls the expected API endpoints when the CloudSpace already exists."""
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = V1GetUserResponse(
- username="tester", features=V1UserFeatures(code_tab=True)
- )
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
-
- mock_client.cloud_space_service_list_cloud_spaces.return_value = V1ListCloudSpacesResponse(
- cloudspaces=[V1CloudSpace(id="cloudspace_id", name="test_space")]
- )
-
- running_instance = Externalv1LightningappInstance(
- id="instance_id",
- name="test_space",
- spec=V1LightningappInstanceSpec(cluster_id="test"),
- status=V1LightningappInstanceStatus(phase=V1LightningappInstanceState.RUNNING),
- )
-
- stopped_instance = Externalv1LightningappInstance(
- id="instance_id",
- name="test_space",
- spec=V1LightningappInstanceSpec(cluster_id="test"),
- status=V1LightningappInstanceStatus(phase=V1LightningappInstanceState.STOPPED),
- )
-
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[running_instance])
- )
- mock_client.lightningapp_instance_service_update_lightningapp_instance.return_value = running_instance
- mock_client.lightningapp_instance_service_get_lightningapp_instance.return_value = stopped_instance
-
- mock_client.cloud_space_service_create_cloud_space.return_value = V1CloudSpace(id="cloudspace_id")
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(id="run_id")
-
- cluster = Externalv1Cluster(id="test", spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL))
- mock_client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[V1ProjectClusterBinding(cluster_id="test")],
- )
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([cluster])
- mock_client.cluster_service_get_cluster.return_value = cluster
-
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- mock_local_source = mock.MagicMock()
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock_local_source)
-
- cloud_runtime = cloud.CloudRuntime(entrypoint=Path("."))
-
- cloud_runtime.open("test_space")
-
- mock_client.cloud_space_service_create_lightning_run_instance.assert_not_called()
- mock_client.cloud_space_service_create_cloud_space.assert_not_called()
-
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="test-project-id", cloudspace_id="cloudspace_id", body=mock.ANY
- )
-
- def test_not_enabled(self, monkeypatch, capsys):
- """Tests that an error is printed and the call exits if the feature isn't enabled for the user."""
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = V1GetUserResponse(
- username="tester",
- features=V1UserFeatures(code_tab=False),
- )
-
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
-
- cloud_runtime = cloud.CloudRuntime(entrypoint=Path("."))
-
- monkeypatch.setattr(cloud, "Path", Path)
-
- exited = False
- try:
- cloud_runtime.open("test_space")
- except SystemExit:
- # Expected behaviour
- exited = True
-
- out, _ = capsys.readouterr()
-
- assert exited
- assert "`lightning_app open` command has not been enabled" in out
-
-
-class TestCloudspaceDispatch:
- @mock.patch.object(pathlib.Path, "exists")
- @pytest.mark.parametrize(
- ("custom_env_sync_path_value", "cloudspace"),
- [
- (None, V1CloudSpace(id="test_id", code_config=V1CloudSpaceInstanceConfig())),
- (
- Path("/tmp/sys-customizations-sync"),
- V1CloudSpace(id="test_id", code_config=V1CloudSpaceInstanceConfig()),
- ),
- (
- Path("/tmp/sys-customizations-sync"),
- V1CloudSpace(
- id="test_id",
- code_config=V1CloudSpaceInstanceConfig(data_connection_mounts=[V1DataConnectionMount(id="test")]),
- ),
- ),
- ],
- )
- def test_cloudspace_dispatch(self, custom_env_sync_root, custom_env_sync_path_value, cloudspace, monkeypatch):
- """Tests that the cloudspace_dispatch method calls the expected API endpoints."""
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = V1GetUserResponse(
- username="tester",
- features=V1UserFeatures(),
- )
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="project", project_id="project_id")]
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(id="run_id")
- mock_client.cloud_space_service_create_lightning_run_instance.return_value = Externalv1LightningappInstance(
- id="instance_id"
- )
-
- cluster = Externalv1Cluster(id="test", spec=V1ClusterSpec(cluster_type=V1ClusterType.GLOBAL))
- mock_client.projects_service_list_project_cluster_bindings.return_value = V1ListProjectClusterBindingsResponse(
- clusters=[V1ProjectClusterBinding(cluster_id="cluster_id")],
- )
- mock_client.cluster_service_list_clusters.return_value = V1ListClustersResponse([cluster])
- mock_client.cluster_service_get_cluster.return_value = cluster
- mock_client.cloud_space_service_get_cloud_space.return_value = cloudspace
-
- cloud_backend = mock.MagicMock()
- cloud_backend.client = mock_client
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
- mock_repo = mock.MagicMock()
- mock_local_source = mock.MagicMock(return_value=mock_repo)
- monkeypatch.setattr(cloud, "LocalSourceCodeDir", mock_local_source)
- custom_env_sync_root.return_value = custom_env_sync_path_value
-
- mock_app = mock.MagicMock()
- mock_app.works = [mock.MagicMock()]
- cloud_runtime = cloud.CloudRuntime(app=mock_app, entrypoint=Path("."))
-
- app = cloud_runtime.cloudspace_dispatch("project_id", "cloudspace_id", "run_name", "cluster_id")
- assert app.id == "instance_id"
-
- mock_client.cloud_space_service_get_cloud_space.assert_called_once_with(
- project_id="project_id", id="cloudspace_id"
- )
-
- mock_client.cloud_space_service_create_lightning_run.assert_called_once_with(
- project_id="project_id", cloudspace_id="cloudspace_id", body=mock.ANY
- )
-
- assert (
- mock_client.cloud_space_service_create_lightning_run.call_args.kwargs["body"]
- .works[0]
- .spec.data_connection_mounts
- == cloudspace.code_config.data_connection_mounts
- )
-
- mock_client.cloud_space_service_create_lightning_run_instance.assert_called_once_with(
- project_id="project_id", cloudspace_id="cloudspace_id", id="run_id", body=mock.ANY
- )
-
- assert mock_client.cloud_space_service_create_lightning_run_instance.call_args.kwargs["body"].name == "run_name"
-
-
-@mock.patch("lightning.app.core.queues.QueuingSystem", MagicMock())
-@mock.patch("lightning.app.runners.backends.cloud.LightningClient", MagicMock())
-def test_get_project(monkeypatch):
- mock_client = mock.MagicMock()
- monkeypatch.setattr(cloud, "CloudBackend", mock.MagicMock(return_value=mock_client))
- app = mock.MagicMock(spec=LightningApp)
- cloud.CloudRuntime(app=app, entrypoint=Path("entrypoint.py"))
-
- # No valid projects
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(memberships=[])
-
- with pytest.raises(ValueError, match="No valid projects found"):
- _get_project(mock_client)
-
- # One valid project
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- ret = _get_project(mock_client)
- assert ret.project_id == "test-project-id"
-
- # Multiple valid projects
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[
- V1Membership(name="test-project1", project_id="test-project-id1"),
- V1Membership(name="test-project2", project_id="test-project-id2"),
- ]
- )
- ret = _get_project(mock_client)
- assert ret.project_id == "test-project-id1"
-
-
-def write_file_of_size(path, size):
- os.makedirs(os.path.dirname(path), exist_ok=True)
- with open(path, "wb") as f:
- f.seek(size)
- f.write(b"\0")
-
-
-@mock.patch("lightning.app.core.queues.QueuingSystem", MagicMock())
-@mock.patch("lightning.app.runners.backends.cloud.LightningClient", MagicMock())
-def test_check_uploaded_folder(monkeypatch, tmpdir, caplog):
- app = MagicMock()
- root = Path(tmpdir)
- repo = LocalSourceCodeDir(root)
- backend = cloud.CloudRuntime(app)
- with caplog.at_level(logging.WARN):
- backend._validate_repo(root, repo)
- assert caplog.messages == []
-
- # write some files to assert the message below.
- write_file_of_size(root / "a.png", 4 * 1000 * 1000)
- write_file_of_size(root / "b.txt", 5 * 1000 * 1000)
- write_file_of_size(root / "c.jpg", 6 * 1000 * 1000)
-
- repo._non_ignored_files = None # force reset
- with caplog.at_level(logging.WARN):
- backend._validate_repo(root, repo)
- assert f"Your application folder '{root.absolute()}' is more than 2 MB" in caplog.text
- assert "The total size is 15.0 MB" in caplog.text
- assert "4 files were uploaded" in caplog.text
- assert "files:\n6.0 MB: c.jpg\n5.0 MB: b.txt\n4.0 MB: a.png\nPerhaps" in caplog.text # tests the order
- assert "adding them to `.lightningignore`." in caplog.text
- assert "lightningingore` attribute in a Flow or Work" in caplog.text
-
-
-@mock.patch("lightning.app.core.queues.QueuingSystem", MagicMock())
-@mock.patch("lightning.app.runners.backends.cloud.LightningClient", MagicMock())
-def test_project_has_sufficient_credits():
- app = mock.MagicMock(spec=LightningApp)
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=Path("entrypoint.py"))
- credits_and_test_value = [
- [0.3, True],
- [1, False],
- [1.1, False],
- ]
- for balance, result in credits_and_test_value:
- project = V1Membership(name="test-project1", project_id="test-project-id1", balance=balance)
- assert cloud_runtime._resolve_needs_credits(project) is result
-
-
-@pytest.mark.parametrize(
- "lines",
- [
- [
- "import this_package_is_not_real",
- "from lightning.app import LightningApp",
- "from lightning.app.testing.helpers import EmptyWork",
- "app = LightningApp(EmptyWork())",
- ],
- [
- "from this_package_is_not_real import this_module_is_not_real",
- "from lightning.app import LightningApp",
- "from lightning.app.testing.helpers import EmptyWork",
- "app = LightningApp(EmptyWork())",
- ],
- [
- "import this_package_is_not_real",
- "from this_package_is_not_real import this_module_is_not_real",
- "from lightning.app import LightningApp",
- "from lightning.app.testing.helpers import EmptyWork",
- "app = LightningApp(EmptyWork())",
- ],
- [
- "import this_package_is_not_real",
- "from lightning.app import LightningApp",
- "from lightning.app.core.flow import _RootFlow",
- "from lightning.app.testing.helpers import EmptyWork",
- "class MyFlow(_RootFlow):",
- " def configure_layout(self):",
- " return [{'name': 'test', 'content': this_package_is_not_real()}]",
- "app = LightningApp(MyFlow(EmptyWork()))",
- ],
- ],
-)
-@pytest.mark.skipif(sys.platform != "linux", reason="Causing conflicts on non-linux")
-def test_load_app_from_file_mock_imports(tmpdir, lines):
- path = copy(sys.path)
- app_file = os.path.join(tmpdir, "app.py")
-
- with open(app_file, "w") as f:
- f.write("\n".join(lines))
-
- app = CloudRuntime.load_app_from_file(app_file)
- assert isinstance(app, LightningApp)
- assert isinstance(app.root.work, EmptyWork)
-
- # Cleanup PATH to prevent conflict with other tests
- sys.path = path
- os.remove(app_file)
-
-
-def test_load_app_from_file():
- test_script_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "core", "scripts")
-
- app = CloudRuntime.load_app_from_file(
- os.path.join(test_script_dir, "app_with_env.py"),
- )
- assert app.works[0].cloud_compute.name == "cpu-small"
-
- app = CloudRuntime.load_app_from_file(
- os.path.join(test_script_dir, "app_with_env.py"),
- env_vars={"COMPUTE_NAME": "foo"},
- )
- assert app.works[0].cloud_compute.name == "foo"
-
-
-def test_incompatible_cloud_compute_and_build_config(monkeypatch):
- """Test that an exception is raised when a build config has a custom image defined, but the cloud compute is the
- default.
-
- This combination is not supported by the platform.
-
- """
- mock_client = mock.MagicMock()
- cloud_backend = mock.MagicMock(client=mock_client)
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
-
- class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.cloud_compute = CloudCompute(name="default")
- # TODO: Remove me
- self.cloud_compute.name = "default"
- self.cloud_build_config = BuildConfig(image="custom")
-
- def run(self):
- pass
-
- app = MagicMock()
- app.works = [Work()]
-
- with pytest.raises(ValueError, match="You requested a custom base image for the Work with name"):
- CloudRuntime(app=app)._validate_work_build_specs_and_compute()
-
-
-def test_programmatic_lightningignore(monkeypatch, caplog, tmpdir):
- path = Path(tmpdir)
- entrypoint = path / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(cluster_id="test")
- cloud_backend = mock.MagicMock(client=mock_client)
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
-
- class MyWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.lightningignore += ("foo", "lightning_logs")
-
- def run(self):
- with pytest.raises(RuntimeError, match="w.lightningignore` does not"):
- self.lightningignore += ("foobar",)
-
- class MyFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.lightningignore = ("foo",)
- self.w = MyWork()
-
- def run(self):
- with pytest.raises(RuntimeError, match="root.lightningignore` does not"):
- self.lightningignore = ("baz",)
- self.w.run()
-
- flow = MyFlow()
- app = LightningApp(flow)
-
- monkeypatch.setattr(app, "_update_index_file", mock.MagicMock())
-
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- monkeypatch.setattr(LocalSourceCodeDir, "upload", mock.MagicMock())
-
- # write some files
- write_file_of_size(path / "a.txt", 5 * 1000 * 1000)
- write_file_of_size(path / "foo.png", 4 * 1000 * 1000)
- write_file_of_size(path / "lightning_logs" / "foo.ckpt", 6 * 1000 * 1000)
- # also an actual .lightningignore file
- (path / ".lightningignore").write_text("foo.png")
-
- with mock.patch(
- "lightning.app.runners.cloud._parse_lightningignore", wraps=_parse_lightningignore
- ) as parse_mock, mock.patch(
- "lightning.app.source_code.local._copytree", wraps=_copytree
- ) as copy_mock, caplog.at_level(logging.WARN):
- cloud_runtime.dispatch()
-
- parse_mock.assert_called_once_with(("foo", "foo", "lightning_logs"))
- assert copy_mock.mock_calls[0].kwargs["ignore_functions"][0].args[1] == {"lightning_logs", "foo"}
-
- assert f"Your application folder '{path.absolute()}' is more than 2 MB" in caplog.text
- assert "The total size is 5.0 MB" in caplog.text
- assert "2 files were uploaded" # a.txt and .lightningignore
- assert "files:\n5.0 MB: a.txt\nPerhaps" in caplog.text # only this file appears
-
- flow.run()
-
-
-def test_default_lightningignore(monkeypatch, caplog, tmpdir):
- path = Path(tmpdir)
- entrypoint = path / "entrypoint.py"
- entrypoint.touch()
-
- mock_client = mock.MagicMock()
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(name="test-project", project_id="test-project-id")]
- )
- mock_client.lightningapp_instance_service_list_lightningapp_instances.return_value = (
- V1ListLightningappInstancesResponse(lightningapps=[])
- )
- mock_client.cloud_space_service_create_lightning_run.return_value = V1LightningRun(cluster_id="test")
- cloud_backend = mock.MagicMock(client=mock_client)
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
-
- class MyWork(LightningWork):
- def run(self):
- pass
-
- app = LightningApp(MyWork())
-
- cloud_runtime = cloud.CloudRuntime(app=app, entrypoint=entrypoint)
- monkeypatch.setattr(LocalSourceCodeDir, "upload", mock.MagicMock())
-
- # write some files
- write_file_of_size(path / "a.txt", 5 * 1000 * 1000)
- write_file_of_size(path / "venv" / "foo.txt", 4 * 1000 * 1000)
-
- assert not (path / ".lightningignore").exists()
-
- with mock.patch(
- "lightning.app.runners.cloud._parse_lightningignore", wraps=_parse_lightningignore
- ) as parse_mock, mock.patch(
- "lightning.app.source_code.local._copytree", wraps=_copytree
- ) as copy_mock, caplog.at_level(logging.WARN):
- cloud_runtime.dispatch()
-
- parse_mock.assert_called_once_with(())
- assert copy_mock.mock_calls[0].kwargs["ignore_functions"][0].args[1] == set()
-
- assert (path / ".lightningignore").exists()
-
- assert f"Your application folder '{path.absolute()}' is more than 2 MB" in caplog.text
- assert "The total size is 5.0 MB" in caplog.text
- assert "2 files were uploaded" # a.txt and .lightningignore
- assert "files:\n5.0 MB: a.txt\nPerhaps" in caplog.text # only this file appears
-
-
-@pytest.mark.parametrize(
- ("project", "run_instance", "user", "tab", "lightning_cloud_url", "expected_url"),
- [
- # Old style
- (
- V1Membership(),
- Externalv1LightningappInstance(id="test-app-id"),
- V1GetUserResponse(username="tester", features=V1UserFeatures()),
- "logs",
- "https://lightning.ai",
- "https://lightning.ai/tester/apps/test-app-id/logs",
- ),
- (
- V1Membership(),
- Externalv1LightningappInstance(id="test-app-id"),
- V1GetUserResponse(username="tester", features=V1UserFeatures()),
- "logs",
- "http://localhost:9800",
- "http://localhost:9800/tester/apps/test-app-id/logs",
- ),
- # New style
- (
- V1Membership(name="tester's project"),
- Externalv1LightningappInstance(name="test/job"),
- V1GetUserResponse(username="tester", features=V1UserFeatures(project_selector=True)),
- "logs",
- "https://lightning.ai",
- "https://lightning.ai/tester/tester%27s%20project/jobs/test%2Fjob/logs",
- ),
- (
- V1Membership(name="tester's project"),
- Externalv1LightningappInstance(name="test/job"),
- V1GetUserResponse(username="tester", features=V1UserFeatures(project_selector=True)),
- "logs",
- "https://localhost:9800",
- "https://localhost:9800/tester/tester%27s%20project/jobs/test%2Fjob/logs",
- ),
- ],
-)
-def test_get_app_url(monkeypatch, project, run_instance, user, tab, lightning_cloud_url, expected_url):
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = user
- cloud_backend = mock.MagicMock(client=mock_client)
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
-
- runtime = CloudRuntime()
-
- with mock.patch(
- "lightning.app.runners.cloud.get_lightning_cloud_url", mock.MagicMock(return_value=lightning_cloud_url)
- ):
- assert runtime._get_app_url(project, run_instance, tab) == expected_url
-
-
-@pytest.mark.parametrize(
- ("user", "project", "cloudspace_name", "tab", "lightning_cloud_url", "expected_url"),
- [
- (
- V1GetUserResponse(username="tester", features=V1UserFeatures()),
- V1Membership(name="default-project"),
- "test/cloudspace",
- "code",
- "https://lightning.ai",
- "https://lightning.ai/tester/default-project/apps/test%2Fcloudspace/code",
- ),
- (
- V1GetUserResponse(username="tester", features=V1UserFeatures()),
- V1Membership(name="Awesome Project"),
- "The Best CloudSpace ever",
- "web-ui",
- "http://localhost:9800",
- "http://localhost:9800/tester/Awesome%20Project/apps/The%20Best%20CloudSpace%20ever/web-ui",
- ),
- ],
-)
-def test_get_cloudspace_url(monkeypatch, user, project, cloudspace_name, tab, lightning_cloud_url, expected_url):
- mock_client = mock.MagicMock()
- mock_client.auth_service_get_user.return_value = user
- cloud_backend = mock.MagicMock(client=mock_client)
- monkeypatch.setattr(backends, "CloudBackend", mock.MagicMock(return_value=cloud_backend))
-
- runtime = CloudRuntime()
-
- with mock.patch(
- "lightning.app.runners.cloud.get_lightning_cloud_url", mock.MagicMock(return_value=lightning_cloud_url)
- ):
- assert runtime._get_cloudspace_url(project, cloudspace_name, tab) == expected_url
diff --git a/tests/tests_app/runners/test_multiprocess.py b/tests/tests_app/runners/test_multiprocess.py
deleted file mode 100644
index ce6c41e2d56fb..0000000000000
--- a/tests/tests_app/runners/test_multiprocess.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import os
-import sys
-from unittest import mock
-from unittest.mock import Mock
-
-import pytest
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.core import constants
-from lightning.app.frontend import StaticWebFrontend, StreamlitFrontend
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.utilities.component import _get_context
-from lightning.app.utilities.imports import _IS_WINDOWS
-
-
-def _streamlit_render_fn():
- pass
-
-
-class StreamlitFlow(LightningFlow):
- def run(self):
- self.stop()
-
- def configure_layout(self):
- frontend = StreamlitFrontend(render_fn=_streamlit_render_fn)
- frontend.start_server = Mock()
- frontend.stop_server = Mock()
- return frontend
-
-
-class WebFlow(LightningFlow):
- def run(self):
- self.stop()
-
- def configure_layout(self):
- frontend = StaticWebFrontend(serve_dir="a/b/c")
- frontend.start_server = Mock()
- frontend.stop_server = Mock()
- return frontend
-
-
-class StartFrontendServersTestFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.flow0 = StreamlitFlow()
- self.flow1 = WebFlow()
-
- def run(self):
- self.stop()
-
-
-@pytest.mark.skipif(_IS_WINDOWS, reason="strange TimeOut exception")
-@pytest.mark.xfail(strict=False, reason="hanging with timeout") # fixme
-@pytest.mark.parametrize(
- ("cloudspace_host", "port", "expected_host", "expected_target"),
- [
- (None, 7000, "localhost", "http://localhost:7000"),
- ("test.lightning.ai", 7000, "0.0.0.0", "https://7000-test.lightning.ai"), # noqa: S104
- ],
-)
-@mock.patch("lightning.app.runners.multiprocess.find_free_network_port")
-def test_multiprocess_starts_frontend_servers(
- mock_find_free_network_port, monkeypatch, cloudspace_host, port, expected_host, expected_target
-):
- """Test that the MultiProcessRuntime starts the servers for the frontends in each LightningFlow."""
-
- monkeypatch.setattr(constants, "LIGHTNING_CLOUDSPACE_HOST", cloudspace_host)
- mock_find_free_network_port.return_value = port
-
- root = StartFrontendServersTestFlow()
- app = LightningApp(root)
- MultiProcessRuntime(app).dispatch()
-
- app.frontends[root.flow0.name].start_server.assert_called_once()
- assert app.frontends[root.flow0.name].start_server.call_args.kwargs["host"] == expected_host
-
- app.frontends[root.flow1.name].start_server.assert_called_once()
- assert app.frontends[root.flow1.name].start_server.call_args.kwargs["host"] == expected_host
-
- assert app.frontends[root.flow0.name].flow._layout["target"] == f"{expected_target}/{root.flow0.name}"
- assert app.frontends[root.flow1.name].flow._layout["target"] == f"{expected_target}/{root.flow1.name}"
-
- app.frontends[root.flow0.name].stop_server.assert_called_once()
- app.frontends[root.flow1.name].stop_server.assert_called_once()
-
-
-class ContextWork(LightningWork):
- def __init__(self):
- super().__init__()
-
- def run(self):
- assert _get_context().value == "work"
-
-
-class ContextFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = ContextWork()
- assert _get_context() is None
-
- def run(self):
- assert _get_context().value == "flow"
- self.work.run()
- assert _get_context().value == "flow"
- self.stop()
-
-
-@pytest.mark.skipif(_IS_WINDOWS, reason="strange TimeOut exception")
-@pytest.mark.xfail(strict=False, reason="hanging with timeout") # fixme
-def test_multiprocess_runtime_sets_context():
- """Test that the runtime sets the global variable COMPONENT_CONTEXT in Flow and Work."""
- MultiProcessRuntime(LightningApp(ContextFlow())).dispatch()
-
-
-@pytest.mark.parametrize(
- ("env", "expected_url"),
- [
- ({}, "http://127.0.0.1:7501/view"),
- ({"APP_SERVER_HOST": "http://test"}, "http://test"),
- ],
-)
-@pytest.mark.skipif(sys.platform == "win32", reason="hanging with timeout")
-def test_get_app_url(env, expected_url):
- with mock.patch.dict(os.environ, env):
- assert MultiProcessRuntime._get_app_url() == expected_url
diff --git a/tests/tests_app/runners/test_runtime.py b/tests/tests_app/runners/test_runtime.py
deleted file mode 100644
index fcf861e07310c..0000000000000
--- a/tests/tests_app/runners/test_runtime.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import os
-import signal
-from unittest import mock
-
-import pytest
-from lightning.app.runners import cloud
-from lightning.app.runners.runtime import dispatch
-from lightning.app.runners.runtime_type import RuntimeType
-
-from tests_app import _PROJECT_ROOT
-
-
-@pytest.mark.parametrize(
- "runtime_type",
- [
- RuntimeType.MULTIPROCESS,
- RuntimeType.CLOUD,
- ],
-)
-@mock.patch("lightning.app.core.queues.QueuingSystem", mock.MagicMock())
-@mock.patch("lightning.app.runners.backends.cloud.LightningClient", mock.MagicMock())
-def test_dispatch(runtime_type, monkeypatch):
- """This test ensures the runtime dispatch method gets called when using dispatch."""
- monkeypatch.setattr(cloud, "CloudBackend", mock.MagicMock())
-
- with pytest.raises(FileNotFoundError, match="doesnt_exists.py"):
- dispatch(
- entrypoint_file=os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/doesnt_exists.py"),
- runtime_type=runtime_type,
- start_server=False,
- )
-
- runtime = runtime_type.get_runtime()
- dispath_method_path = f"{runtime.__module__}.{runtime.__name__}.dispatch"
-
- with mock.patch(dispath_method_path) as dispatch_mock_fn:
- dispatch(
- entrypoint_file=os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/app_metadata.py"),
- runtime_type=runtime_type,
- start_server=False,
- )
- dispatch_mock_fn.assert_called_once()
- assert signal.getsignal(signal.SIGINT) is signal.default_int_handler
diff --git a/tests/tests_app/source_code/test_copytree.py b/tests/tests_app/source_code/test_copytree.py
deleted file mode 100644
index fb6f812f4486b..0000000000000
--- a/tests/tests_app/source_code/test_copytree.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import os
-
-from lightning.app.source_code.copytree import _copytree, _read_lightningignore
-
-
-def test_read_lightningignore(tmpdir):
- """_read_lightningignore() removes comments from ignore files."""
- test_path = tmpdir.join(".lightningignore")
- expected = "test"
- not_expected = "# comment"
- with open(test_path, "a") as f:
- f.write(not_expected)
- f.write(expected)
-
- result = _read_lightningignore(test_path)
- assert not_expected not in result
- assert expected not in result
-
-
-def test_read_lightningignore_excludes_empty_lines(tmpdir):
- """_read_lightningignore() excludes empty lines."""
- test_path = tmpdir.join(".lightningignore")
- gitignore = """
-
- foo
-
- bar
-
-
-
- """
- test_path.write(gitignore)
-
- # results exclude all empty lines
- result = _read_lightningignore(test_path)
- assert len(result) == 2
-
-
-def test_copytree_ignoring_files(tmp_path_factory):
- # lightningignore for ignoring txt file in dir2, the whole dir1 and .zip file everywhere
- test_dir = tmp_path_factory.mktemp("lightningignore-test")
- source = test_dir / "source"
- source.mkdir()
-
- # lightningignore at root
- source.joinpath(".lightningignore").write_text("dir1/*.txt\ndir0\n*.zip")
-
- # not creating the destination directory
- dest = test_dir / "dest"
-
- # # setting up test files and nested lightningignore in dir4
- source.joinpath("dir3").mkdir()
- source.joinpath("dir3").joinpath(".lightningignore").write_text("*.pt")
- source.joinpath("dir3").joinpath("model.pt").write_text("")
- source.joinpath("dir3").joinpath("model.non-pt").write_text("")
-
- source.joinpath("dir0").mkdir() # dir0 is ignored
- source.joinpath("dir0/file1").write_text("") # ignored because the parent dir is ignored
- source.joinpath("dir1").mkdir()
- source.joinpath("dir1/file.tar.gz").write_text("")
- source.joinpath("dir1/file.txt").write_text("") # .txt in dir1 is ignored
- source.joinpath("dir2").mkdir()
- source.joinpath("dir2/file.txt").write_text("")
- source.joinpath("dir2/file.zip").write_text("") # .zip everywhere is ignored
-
- files_copied = _copytree(source, dest)
- relative_names = set()
- for file in files_copied:
- relative_names.add(file.split("source")[1].strip("/").strip("\\"))
-
- if os.name == "nt":
- assert {
- ".lightningignore",
- "dir2\\file.txt",
- "dir3\\.lightningignore",
- "dir3\\model.non-pt",
- "dir1\\file.tar.gz",
- } == relative_names
- else:
- assert {
- ".lightningignore",
- "dir2/file.txt",
- "dir3/.lightningignore",
- "dir3/model.non-pt",
- "dir1/file.tar.gz",
- } == relative_names
-
- first_level_dirs = list(dest.iterdir())
- assert len(first_level_dirs) == 4 # .lightningignore, dir2, dir1 and dir3
- assert {".lightningignore", "dir2", "dir1", "dir3"} == {d.name for d in first_level_dirs}
-
- for d in first_level_dirs:
- if d.name == "dir1":
- assert "file.txt" not in [file.name for file in d.iterdir()]
- assert "file.tar.gz" in [file.name for file in d.iterdir()]
- assert len([file.name for file in d.iterdir()]) == 1
-
- if d.name == "dir2":
- assert "file.zip" not in [file.name for file in d.iterdir()]
- assert "file.txt" in [file.name for file in d.iterdir()]
- assert len([file.name for file in d.iterdir()]) == 1
-
- if d.name == "dir3":
- assert "model.pt" not in [file.name for file in d.iterdir()]
- assert "model.non-pt" in [file.name for file in d.iterdir()]
- assert ".lightningignore" in [file.name for file in d.iterdir()]
- assert len([file.name for file in d.iterdir()]) == 2
diff --git a/tests/tests_app/source_code/test_local.py b/tests/tests_app/source_code/test_local.py
deleted file mode 100644
index 11fa62dd28bf5..0000000000000
--- a/tests/tests_app/source_code/test_local.py
+++ /dev/null
@@ -1,376 +0,0 @@
-import os
-import sys
-import tarfile
-import uuid
-from pathlib import Path
-from unittest import mock
-
-import pytest
-from lightning.app.source_code import LocalSourceCodeDir
-
-
-def test_repository_checksum(tmp_path):
- """LocalRepository.version() generates a different version each time."""
- repository = LocalSourceCodeDir(path=Path(tmp_path))
- version_a = repository.version
-
- # version is different
- repository = LocalSourceCodeDir(path=Path(tmp_path))
- version_b = repository.version
-
- assert version_a != version_b
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="this runs only on linux")
-@mock.patch.dict(os.environ, {"LIGHTNING_VSCODE_WORKSPACE": "something"})
-def test_local_cache_path_tmp(tmp_path):
- """LocalRepository.cache_location is under tmp."""
- repository = LocalSourceCodeDir(path=Path(tmp_path))
- assert str(repository.cache_location).startswith("/tmp")
-
-
-def test_local_cache_path_home(tmp_path):
- """LocalRepository.cache_location is under home."""
- repository = LocalSourceCodeDir(path=Path(tmp_path))
- assert str(repository.cache_location).startswith(str(Path.home()))
-
-
-def test_repository_package(tmp_path, monkeypatch):
- """LocalRepository.package() creates package from local dir."""
- cache_path = Path(tmp_path)
- source_path = cache_path / "nested"
- source_path.mkdir(parents=True, exist_ok=True)
- (source_path / "test.txt").write_text("test")
-
- repository = LocalSourceCodeDir(path=source_path)
- repository.cache_location = cache_path
- repository.package()
-
- # test that package is created
- for file in cache_path.glob("**/*"):
- if file.is_file() and file.name.endswith(".tar.gz"):
- assert file.name == f"{repository.version}.tar.gz"
-
-
-def test_repository_lightningignore(tmp_path):
- """LocalRepository.version uses the assumed checksum correctly."""
- # write .lightningignore file
- lightningignore = """
- # ignore files in this dir
- ignore/
-
- """
- (tmp_path / ".lightningignore").write_text(lightningignore)
- (tmp_path / "test.txt").write_text("test")
-
- repository = LocalSourceCodeDir(path=Path(tmp_path))
-
- assert set(repository.files) == {str(tmp_path / ".lightningignore"), str(tmp_path / "test.txt")}
-
- # write file that needs to be ignored
- (tmp_path / "ignore").mkdir()
- (tmp_path / "ignore/test.txt").write_text(str(uuid.uuid4()))
-
- repository = LocalSourceCodeDir(path=Path(tmp_path))
-
- assert set(repository.files) == {str(tmp_path / ".lightningignore"), str(tmp_path / "test.txt")}
-
-
-def test_repository_filters_with_absolute_relative_path(tmp_path):
- """.lightningignore parsing parses paths starting with / correctly."""
- lightningignore = """
- /ignore_file/test.txt
-
- /ignore_dir
- """
- (tmp_path / ".lightningignore").write_text(lightningignore)
- (tmp_path / "test.txt").write_text("test")
-
- repository = LocalSourceCodeDir(path=Path(tmp_path))
-
- assert set(repository.files) == {str(tmp_path / ".lightningignore"), str(tmp_path / "test.txt")}
-
- # write file that needs to be ignored
- (tmp_path / "ignore_file").mkdir()
- (tmp_path / "ignore_dir").mkdir()
- (tmp_path / "ignore_file/test.txt").write_text(str(uuid.uuid4()))
- (tmp_path / "ignore_dir/test.txt").write_text(str(uuid.uuid4()))
-
- repository = LocalSourceCodeDir(path=Path(tmp_path))
-
- assert set(repository.files) == {str(tmp_path / ".lightningignore"), str(tmp_path / "test.txt")}
-
-
-def test_repository_lightningignore_supports_different_patterns(tmp_path):
- """.lightningignore parsing supports different patterns."""
- # write .lightningignore file
- # default github python .gitignore
- lightningignore = """
- # ignore files in this dir
- ignore/
-
- # Byte-compiled / optimized / DLL files
- __pycache__/
- *.py[cod]
- *$py.class
-
- # C extensions
- *.so
-
- # Distribution / packaging
- .Python
- build/
- develop-eggs/
- dist/
- downloads/
- eggs/
- .eggs/
- lib/
- lib64/
- parts/
- sdist/
- var/
- wheels/
- *.egg-info/
- .installed.cfg
- *.egg
- MANIFEST
-
- # PyInstaller
- # Usually these files are written by a python script from a template
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
- *.manifest
- *.spec
-
- # Installer logs
- pip-log.txt
- pip-delete-this-directory.txt
-
- # Unit test / coverage reports
- htmlcov/
- .tox/
- .coverage
- .coverage.*
- .cache
- nosetests.xml
- coverage.xml
- *.cover
- .hypothesis/
- .pytest_cache/
-
- # Translations
- *.mo
- *.pot
-
- # Django stuff:
- *.log
- local_settings.py
- db.sqlite3
-
- # Flask stuff:
- instance/
- .webassets-cache
-
- # Scrapy stuff:
- .scrapy
-
- # Sphinx documentation
- docs/_build/
-
- # PyBuilder
- target/
-
- # Jupyter Notebook
- .ipynb_checkpoints
-
- # pyenv
- .python-version
-
- # celery beat schedule file
- celerybeat-schedule
-
- # SageMath parsed files
- *.sage.py
-
- # Environments
- .env
- .env.docker
- .venv
- env/
- venv/
- ENV/
- env.bak/
- venv.bak/
-
- # Spyder project settings
- .spyderproject
- .spyproject
-
- # Rope project settings
- .ropeproject
-
- # mkdocs documentation
- /site
-
- # mypy
- .mypy_cache/
-
- # VS Code files
- .vscode/
-
- # UI files
- node_modules/
-
- # Data files
- models/
- models/*
- !grid/openapi/models
- postgresql_data/
- redis_data/
-
- # Secrets folders
- secrets/
-
- # Built UI
- ui/
-
- # Ignores Grid Runner
- vendor/
- ignore_test.py
-
- # Ignore cov report
- *.xml
-
- """
- (tmp_path / ".lightningignore").write_text(lightningignore)
- (tmp_path / "test.txt").write_text("test")
-
- repository = LocalSourceCodeDir(path=Path(tmp_path))
-
- assert set(repository.files) == {str(tmp_path / ".lightningignore"), str(tmp_path / "test.txt")}
-
- # write file that needs to be ignored
- (tmp_path / "ignore").mkdir()
- (tmp_path / "ignore/test.txt").write_text(str(uuid.uuid4()))
-
- # check that version remains the same
- repository = LocalSourceCodeDir(path=Path(tmp_path))
-
- assert set(repository.files) == {str(tmp_path / ".lightningignore"), str(tmp_path / "test.txt")}
-
-
-def test_repository_lightningignore_unpackage(tmp_path, monkeypatch):
- """.lightningignore behaves similarly to the gitignore standard."""
- lorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
-
- cache_path = tmp_path / "cache"
- source_path = tmp_path / "source"
- source_path.mkdir()
-
- # set cache location to temp dir
-
- lightningignore = """
- # Ignore on all levels
- *.pyc
- *__pycache__/
- build/
- .env
- # Ignore wildcard on one level
- ./*.txt
- /*.md
- ./one-level/*.txt
- /one-level/*.md
- # Ignore only relative
- ./downloads
- /relative_downloads
- # nested
- /nested//level/
- /nested/level/
- """
- (source_path / ".lightningignore").write_text(lightningignore)
-
- # Dir structure
- (source_path / "include.py").write_text(lorem_ipsum)
- (source_path / "exclude.pyc").write_text(lorem_ipsum)
- (source_path / "__pycache__").mkdir()
- (source_path / "__pycache__" / "exclude.py").write_text(
- lorem_ipsum
- ) # Even tho it's .py it's in excluded __pycache__ directory
- (source_path / "__pycache__" / "exclude.pyc").write_text(
- lorem_ipsum
- ) # Even tho it's .py it's in excluded __pycache__ directory
- (source_path / "build.py").write_text(lorem_ipsum) # Common prefix with excluded build but it's not it
- (source_path / "builds").mkdir() # Common prefix with excluded build but it's not excluded
- (source_path / "builds" / "include.py").write_text(lorem_ipsum)
- (source_path / "builds" / "__pycache__").mkdir() # Recursively excluded
- (source_path / "builds" / "__pycache__" / "exclude.py").write_text(lorem_ipsum)
- (source_path / "build").mkdir() # Recursively excluded
- (source_path / "build" / "exclude.db").write_text(lorem_ipsum)
- (source_path / ".env").write_text(lorem_ipsum) # No issues with handling hidden (.dot) files
- (source_path / "downloads").mkdir() # exclude
- (source_path / "downloads" / "something.jpeg").write_text(lorem_ipsum)
- (source_path / "relative_downloads").mkdir() # exclude
- (source_path / "relative_downloads" / "something.jpeg").write_text(lorem_ipsum)
- (source_path / "include").mkdir() # include
- (source_path / "include" / "exclude.pyc").write_text(lorem_ipsum) # exclude because of *.pyc rule
- (source_path / "include" / "include.py").write_text(lorem_ipsum) # include
- (source_path / "include" / "downloads").mkdir() # include because it was excluded only relative to root
- (source_path / "include" / "downloads" / "something.jpeg").write_text(lorem_ipsum)
- (source_path / "include" / "relative_downloads").mkdir() # include because it was excluded only relative to root
- (source_path / "include" / "relative_downloads" / "something.jpeg").write_text(lorem_ipsum)
- (source_path / "exclude.txt").write_text(lorem_ipsum)
- (source_path / "exclude.md").write_text(lorem_ipsum)
- (source_path / "one-level").mkdir()
- (source_path / "one-level" / "exclude.txt").write_text(lorem_ipsum)
- (source_path / "one-level" / "exclude.md").write_text(lorem_ipsum)
- (source_path / "one-level" / "include.py").write_text(lorem_ipsum)
- (source_path / "nested").mkdir()
- (source_path / "nested" / "include.py").write_text(lorem_ipsum)
- (source_path / "nested" / "level").mkdir()
- (source_path / "nested" / "level" / "exclude.py").write_text(lorem_ipsum)
-
- # create repo object
- repository = LocalSourceCodeDir(path=source_path)
- repository.cache_location = cache_path
- repository.package()
-
- unpackage_path = tmp_path / "unpackage"
-
- with tarfile.open(repository.package_path) as f:
- f.extractall(unpackage_path)
-
- assert (unpackage_path / "include.py").exists()
- assert not (unpackage_path / "exclude.pyc").exists() # Excluded by *.pyc
- assert not (unpackage_path / "__pycache__").exists()
- assert not (
- unpackage_path / "__pycache__" / "exclude.py"
- ).exists() # Even tho it's .py it's in excluded __pycache__ directory
- assert not (
- unpackage_path / "__pycache__" / "exclude.pyc"
- ).exists() # Even tho it's .py it's in excluded __pycache__ directory
- assert (unpackage_path / "build.py").exists() # Common prefix with excluded build but it's not it
- assert (unpackage_path / "builds" / "include.py").exists()
- assert not (unpackage_path / "builds" / "__pycache__").exists() # Recursively excluded
- assert not (unpackage_path / "builds" / "__pycache__" / "exclude.py").exists()
- assert not (unpackage_path / "build").exists() # Recursively excluded
- assert not (unpackage_path / "build" / "exclude.db").exists()
- assert not (unpackage_path / ".env").exists() # No issues with handling hidden (.dot) files
- assert not (unpackage_path / "downloads").mkdir() # exclude
- assert not (unpackage_path / "downloads" / "something.jpeg").exists()
- assert not (unpackage_path / "relative_downloads").mkdir() # exclude
- assert not (unpackage_path / "relative_downloads" / "something.jpeg").exists()
- assert not (unpackage_path / "include" / "exclude.pyc").exists() # exclude because of *.pyc rule
- assert (unpackage_path / "include" / "include.py").exists() # include
- assert (
- unpackage_path / "include" / "downloads" / "something.jpeg"
- ).exists() # include because it was excluded only relative to root
- assert (
- unpackage_path / "include" / "relative_downloads" / "something.jpeg"
- ).exists() # include because it was excluded only relative to root
- assert not (unpackage_path / "exclude.txt").exists()
- assert not (unpackage_path / "exclude.md").exists()
- assert not (unpackage_path / "one-level" / "exclude.txt").exists()
- assert not (unpackage_path / "one-level" / "exclude.md").exists()
- assert (unpackage_path / "one-level" / "include.py").exists()
- assert (unpackage_path / "nested" / "include.py").exists()
- assert not (unpackage_path / "nested" / "level" / "exclude.py").exists()
diff --git a/tests/tests_app/source_code/test_tar.py b/tests/tests_app/source_code/test_tar.py
deleted file mode 100644
index a1e8d99bfe8de..0000000000000
--- a/tests/tests_app/source_code/test_tar.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import math
-import os
-import tarfile
-from pathlib import Path
-
-import pytest
-from lightning.app.source_code.tar import MAX_SPLIT_COUNT, _get_dir_size_and_count, _get_split_size, _tar_path
-
-
-def _create_files(basedir: Path):
- source_dir = basedir / "source"
- inner_dir = source_dir / "dir"
- os.makedirs(inner_dir)
- with open(source_dir / "f1", "w") as fp:
- fp.write("f1")
-
- with open(inner_dir / "f2", "w") as fp:
- fp.write("f2")
- return source_dir, inner_dir
-
-
-def test_max_upload_parts():
- import click
-
- with pytest.raises(click.ClickException):
- barely_over = MAX_SPLIT_COUNT * 2**31 + 1
- _get_split_size(barely_over)
-
-
-def test_almost_max_upload_parts():
- barely_under = MAX_SPLIT_COUNT * 2**31 - 1
- assert _get_split_size(barely_under) == math.ceil(barely_under / MAX_SPLIT_COUNT)
-
-
-@pytest.mark.parametrize("size", [1024 * 512, 1024 * 1024 * 5])
-def test_get_dir_size_and_count(tmpdir: Path, size):
- data = os.urandom(size)
- with open(os.path.join(tmpdir, "a"), "wb") as f:
- f.write(data)
- with open(os.path.join(tmpdir, "b"), "wb") as f:
- f.write(data)
- assert _get_dir_size_and_count(tmpdir, "a") == (size, 1)
-
-
-def test_tar_path(tmpdir: Path, monkeypatch):
- source_dir, inner_dir = _create_files(tmpdir)
-
- # Test directory
- target_file = tmpdir / "target.tar.gz"
- results = _tar_path(source_path=source_dir, target_file=target_file)
- assert results.before_size > 0
- assert results.after_size > 0
-
- verify_dir = tmpdir / "verify"
- os.makedirs(verify_dir)
- with tarfile.open(target_file) as tar:
- tar.extractall(verify_dir)
-
- assert (verify_dir / "f1").exists()
- assert (verify_dir / "dir" / "f2").exists()
-
- # Test single file
- f2_path = inner_dir / "f2"
-
- target_file = tmpdir / "target_file.tar.gz"
- results = _tar_path(source_path=f2_path, target_file=target_file)
- assert results.before_size > 0
- assert results.after_size > 0
-
- verify_dir = tmpdir / "verify_file"
- os.makedirs(verify_dir)
- with tarfile.open(target_file) as tar:
- tar.extractall(verify_dir)
-
- assert (verify_dir / "f2").exists()
-
- # Test single file (local)
- monkeypatch.chdir(inner_dir)
-
- f2_path = "f2"
-
- target_file = tmpdir / "target_file_local.tar.gz"
- results = _tar_path(source_path=f2_path, target_file=target_file)
- assert results.before_size > 0
- assert results.after_size > 0
-
- verify_dir = tmpdir / "verify_file_local"
- os.makedirs(verify_dir)
- with tarfile.open(target_file) as tar:
- tar.extractall(verify_dir)
-
- assert (verify_dir / "f2").exists()
-
-
-def test_get_split_size():
- split_size = _get_split_size(minimum_split_size=1024 * 1000 * 10, max_split_count=10000, total_size=200000000001)
-
- # We shouldn't go over the max split count
- assert math.ceil(200000000001 / split_size) <= 10000
-
- split_size = _get_split_size(
- minimum_split_size=1024 * 1000 * 10, max_split_count=10000, total_size=1024 * 500 * 1000 * 10
- )
-
- assert split_size == 1024 * 1000 * 10
-
-
-def test_tar_path_no_compression(tmpdir):
- source_dir, _ = _create_files(tmpdir)
-
- target_file = tmpdir / "target.tar.gz"
- _tar_path(source_path=source_dir, target_file=target_file, compression=False)
-
- verify_dir = tmpdir / "verify"
- os.makedirs(verify_dir)
- with tarfile.open(target_file) as target_tar:
- target_tar.extractall(verify_dir)
-
- assert (verify_dir / "f1").exists()
- assert (verify_dir / "dir" / "f2").exists()
diff --git a/tests/tests_app/source_code/test_uploader.py b/tests/tests_app/source_code/test_uploader.py
deleted file mode 100644
index 7617d9c647510..0000000000000
--- a/tests/tests_app/source_code/test_uploader.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from unittest import mock
-from unittest.mock import ANY, MagicMock
-
-import pytest
-from lightning.app.source_code import uploader
-
-# keeping as global var so individual tests can access/modify it
-response = {"response": MagicMock(headers={"ETag": "test-etag"})}
-
-
-class MockedRequestSession(MagicMock):
- def put(self, url, data):
- assert url == "https://test-url"
- assert data == "test-data"
- return response["response"]
-
- def mount(self, prefix, adapter):
- assert prefix == "https://"
- assert adapter.max_retries.total == 10
-
-
-@mock.patch("builtins.open", mock.mock_open(read_data="test-data"))
-@mock.patch("lightning.app.source_code.uploader.requests.Session", MockedRequestSession)
-def test_file_uploader():
- file_uploader = uploader.FileUploader(
- presigned_url="https://test-url", source_file="test.txt", total_size=100, name="test.txt"
- )
- file_uploader.progress = MagicMock()
-
- file_uploader.upload()
-
- file_uploader.progress.add_task.assert_called_once_with("upload", filename="test.txt", total=100)
- file_uploader.progress.start.assert_called_once()
- file_uploader.progress.update.assert_called_once_with(ANY, advance=9)
-
-
-@mock.patch("builtins.open", mock.mock_open(read_data="test-data"))
-@mock.patch("lightning.app.source_code.uploader.requests.Session", MockedRequestSession)
-def test_file_uploader_failing_when_no_etag():
- response["response"] = MagicMock(headers={})
- presigned_url = "https://test-url"
- file_uploader = uploader.FileUploader(
- presigned_url=presigned_url, source_file="test.txt", total_size=100, name="test.txt"
- )
- file_uploader.progress = MagicMock()
-
- with pytest.raises(ValueError, match=f"Unexpected response from {presigned_url}, response"):
- file_uploader.upload()
diff --git a/tests/tests_app/storage/__init__.py b/tests/tests_app/storage/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/storage/test_copier.py b/tests/tests_app/storage/test_copier.py
deleted file mode 100644
index 14d3f965ce422..0000000000000
--- a/tests/tests_app/storage/test_copier.py
+++ /dev/null
@@ -1,148 +0,0 @@
-import os
-import pathlib
-from unittest import mock
-from unittest.mock import Mock
-
-import lightning.app
-import pytest
-from lightning.app.storage.copier import _Copier, _copy_files
-from lightning.app.storage.path import Path
-from lightning.app.storage.requests import _ExistsRequest, _GetRequest
-from lightning.app.testing.helpers import _MockQueue
-
-
-class MockPatch:
- @staticmethod
- def _handle_get_request(work, request):
- return Path._handle_get_request(work, request)
-
- @staticmethod
- def _handle_exists_request(work, request):
- return Path._handle_exists_request(work, request)
-
-
-@mock.patch("lightning.app.storage.path.pathlib.Path.is_dir")
-@mock.patch("lightning.app.storage.path.pathlib.Path.stat")
-@mock.patch("lightning.app.storage.copier._filesystem")
-def test_copier_copies_all_files(fs_mock, stat_mock, dir_mock, tmpdir):
- """Test that the Copier calls the copy with the information provided in the request."""
- stat_mock().st_size = 0
- dir_mock.return_value = False
- copy_request_queue = _MockQueue()
- copy_response_queue = _MockQueue()
- work = mock.Mock()
- work.name = MockPatch()
- work._paths = {"file": {"source": "src", "path": "file", "hash": "123", "destination": "dest", "name": "name"}}
- with mock.patch.dict(os.environ, {"SHARED_MOUNT_DIRECTORY": str(tmpdir / ".shared")}):
- copier = _Copier(work, copy_request_queue=copy_request_queue, copy_response_queue=copy_response_queue)
- request = _GetRequest(source="src", path="file", hash="123", destination="dest", name="name")
- copy_request_queue.put(request)
- copier.run_once()
- fs_mock().put.assert_called_once_with("file", tmpdir / ".shared" / "123")
-
-
-@mock.patch("lightning.app.storage.path.pathlib.Path.is_dir")
-@mock.patch("lightning.app.storage.path.pathlib.Path.stat")
-def test_copier_handles_exception(stat_mock, dir_mock, monkeypatch):
- """Test that the Copier captures exceptions from the file copy and forwards them through the queue without raising
- it."""
- stat_mock().st_size = 0
- dir_mock.return_value = False
- copy_request_queue = _MockQueue()
- copy_response_queue = _MockQueue()
- fs = mock.Mock()
- fs.exists.return_value = False
- fs.put = mock.Mock(side_effect=OSError("Something went wrong"))
- monkeypatch.setattr(lightning.app.storage.copier, "_filesystem", mock.Mock(return_value=fs))
-
- work = mock.Mock()
- work.name = MockPatch()
- work._paths = {"file": {"source": "src", "path": "file", "hash": "123", "destination": "dest", "name": "name"}}
- copier = _Copier(work, copy_request_queue=copy_request_queue, copy_response_queue=copy_response_queue)
- request = _GetRequest(source="src", path="file", hash="123", destination="dest", name="name")
- copy_request_queue.put(request)
- copier.run_once()
- response = copy_response_queue.get()
- assert type(response.exception) is OSError
- assert response.exception.args[0] == "Something went wrong"
-
-
-def test_copier_existence_check(tmpdir):
- """Test that the Copier responds to an existence check request."""
- copy_request_queue = _MockQueue()
- copy_response_queue = _MockQueue()
-
- work = mock.Mock()
- work.name = MockPatch()
- work._paths = {
- "file": {
- "source": "src",
- "path": str(tmpdir / "notexists"),
- "hash": "123",
- "destination": "dest",
- "name": "name",
- }
- }
-
- copier = _Copier(work, copy_request_queue=copy_request_queue, copy_response_queue=copy_response_queue)
-
- # A Path that does NOT exist
- request = _ExistsRequest(source="src", path=str(tmpdir / "notexists"), destination="dest", name="name", hash="123")
- copy_request_queue.put(request)
- copier.run_once()
- response = copy_response_queue.get()
- assert response.exists is False
-
- # A Path that DOES exist
- request = _ExistsRequest(source="src", path=str(tmpdir), destination="dest", name="name", hash="123")
- copy_request_queue.put(request)
- copier.run_once()
- response = copy_response_queue.get()
- assert response.exists is True
-
-
-def test_copy_files(tmpdir):
- """Test that the `test_copy_files` utility can handle both files and folders when the destination does not
- exist."""
- # copy from a src that does not exist
- src = pathlib.Path(tmpdir, "dir1")
- dst = pathlib.Path(tmpdir, "dir2")
- with pytest.raises(FileNotFoundError):
- _copy_files(src, dst)
-
- # copy to a dst dir that does not exist
- src.mkdir()
- (src / "empty.txt").touch()
- assert not dst.exists()
- _copy_files(src, dst)
- assert dst.is_dir()
-
- # copy to a destination dir that already exists (no error should be raised)
- _copy_files(src, dst)
- assert dst.is_dir()
-
- # copy file to a dst that does not exist
- src = pathlib.Path(tmpdir, "dir3", "src-file.txt")
- dst = pathlib.Path(tmpdir, "dir4", "dst-file.txt")
- src.parent.mkdir(parents=True)
- src.touch()
- assert not dst.exists()
- _copy_files(src, dst)
- assert dst.is_file()
-
-
-def test_copy_files_with_exception(tmpdir):
- """Test that the `test_copy_files` utility properly raises exceptions from within the ThreadPoolExecutor."""
- fs_mock = Mock()
- fs_mock().put = Mock(side_effect=ValueError("error from thread"))
-
- src = pathlib.Path(tmpdir, "src")
- src.mkdir()
- assert src.is_dir()
- pathlib.Path(src, "file.txt").touch()
- dst = pathlib.Path(tmpdir, "dest")
-
- with mock.patch("lightning.app.storage.copier._filesystem", fs_mock), pytest.raises(
- ValueError, match="error from thread"
- ):
- _copy_files(src, dst)
diff --git a/tests/tests_app/storage/test_drive.py b/tests/tests_app/storage/test_drive.py
deleted file mode 100644
index d9fc9b4504372..0000000000000
--- a/tests/tests_app/storage/test_drive.py
+++ /dev/null
@@ -1,256 +0,0 @@
-import os
-import pathlib
-from copy import deepcopy
-from time import sleep
-
-import pytest
-from deepdiff import DeepDiff
-from lightning.app import LightningFlow, LightningWork
-from lightning.app.core.app import LightningApp
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.drive import Drive, _maybe_create_drive
-from lightning.app.utilities.component import _set_flow_context
-
-
-class SyncWorkLITDriveA(LightningWork):
- def __init__(self, tmpdir):
- super().__init__()
- self.tmpdir = tmpdir
-
- def run(self, drive: Drive):
- with open(f"{self.tmpdir}/a.txt", "w") as f:
- f.write("example")
-
- drive.root_folder = self.tmpdir
- drive.put("a.txt")
- os.remove(f"{self.tmpdir}/a.txt")
-
-
-class SyncWorkLITDriveB(LightningWork):
- def run(self, drive: Drive):
- assert not os.path.exists("a.txt")
- drive.get("a.txt")
- assert os.path.exists("a.txt")
-
-
-class SyncFlowLITDrives(LightningFlow):
- def __init__(self, tmpdir):
- super().__init__()
- self.log_dir = Drive("lit://log_dir")
- self.work_a = SyncWorkLITDriveA(str(tmpdir))
- self.work_b = SyncWorkLITDriveB()
-
- def run(self):
- self.work_a.run(self.log_dir)
- self.work_b.run(self.log_dir)
- self.stop()
-
-
-@pytest.mark.flaky(reruns=3, reruns_delay=5) # todo: likely dead feature, fine to crash...
-def test_synchronization_lit_drive(tmpdir):
- if os.path.exists("a.txt"):
- os.remove("a.txt")
- app = LightningApp(SyncFlowLITDrives(tmpdir))
- MultiProcessRuntime(app, start_server=False).dispatch()
- if os.path.exists("a.txt"):
- os.remove("a.txt")
-
-
-class LITDriveWork(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.drive = None
- self.counter = 0
-
- def run(self, *args, **kwargs):
- if self.counter == 0:
- self.drive = Drive("lit://this_drive_id")
- sleep(10)
- with open("a.txt", "w") as f:
- f.write("example")
-
- self.drive.put("a.txt")
- else:
- assert self.drive
- assert self.drive.list(".") == ["a.txt"]
- self.drive.delete("a.txt")
- assert self.drive.list(".") == []
- self.counter += 1
-
-
-class LITDriveWork2(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
-
- def run(self, drive: Drive, **kwargs):
- assert drive.list(".") == []
- drive.get("a.txt", timeout=60)
- assert drive.list(".") == ["a.txt"]
- assert drive.list(".", component_name=self.name) == []
-
-
-class LITDriveFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = LITDriveWork()
- self.work2 = LITDriveWork2()
-
- def run(self):
- self.work.run("0")
- if self.work.drive:
- self.work2.run(self.work.drive, something="hello")
- if self.work2.has_succeeded:
- self.work.run("1")
- if self.work.counter == 2:
- self.stop()
-
-
-@pytest.mark.flaky(reruns=3, reruns_delay=5) # todo: likely dead feature, fine to crash...
-def test_lit_drive_transferring_files():
- app = LightningApp(LITDriveFlow())
- MultiProcessRuntime(app, start_server=False).dispatch()
- os.remove("a.txt")
-
-
-@pytest.mark.xfail(strict=False) # todo: likely dead feature, fine to crash...
-def test_lit_drive():
- with pytest.raises(Exception, match="Unknown protocol for the drive 'id' argument"):
- Drive("invalid_drive_id")
-
- with pytest.raises(
- Exception, match="The id should be unique to identify your drive. Found `this_drive_id/something_else`."
- ):
- Drive("lit://this_drive_id/something_else")
-
- drive = Drive("lit://this_drive_id")
- with pytest.raises(Exception, match="The component name needs to be known to put a path to the Drive."):
- drive.put(".")
-
- with pytest.raises(Exception, match="The component name needs to be known to delete a path to the Drive."):
- drive.delete(".")
-
- with open("a.txt", "w") as f:
- f.write("example")
-
- os.makedirs("checkpoints")
- with open("checkpoints/a.txt", "w") as f:
- f.write("example")
-
- drive = Drive("lit://drive_1", allow_duplicates=False)
- drive.component_name = "root.work_1"
- assert drive.list(".") == []
- drive.put("a.txt")
- assert drive.list(".") == ["a.txt"]
- drive.component_name = "root.work_2"
- with pytest.raises(Exception, match="The file a.txt can't be added as already found in the Drive."):
- drive.put("a.txt")
- drive.get("a.txt")
-
- drive = Drive("lit://drive_2", allow_duplicates=False)
- drive.component_name = "root.work_1"
- drive.put("checkpoints/a.txt")
- drive.component_name = "root.work_2"
- with pytest.raises(Exception, match="The file checkpoints/a.txt can't be added as already found in the Drive."):
- drive.put("checkpoints/a.txt")
-
- drive = Drive("lit://drive_3", allow_duplicates=False)
- drive.component_name = "root.work_1"
- drive.put("checkpoints/")
- drive.component_name = "root.work_2"
- with pytest.raises(Exception, match="The file checkpoints/a.txt can't be added as already found in the Drive."):
- drive.put("checkpoints/a.txt")
-
- drive = Drive("lit://drive_3", allow_duplicates=True)
- drive.component_name = "root.work_1"
- drive.put("checkpoints/")
- drive.component_name = "root.work_2"
- with pytest.raises(
- Exception, match="The file checkpoints/a.txt doesn't exists in the component_name space root.work_2."
- ):
- drive.delete("checkpoints/a.txt")
- drive.put("checkpoints/a.txt")
- drive.delete("checkpoints/a.txt")
-
- drive = Drive("lit://drive_3", allow_duplicates=True)
- drive.component_name = "root.work_1"
- drive.put("checkpoints/")
- with pytest.raises(Exception, match="['root.work_1', 'root.work_2']"):
- drive.get("checkpoints/")
- drive.get("checkpoints/a.txt", component_name="root.work_1")
- drive.get("checkpoints/a.txt", component_name="root.work_1", timeout=1)
-
- with pytest.raises(FileNotFoundError):
- drive.get("checkpoints/b.txt", component_name="root.work_1")
- with pytest.raises(Exception, match="The following checkpoints/b.txt wasn't found in 1 seconds"):
- drive.get("checkpoints/b.txt", component_name="root.work_1", timeout=1)
- drive.component_name = "root.work_2"
- drive.put("checkpoints/")
- drive.component_name = "root.work_3"
- with pytest.raises(Exception, match="We found several matching files created by multiples components"):
- drive.get("checkpoints/a.txt")
- with pytest.raises(Exception, match="We found several matching files created by multiples components"):
- drive.get("checkpoints/a.txt", timeout=1)
-
- drive = Drive("lit://drive_4", allow_duplicates=True)
- drive.component_name = "root.work_1"
- with pytest.raises(Exception, match="The following checkpoints/a.txt wasn't found in 1 seconds."):
- drive.get("checkpoints/a.txt", timeout=1)
-
- drive = Drive("lit://test", allow_duplicates=True)
- drive.component_name = "root.work1"
- drive.put("checkpoints")
- drive.get("checkpoints", overwrite=True)
- with pytest.raises(FileExistsError, match="overwrite=True"):
- drive.get("checkpoints")
-
- drive = Drive("lit://drive_5", allow_duplicates=True)
- drive.component_name = "root.work"
- _set_flow_context()
- with pytest.raises(Exception, match="The flow isn't allowed to put files into a Drive."):
- drive.put("a.txt")
- with pytest.raises(Exception, match="The flow isn't allowed to list files from a Drive."):
- drive.list("a.txt")
- with pytest.raises(Exception, match="The flow isn't allowed to get files from a Drive."):
- drive.get("a.txt")
-
- os.remove("checkpoints/a.txt")
- os.rmdir("checkpoints")
- os.remove("a.txt")
-
-
-@pytest.mark.parametrize("drive_id", ["lit://drive"])
-def test_maybe_create_drive(drive_id):
- drive = Drive(drive_id, allow_duplicates=False)
- drive.component_name = "root.work1"
- assert isinstance(drive.root_folder, pathlib.Path)
- drive_state = drive.to_dict()
- assert isinstance(drive_state["root_folder"], str)
- new_drive = _maybe_create_drive(drive.component_name, drive.to_dict())
- assert isinstance(drive.root_folder, pathlib.Path)
- assert new_drive.protocol == drive.protocol
- assert new_drive.id == drive.id
- assert new_drive.component_name == drive.component_name
- drive_state["root_folder"] = pathlib.Path(drive_state["root_folder"])
- copy_drive_state = deepcopy(drive_state)
- deep_diff = DeepDiff(copy_drive_state, drive_state)
- assert "unprocessed" in deep_diff
- deep_diff.pop("unprocessed")
-
-
-@pytest.mark.parametrize("drive_id", ["lit://drive"])
-def test_drive_deepcopy(drive_id):
- drive = Drive(drive_id, allow_duplicates=True)
- drive.component_name = "root.work1"
- new_drive = deepcopy(drive)
- assert new_drive.id == drive.id
- assert new_drive.component_name == drive.component_name
-
-
-def test_s3_drive_raises_error_telling_users_to_use_mounts():
- with pytest.raises(ValueError, match="Using S3 buckets in a Drive is no longer supported."):
- Drive("s3://foo/")
-
-
-def test_drive_root_folder_breaks():
- with pytest.raises(Exception, match="The provided root_folder isn't a directory: a"):
- Drive("lit://drive", root_folder="a")
diff --git a/tests/tests_app/storage/test_filesystem.py b/tests/tests_app/storage/test_filesystem.py
deleted file mode 100644
index 8bc760209c602..0000000000000
--- a/tests/tests_app/storage/test_filesystem.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import os
-import sys
-
-import pytest
-from lightning.app.storage import FileSystem
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="TODO: Add support for windows")
-def test_filesystem(tmpdir):
- fs = FileSystem()
-
- with open(f"{tmpdir}/a.txt", "w") as f:
- f.write("example")
-
- os.makedirs(f"{tmpdir}/checkpoints", exist_ok=True)
- with open(f"{tmpdir}/checkpoints/a.txt", "w") as f:
- f.write("example")
-
- with open(f"{tmpdir}/info.txt", "w") as f:
- f.write("example")
-
- assert fs.listdir("/") == []
- fs.put(f"{tmpdir}/a.txt", "/a.txt")
- fs.put(f"{tmpdir}/info.txt", "/info.txt")
- assert fs.listdir("/") == ["a.txt"]
-
- assert fs.isfile("/a.txt")
-
- fs.put(f"{tmpdir}/checkpoints", "/checkpoints")
- assert not fs.isfile("/checkpoints")
- assert fs.isdir("/checkpoints")
- assert fs.isfile("/checkpoints/a.txt")
-
- assert fs.listdir("/") == ["a.txt", "checkpoints"]
- assert fs.walk("/") == ["a.txt", "checkpoints/a.txt"]
-
- os.remove(f"{tmpdir}/a.txt")
-
- assert not os.path.exists(f"{tmpdir}/a.txt")
-
- fs.get("/a.txt", f"{tmpdir}/a.txt")
-
- assert os.path.exists(f"{tmpdir}/a.txt")
-
- fs.rm("/a.txt")
-
- assert fs.listdir("/") == ["checkpoints"]
- fs.rm("/checkpoints/a.txt")
- assert fs.listdir("/") == ["checkpoints"]
- assert fs.walk("/checkpoints") == []
- fs.rm("/checkpoints/")
- assert fs.listdir("/") == []
-
- with pytest.raises(FileExistsError, match="HERE"):
- fs.put("HERE", "/HERE")
-
- with pytest.raises(RuntimeError, match="The provided path"):
- fs.listdir("/space")
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="TODO: Add support for windows")
-def test_filesystem_root(tmpdir):
- fs = FileSystem()
-
- with open(f"{tmpdir}/a.txt", "w") as f:
- f.write("example")
-
- os.makedirs(f"{tmpdir}/checkpoints", exist_ok=True)
- with open(f"{tmpdir}/checkpoints/a.txt", "w") as f:
- f.write("example")
-
- assert fs.listdir("/") == []
- fs.put(f"{tmpdir}/a.txt", "/")
- fs.put(f"{tmpdir}/checkpoints", "/")
- assert fs.listdir("/") == ["a.txt", "checkpoints"]
diff --git a/tests/tests_app/storage/test_mount.py b/tests/tests_app/storage/test_mount.py
deleted file mode 100644
index ab4802c76aecf..0000000000000
--- a/tests/tests_app/storage/test_mount.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import pytest
-from lightning.app.storage.mount import Mount
-
-
-def test_create_s3_mount_successfully():
- mount = Mount(source="s3://foo/bar/", mount_path="/foo")
- assert mount.source == "s3://foo/bar/"
- assert mount.mount_path == "/foo"
- assert mount.protocol == "s3://"
-
-
-def test_create_non_s3_mount_fails():
- with pytest.raises(ValueError, match="Unknown protocol for the mount 'source' argument"):
- Mount(source="foo/bar/", mount_path="/foo")
-
- with pytest.raises(ValueError, match="Unknown protocol for the mount 'source' argument"):
- Mount(source="gcs://foo/bar/", mount_path="/foo")
-
- with pytest.raises(ValueError, match="Unknown protocol for the mount 'source' argument"):
- Mount(source="3://foo/bar/", mount_path="/foo")
-
-
-def test_create_s3_mount_without_directory_prefix_fails():
- with pytest.raises(ValueError, match="S3 mounts must end in a trailing slash"):
- Mount(source="s3://foo/bar", mount_path="/foo")
-
- with pytest.raises(ValueError, match="S3 mounts must end in a trailing slash"):
- Mount(source="s3://foo", mount_path="/foo")
-
-
-def test_create_mount_without_mount_path_argument():
- m = Mount(source="s3://foo/")
- assert m.mount_path == "/data/foo"
-
- m = Mount(source="s3://foo/bar/")
- assert m.mount_path == "/data/bar"
-
-
-def test_create_mount_path_with_relative_path_errors():
- with pytest.raises(ValueError, match="mount_path argument must be an absolute path"):
- Mount(source="s3://foo/", mount_path="./doesnotwork")
diff --git a/tests/tests_app/storage/test_orchestrator.py b/tests/tests_app/storage/test_orchestrator.py
deleted file mode 100644
index 41a8d0191098b..0000000000000
--- a/tests/tests_app/storage/test_orchestrator.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from unittest.mock import MagicMock
-
-from lightning.app.storage.orchestrator import StorageOrchestrator
-from lightning.app.storage.requests import _GetRequest, _GetResponse
-from lightning.app.testing.helpers import _MockQueue
-from lightning.app.utilities.enum import WorkStageStatus
-
-
-def test_orchestrator():
- """Simulate orchestration when Work B requests a file from Work A."""
- request_queues = {"work_a": _MockQueue(), "work_b": _MockQueue()}
- response_queues = {"work_a": _MockQueue(), "work_b": _MockQueue()}
- copy_request_queues = {"work_a": _MockQueue(), "work_b": _MockQueue()}
- copy_response_queues = {"work_a": _MockQueue(), "work_b": _MockQueue()}
- app = MagicMock()
- work = MagicMock()
- work.status.stage = WorkStageStatus.RUNNING
- app.get_component_by_name = MagicMock(return_value=work)
-
- orchestrator = StorageOrchestrator(
- app,
- request_queues=request_queues,
- response_queues=response_queues,
- copy_request_queues=copy_request_queues,
- copy_response_queues=copy_response_queues,
- )
-
- # test idle behavior when queues are empty
- orchestrator.run_once("work_a")
- orchestrator.run_once("work_b")
- assert not orchestrator.waiting_for_response
-
- # simulate Work B sending a request for a file in Work A
- request = _GetRequest(source="work_a", path="/a/b/c.txt", hash="", destination="", name="")
- request_queues["work_b"].put(request)
- orchestrator.run_once("work_a")
- assert not orchestrator.waiting_for_response
- orchestrator.run_once("work_b")
-
- # orchestrator is now waiting for a response for copier in Work A
- assert "work_b" in orchestrator.waiting_for_response
- assert len(request_queues["work_a"]) == 0
- assert request in copy_request_queues["work_a"]
- assert request.destination == "work_b"
-
- # simulate loop while waiting for new elements in the queues
- orchestrator.run_once("work_a")
- orchestrator.run_once("work_b")
-
- # edge case: `None` requests on the queue get ignored
- # TODO: Investigate how `None` values end up in the queue
- request_queues["work_a"].put(None)
- orchestrator.run_once("work_a")
- orchestrator.run_once("work_b")
- assert not request_queues["work_a"]._queue
-
- # simulate copier A confirms that the file is available on the shared volume
- response = _GetResponse(source="work_a", path="/a/b/c.txt", hash="", destination="work_b", name="")
- copy_request_queues["work_a"].get()
- copy_response_queues["work_a"].put(response)
-
- # orchestrator processes confirmation and confirms to the pending request from Work B
- orchestrator.run_once("work_a")
- assert len(copy_response_queues["work_a"]) == 0
- assert response in response_queues["work_b"]
- assert not orchestrator.waiting_for_response
- orchestrator.run_once("work_b")
-
- # simulate loop while waiting for new elements in the queues
- orchestrator.run_once("work_a")
- orchestrator.run_once("work_b")
- assert not orchestrator.waiting_for_response
-
- # simulate Work B receiving the confirmation that the file was copied
- response = response_queues["work_b"].get()
- assert response.source == "work_a"
- assert response.destination == "work_b"
- assert response.exception is None
-
- # all queues should be empty
- assert all(len(queue) == 0 for queue in request_queues.values())
- assert all(len(queue) == 0 for queue in response_queues.values())
- assert all(len(queue) == 0 for queue in copy_request_queues.values())
- assert all(len(queue) == 0 for queue in copy_response_queues.values())
diff --git a/tests/tests_app/storage/test_path.py b/tests/tests_app/storage/test_path.py
deleted file mode 100644
index 5156dcb16743d..0000000000000
--- a/tests/tests_app/storage/test_path.py
+++ /dev/null
@@ -1,725 +0,0 @@
-import json
-import os
-import pathlib
-import pickle
-import sys
-from re import escape
-from time import sleep
-from unittest import TestCase, mock
-from unittest.mock import MagicMock, Mock
-
-import pytest
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.path import (
- Path,
- _artifacts_path,
- _filesystem,
- _is_lit_path,
- _shared_storage_path,
- _storage_root_dir,
-)
-from lightning.app.storage.requests import _ExistsResponse, _GetResponse
-from lightning.app.testing.helpers import EmptyWork, _MockQueue, _RunIf
-from lightning.app.utilities.app_helpers import LightningJSONEncoder
-from lightning.app.utilities.component import _context
-from lightning.app.utilities.imports import _IS_WINDOWS, _is_s3fs_available
-
-
-def test_path_instantiation():
- assert Path() == pathlib.Path()
- assert Path("a/b") == pathlib.Path("a/b")
- assert Path("a", "b") == pathlib.Path("a", "b")
- assert Path(pathlib.Path("a"), pathlib.Path("b")) == pathlib.Path("a/b")
- assert Path(Path(Path("a/b"))) == pathlib.Path("a/b")
-
- path = Path()
- assert path._origin is path._consumer is path._request_queue is path._response_queue is None
-
- folder = Path("x/y/z")
- folder._origin = "origin"
- folder._consumer = "consumer"
-
- # from parts where the first is a Lightning Path and the other(s) are string
- file = Path(folder, "file.txt")
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
- # from parts that are instance of Path and have no origin
- file = Path(folder, Path("file.txt"))
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
- # from parts that are instance of Path and have a different origin than the top folder
- filename = Path("file.txt")
- filename._origin = "different"
- with pytest.raises(TypeError, match="Tried to instantiate a Lightning Path from multiple other Paths"):
- Path(folder, filename)
-
- # from parts that are instance of Path and have the SAME origin as the top folder
- filename = Path("file.txt")
- filename._origin = "origin"
- file = Path(folder, filename)
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
-
-def test_path_instantiation_lit():
- assert Path("lit://") == _storage_root_dir()
- assert Path("lit://a/b") == pathlib.Path(_storage_root_dir(), "a/b")
- assert Path("lit://", "a", "b") == pathlib.Path(_storage_root_dir(), "a", "b")
- assert Path("lit://", pathlib.Path("a"), pathlib.Path("b")) == pathlib.Path(_storage_root_dir(), "a/b")
- assert Path(Path(Path("lit://a/b"))) == pathlib.Path(_storage_root_dir(), "a", "b")
- assert str(Path("lit://lit-path")) == os.path.join(_storage_root_dir(), "lit-path")
-
-
-def test_is_lit_path():
- assert not _is_lit_path("lit")
- assert not _is_lit_path(Path("lit"))
- assert _is_lit_path("lit://")
- assert _is_lit_path(Path("lit://"))
- assert _is_lit_path("lit://a/b/c")
- assert _is_lit_path(Path("lit://a/b/c"))
- assert _is_lit_path(_storage_root_dir())
-
-
-def test_path_copy():
- """Test that Path creates an exact copy when passing a Path instance to the constructor."""
- path = Path("x/y/z")
- path._origin = "origin"
- path._consumer = "consumer"
- path._request_queue = Mock()
- path._response_queue = Mock()
- path_copy = Path(path)
- assert path_copy._origin == path._origin
- assert path_copy._consumer == path._consumer
- assert path_copy._request_queue == path._request_queue
- assert path_copy._response_queue == path._response_queue
-
-
-def test_path_inheritance():
- """Test that the Lightning Path is a drop-in replacement for pathlib.Path without compromises."""
- file = Path("file.txt")
- pathlibfile = pathlib.Path("file.txt")
- assert file == pathlibfile
- assert isinstance(file, Path)
- assert isinstance(file, pathlib.Path)
-
- folder = Path("./x/y")
- file = folder / "file.txt"
- assert isinstance(file, Path)
-
- file.with_suffix(".png")
- assert isinstance(file, Path)
-
-
-def test_path_concatenation():
- """Test that path concatentaions keep the properties of the paths on the right-hand side of the join."""
- folder = Path("x/y/z")
- folder._origin = "origin"
- folder._consumer = "consumer"
- other = Path("other")
-
- # test __truediv__ when Path is on the left-hand side
- file = folder / other / "more" / "file.txt"
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
- # test __rtruediv__ when Path is on the right-hand side
- switched = pathlib.Path("/") / folder
- assert isinstance(switched, Path)
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
-
-def test_path_with_replacement():
- """Test that the ``Path.with_*`` modifiers keep the properties."""
- folder = Path("x", "y", "z")
- folder._origin = "origin"
- folder._consumer = "consumer"
-
- # with_name
- file = folder.with_name("file.txt")
- assert str(file) == os.path.join("x", "y", "file.txt")
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
- # with_suffix
- file = file.with_suffix(".png")
- assert str(file) == os.path.join("x", "y", "file.png")
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
- # relative_to
- rel_path = folder.relative_to("x")
- assert str(rel_path) == os.path.join("y", "z")
- assert rel_path._origin == "origin"
- assert rel_path._consumer == "consumer"
-
-
-@_RunIf(min_python="3.9")
-def test_path_with_stem_replacement():
- """Test that the ``Path.with_stem`` modifier keep the properties.
-
- This is only available in Python 3.9+.
-
- """
- file = Path("x", "y", "file.txt")
- file._origin = "origin"
- file._consumer = "consumer"
- file = file.with_stem("text")
- assert str(file) == os.path.join("x", "y", "text.txt")
- assert file._origin == "origin"
- assert file._consumer == "consumer"
-
-
-def test_path_parents():
- """Test that the ``Path.parent`` and ``Path.parent`` properties return Paths that inherit the origin and consumer
- attributes."""
- path = Path("a", "b", "c", "d")
- path._origin = "origin"
- path._consumer = "consumer"
-
- # .parent
- assert isinstance(path.parent, Path)
- assert str(path.parent) == os.path.join("a", "b", "c")
- assert path.parent._origin == "origin"
- assert path.parent._consumer == "consumer"
-
- # .parents
- assert path.parents == [Path("a", "b", "c"), Path("a", "b"), Path("a"), Path(".")]
- assert all(parent._origin == "origin" for parent in path.parents)
- assert all(parent._consumer == "consumer" for parent in path.parents)
-
-
-def test_path_hash():
- """Test that the value of the Path hash is a function of the path name and the origin."""
- # a path without origin has no hash
- assert Path("one").hash is Path("two").hash is None
-
- # identical paths with identical origins have the same hash
- path1 = Path("one")
- path2 = Path("one")
- path1._origin = "origin1"
- path1._consumer = "consumer1"
- path2._origin = "origin1"
- path1._consumer = "consumer2"
- assert path1.hash == path2.hash
-
- # identical paths with different origins have different hash
- path2._origin = "origin2"
- assert path1.hash != path2.hash
-
- # different paths but same owner yields a different hash
- path1 = Path("one")
- path2 = Path("other")
- path1._origin = "same"
- path2._origin = "same"
- assert path1.hash != path2.hash
-
-
-def test_path_pickleable():
- path = Path("a/b/c.txt")
- path._origin = "root.x.y.z"
- path._consumer = "root.p.q.r"
- path._request_queue = Mock()
- path._response_queue = Mock()
- loaded = pickle.loads(pickle.dumps(path))
- assert isinstance(loaded, Path)
- assert loaded == path
- assert loaded._origin == path._origin
- assert loaded._consumer == path._consumer
- assert loaded._request_queue is None
- assert loaded._response_queue is None
-
-
-def test_path_json_serializable():
- path = Path("a/b/c.txt")
- path._origin = "root.x.y.z"
- path._consumer = "root.p.q.r"
- path._request_queue = Mock()
- path._response_queue = Mock()
- json_dump = json.dumps(path, cls=LightningJSONEncoder)
- assert "path" in json_dump
- # the replacement of \ is needed for Windows paths
- assert str(path).replace("\\", "\\\\") in json_dump
- assert "origin_name" in json_dump
- assert path._origin in json_dump
- assert "consumer_name" in json_dump
- assert path._consumer in json_dump
-
-
-def test_path_to_dict_from_dict():
- path = Path("a/b/c.txt")
- path._origin = "root.x.y.z"
- path._consumer = "root.p.q.r"
- path._request_queue = Mock()
- path._response_queue = Mock()
- path_dict = path.to_dict()
- same_path = Path.from_dict(path_dict)
- assert same_path == path
- assert same_path._origin == path._origin
- assert same_path._consumer == path._consumer
- assert same_path._request_queue is None
- assert same_path._response_queue is None
- assert same_path._metadata == path._metadata
-
-
-def test_path_attach_work():
- """Test that attaching a path to a LighitningWork will make the Work either the origin or a consumer."""
- path = Path()
- assert path._origin is None
- work1 = EmptyWork()
- work2 = EmptyWork()
- work3 = EmptyWork()
- path._attach_work(work=work1)
- assert path._origin is work1
- # path already has an owner
- path._attach_work(work=work2)
- assert path._origin is work1
- assert path._consumer is work2
-
- # path gets a new consumer
- path._attach_work(work=work3)
- assert path._origin is work1
- assert path._consumer is work3
-
-
-def test_path_attach_queues():
- path = Path()
- request_queue = Mock()
- response_queue = Mock()
- path._attach_queues(request_queue=request_queue, response_queue=response_queue)
- assert path._request_queue is request_queue
- assert path._response_queue is response_queue
-
-
-@pytest.mark.parametrize("cls", [LightningFlow, LightningWork])
-def test_path_in_flow_and_work(cls, tmpdir):
- class PathComponent(cls):
- def __init__(self):
- super().__init__()
- self.path_one = Path("a", "b")
- self.path_one = Path("a", "b", "c")
- self.path_two = Path(tmpdir) / "write.txt"
-
- def run(self):
- self.path_one = self.path_one / "d.txt"
- assert self.path_one == Path("a", "b", "c", "d.txt")
- with open(self.path_two, "w") as file:
- file.write("Hello")
-
- class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.path_component = PathComponent()
-
- def run(self):
- self.path_component.run()
-
- root = RootFlow()
- _ = LightningApp(root) # create an app to convert all paths that got attached
-
- root.run()
-
- assert root.path_component.path_one == Path("a", "b", "c", "d.txt")
- assert root.path_component.path_one == pathlib.Path("a", "b", "c", "d.txt")
- if isinstance(root.path_component, LightningWork):
- assert root.path_component.path_one.origin_name == "root.path_component"
- assert root.path_component.path_one.consumer_name == "root.path_component"
- else:
- assert root.path_component.path_one._origin is None
- assert root.path_component.path_one._consumer is None
- with open(root.path_component.path_two) as fo:
- assert fo.readlines() == ["Hello"]
-
-
-class SourceWork(LightningWork):
- def __init__(self, tmpdir):
- super().__init__(cache_calls=True)
- self.path = Path(tmpdir, "src.txt")
- assert self.path.origin_name == ""
-
- def run(self):
- with open(self.path, "w") as f:
- f.write("Hello from SourceWork")
-
-
-class DestinationWork(LightningWork):
- def __init__(self, source_path):
- super().__init__(cache_calls=True)
- assert source_path.origin_name == "root.src_work"
- self.path = source_path
- assert self.path.origin_name == "root.src_work"
- self.other = Path("other")
- assert self.other.origin_name == ""
-
- def run(self):
- assert self.path.origin_name == "root.src_work"
- assert self.other.origin_name == "root.dst_work"
- # we are running locally, the file is already there (no transfer needed)
- self.path.get(overwrite=True)
- assert self.path.is_file()
- assert self.path.read_text() == "Hello from SourceWork"
-
-
-class SourceToDestFlow(LightningFlow):
- def __init__(self, tmpdir):
- super().__init__()
- self.src_work = SourceWork(tmpdir)
- self.dst_work = DestinationWork(self.src_work.path)
-
- def run(self):
- self.src_work.run()
- if self.src_work.has_succeeded:
- self.dst_work.run()
- if self.dst_work.has_succeeded:
- self.stop()
-
-
-@pytest.mark.skipif(sys.platform == "win32" or sys.platform == "darwin", reason="too slow on Windows or macOs")
-def test_multiprocess_path_in_work_and_flow(tmpdir):
- root = SourceToDestFlow(tmpdir)
- app = LightningApp(root, log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class DynamicSourceToDestFlow(LightningFlow):
- def __init__(self, tmpdir):
- super().__init__()
- self.tmpdir = str(tmpdir)
-
- def run(self):
- if not hasattr(self, "src_work"):
- self.src_work = SourceWork(self.tmpdir)
- self.src_work.run()
- if self.src_work.has_succeeded:
- if not hasattr(self, "dst_work"):
- self.dst_work = DestinationWork(self.src_work.path)
- self.dst_work.run()
- if hasattr(self, "dst_work") and self.dst_work.has_succeeded:
- self.stop()
-
-
-# FIXME(alecmerdler): This test is failing...
-@pytest.mark.skipif(_IS_WINDOWS, reason="strange TimeOut exception")
-@pytest.mark.xfail(strict=False, reason="hanging...")
-def test_multiprocess_path_in_work_and_flow_dynamic(tmpdir):
- root = DynamicSourceToDestFlow(tmpdir)
- app = LightningApp(root)
- MultiProcessRuntime(app).dispatch()
-
-
-class RunPathFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.src_work = PathSourceWork()
- self.run_work = RunPathWork(cache_calls=True)
-
- def run(self):
- self.src_work.run()
- assert self.src_work.src_path_0.origin_name == "root.src_work"
- assert self.src_work.src_path_0.consumer_name == "root.src_work"
-
- # local_path is not attached to any Work
- local_path_0 = Path("local", "file_0.txt")
- local_path_1 = Path("local", "file_1.txt")
- assert local_path_0.origin_name is None
- assert local_path_0.consumer_name is None
-
- nested_local_path = (99, {"nested": local_path_1})
- nested_kwarg_path = ["x", (self.src_work.src_path_1,)]
-
- # TODO: support returning a path from run()
- self.run_work.run(
- self.src_work.src_path_0,
- local_path_0,
- nested_local_path,
- kwarg_path=local_path_1,
- nested_kwarg_path=nested_kwarg_path,
- )
- sleep(1)
- self.stop()
-
-
-class PathSourceWork(EmptyWork):
- def __init__(self):
- super().__init__()
- self.src_path_0 = Path("src", "file_0.txt")
- self.src_path_1 = Path("src", "file_1.txt")
-
-
-class RunPathWork(LightningWork):
- def run(self, src_path_0, local_path_0, nested_local_path, kwarg_path=None, nested_kwarg_path=None):
- all_paths = []
-
- # src_path_0 has an origin which must be preserved, this work becomes consumer
- assert str(src_path_0) == os.path.join("src", "file_0.txt")
- assert src_path_0.origin_name == "root.src_work"
- all_paths.append(src_path_0)
-
- # local_path_0 had no origin, this work becomes both the origin and the consumer
- assert str(local_path_0) == os.path.join("local", "file_0.txt")
- assert local_path_0.origin_name is None
- assert local_path_0.consumer_name is None
- all_paths.append(local_path_0)
-
- # nested_local_path is a nested container that contains a Path
- assert str(nested_local_path[1]["nested"]) == os.path.join("local", "file_1.txt")
- assert nested_local_path[1]["nested"].origin_name is None
- assert nested_local_path[1]["nested"].consumer_name is None
- all_paths.append(nested_local_path[1]["nested"])
-
- # keywoard arguments can also contain Paths
- assert str(kwarg_path) == os.path.join("local", "file_1.txt")
- assert kwarg_path.origin_name is None
- assert kwarg_path.consumer_name is None
- all_paths.append(kwarg_path)
-
- assert str(nested_kwarg_path[1][0]) == os.path.join("src", "file_1.txt")
- assert nested_kwarg_path[1][0].origin_name == "root.src_work"
- all_paths.append(nested_kwarg_path[1][0])
-
- all(p._request_queue == self._request_queue for p in all_paths)
- all(p._response_queue == self._response_queue for p in all_paths)
- all(p.consumer_name == self.name == "root.run_work" for p in all_paths)
-
-
-def test_path_as_argument_to_run_method():
- """Test that Path objects can be passed as arguments to the run() method of a Work in various ways such that the
- origin, consumer and queues get automatically attached."""
- root = RunPathFlow()
- app = LightningApp(root)
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-def test_path_get_errors(tmpdir):
- with _context("work"):
- with pytest.raises(
- RuntimeError, match="Trying to get the file .* but the path is not attached to a LightningApp"
- ):
- Path().get()
-
- with pytest.raises(
- RuntimeError, match="Trying to get the file .* but the path is not attached to a LightningWork"
- ):
- path = Path()
- path._attach_queues(Mock(), Mock())
- path.get()
-
- with pytest.raises(FileExistsError, match="The file or folder .* exists locally. Pass `overwrite=True"):
- path = Path(tmpdir)
- path._attach_queues(Mock(), Mock())
- path._attach_work(Mock())
- path.get()
-
-
-class SourceOverwriteWork(LightningWork):
- def __init__(self, tmpdir):
- super().__init__(raise_exception=True)
- self.path = Path(tmpdir, "folder")
-
- def run(self):
- self.path.mkdir(parents=True, exist_ok=True)
- (self.path / "file.txt").touch()
- assert self.path.exists_local()
-
-
-class DestinationOverwriteWork(LightningWork):
- def __init__(self, source_path):
- super().__init__(raise_exception=True)
- self.path = source_path
-
- def run(self):
- assert self.path.exists()
- with mock.patch("lightning.app.storage.path.shutil") as shutil_mock:
- self.path.get(overwrite=True)
- shutil_mock.rmtree.assert_called_with(self.path)
- assert self.path.exists()
- assert (self.path / "file.txt").exists()
-
-
-class OverwriteFolderFlow(LightningFlow):
- def __init__(self, tmpdir):
- super().__init__()
- self.src_work = SourceOverwriteWork(tmpdir)
- self.dst_work = DestinationOverwriteWork(self.src_work.path)
-
- def run(self):
- self.src_work.run()
- if self.src_work.has_succeeded:
- self.dst_work.run()
- if self.dst_work.has_succeeded:
- self.stop()
-
-
-@pytest.mark.skipif(True, reason="depreceated")
-def test_path_get_overwrite(tmpdir):
- """Test that .get(overwrite=True) overwrites the entire directory and replaces all files."""
- root = OverwriteFolderFlow(tmpdir)
- app = LightningApp(root, log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-def test_path_get_error_in_flow_context():
- with pytest.raises(RuntimeError, match=escape("`Path.get()` can only be called from within the `run()`")), _context(
- "flow"
- ):
- Path().get()
-
-
-def test_path_response_with_exception(tmpdir):
- request_queue = _MockQueue()
- response_queue = _MockQueue()
- path = Path(tmpdir / "file.txt")
- path._attach_queues(request_queue, response_queue)
- path._origin = "origin"
- path._consumer = "consumer"
-
- # simulate that a response will come with an exception raised
- response_queue.put(
- _GetResponse(
- source="origin",
- path=str(tmpdir / "file.txt"),
- hash=path.hash,
- destination="consumer",
- exception=OSError("Something went wrong"),
- name="",
- )
- )
-
- with pytest.raises(
- RuntimeError, match="An exception was raised while trying to transfer the contents at"
- ), _context("work"):
- path.get()
-
-
-def test_path_response_not_matching_reqeuest(tmpdir):
- request_queue = _MockQueue()
- response_queue = _MockQueue()
- path = Path(tmpdir / "file.txt")
- path._attach_queues(request_queue, response_queue)
- path._origin = "origin"
- path._consumer = "consumer"
-
- # simulate a response that has a different owner than the request had
- response = _GetResponse(
- source="other_origin", path=str(tmpdir / "file.txt"), hash=path.hash, destination="consumer", name=""
- )
-
- response_queue.put(response)
- with pytest.raises(
- RuntimeError, match="Tried to get the file .* but received a response for a request it did not send."
- ):
- path.get()
-
- # simulate a response that has a different hash than the request had
- assert len(response_queue) == 0
- response.path = str(path)
- response.hash = "other_hash"
- response_queue.put(response)
- with pytest.raises(
- RuntimeError, match="Tried to get the file .* but received a response for a request it did not send."
- ):
- path.get()
-
-
-def test_path_exists(tmpdir):
- """Test that the Path.exists() behaves as expected: First it should check if the file exists locally, and if not,
- send a message to the orchestrator to eventually check the existenc on the origin Work."""
- # Local Path (no Work queues attached)
- assert not Path("file").exists()
- assert Path(tmpdir).exists()
- with open(tmpdir / "file", "w"):
- assert Path(tmpdir / "file").exists()
-
- # A local path that exists
- path = Path(tmpdir)
- path.exists_remote = Mock()
- path.exists_local = Mock(return_value=True)
- assert path.exists() is True
- path.exists_local.assert_called_once()
- path.exists_remote.assert_not_called() # don't check remotely
-
- # A local path that does not exist, but has no Work attached
- path = Path("not-exists.txt")
- path.exists_local = Mock(return_value=False)
- path.exists_remote = Mock()
- assert not path.exists()
- path.exists_local.assert_called_once()
- path.exists_remote.assert_not_called() # don't check remotely
-
- # A local path that does not exist, but it exists remotely
- path = Path("exists-remotely-only.txt")
- path.exists_local = Mock(return_value=False)
- path.exists_remote = Mock(return_value=True)
- path._origin = "origin"
- assert path.exists()
- path.exists_local.assert_called_once()
- path.exists_remote.assert_called_once() # check remotely
-
-
-def test_path_exists_local(tmpdir):
- assert not Path("file").exists_local()
- assert Path(tmpdir).exists_local()
- with open(tmpdir / "file", "w"):
- assert Path(tmpdir / "file").exists_local()
-
-
-def test_path_exists_remote(tmpdir):
- path = Path(tmpdir / "not-attached.txt")
- with pytest.raises(RuntimeError, match="the path is not attached to a LightningWork"):
- path.exists_remote()
-
- # If Path does not exist locally, ask the orchestrator
- request_queue = _MockQueue()
- response_queue = _MockQueue()
- path = Path(tmpdir / "not-exists.txt")
- path._attach_queues(request_queue, response_queue)
- path._origin = "origin"
- path._consumer = "consumer"
-
- # Put the response into the queue to simulate the orchestrator responding
- response_queue.put(_ExistsResponse(source=path.origin_name, path=str(path), name="", hash="123", exists=False))
- assert not path.exists_remote()
- assert request_queue.get()
-
- response_queue.put(_ExistsResponse(source=path.origin_name, path=str(path), name="", hash="123", exists=True))
- assert path.exists_remote()
- assert request_queue.get()
-
-
-def test_artifacts_path():
- work = Mock()
- work.name = "root.flow.work"
- assert _artifacts_path(work) == _shared_storage_path() / "artifacts" / "root.flow.work"
-
-
-@pytest.mark.skipif(not _is_s3fs_available(), reason="This test requires s3fs.")
-@mock.patch.dict(os.environ, {"LIGHTNING_BUCKET_ENDPOINT_URL": "a"})
-@mock.patch.dict(os.environ, {"LIGHTNING_BUCKET_NAME": "b"})
-@mock.patch.dict(os.environ, {"LIGHTNING_CLOUD_APP_ID": "e"})
-def test_filesystem(monkeypatch):
- from lightning.app.storage import path
-
- mock = MagicMock()
- monkeypatch.setattr(path, "S3FileSystem", mock)
- fs = _filesystem()
- assert fs == mock()
-
-
-class TestSharedStoragePath(TestCase):
- @mock.patch.dict(os.environ, {"LIGHTNING_STORAGE_PATH": "test-bucket/lightningapps/test-project/test-app"})
- def test_shared_storage_path_storage_path_set(self):
- assert pathlib.Path("test-bucket/lightningapps/test-project/test-app") == _shared_storage_path()
-
- @mock.patch.dict(os.environ, {"LIGHTNING_CLOUD_APP_ID": "test-app", "LIGHTNING_BUCKET_NAME": "test-bucket"})
- def test_shared_storage_path_bucket_and_app_id_set(self):
- assert pathlib.Path("test-bucket/lightningapps/test-app") == _shared_storage_path()
-
- @mock.patch.dict(os.environ, {"SHARED_MOUNT_DIRECTORY": "test-app/.shared"})
- def test_shared_storage_path_mount_directory_set(self):
- assert _shared_storage_path().match("*/test-app/.shared")
-
- def test_shared_storage_path_no_envvars_set(self):
- assert _shared_storage_path().match("*/.shared")
diff --git a/tests/tests_app/storage/test_payload.py b/tests/tests_app/storage/test_payload.py
deleted file mode 100644
index 453cead1c6583..0000000000000
--- a/tests/tests_app/storage/test_payload.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import os
-import pathlib
-import pickle
-from copy import deepcopy
-from unittest import mock
-from unittest.mock import Mock
-
-import pytest
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.runners.multiprocess import MultiProcessRuntime
-from lightning.app.storage.payload import Payload
-from lightning.app.storage.requests import _GetRequest
-
-
-def test_payload_copy():
- """Test that Payload creates an exact copy when passing a Payload instance to the constructor."""
- payload = Payload(None)
- payload._origin = "origin"
- payload._consumer = "consumer"
- payload._request_queue = "MockQueue"
- payload._response_queue = "MockQueue"
- payload_copy = deepcopy(payload)
- assert payload_copy._origin == payload._origin
- assert payload_copy._consumer == payload._consumer
- assert payload_copy._request_queue == payload._request_queue
- assert payload_copy._response_queue == payload._response_queue
-
-
-def test_payload_pickable():
- payload = Payload("MyObject")
- payload._origin = "root.x.y.z"
- payload._consumer = "root.p.q.r"
- payload._name = "var_a"
- loaded = pickle.loads(pickle.dumps(payload))
-
- assert isinstance(loaded, Payload)
- assert loaded._origin == payload._origin
- assert loaded._consumer == payload._consumer
- assert loaded._name == payload._name
- assert loaded._request_queue is None
- assert loaded._response_queue is None
-
-
-def test_path_attach_queues():
- path = Payload(None)
- request_queue = Mock()
- response_queue = Mock()
- path._attach_queues(request_queue=request_queue, response_queue=response_queue)
- assert path._request_queue is request_queue
- assert path._response_queue is response_queue
-
-
-class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.var_a = Payload(None)
-
- def run(self):
- pass
-
-
-def test_payload_in_init():
- with pytest.raises(
- AttributeError, match="The Payload object should be set only within the run method of the work."
- ):
- Work()
-
-
-class WorkRun(LightningWork):
- def __init__(self, tmpdir):
- super().__init__()
- self.var_a = None
- self.tmpdir = tmpdir
-
- def run(self):
- self.var_a = Payload("something")
- assert self.var_a.name == "var_a"
- assert self.var_a._origin == "root.a"
- assert self.var_a.hash == "9bd514ad51fc33d895c50657acd0f0582301cf3e"
- source_path = pathlib.Path(self.tmpdir, self.var_a.name)
- assert not source_path.exists()
- response = self.var_a._handle_get_request(
- self,
- _GetRequest(
- name="var_a",
- hash=self.var_a.hash,
- source="root.a",
- path=str(source_path),
- destination="root",
- ),
- )
- assert source_path.exists()
- assert self.var_a.load(str(source_path)) == "something"
- assert not response.exception
-
-
-def test_payload_in_run(tmpdir):
- work = WorkRun(str(tmpdir))
- work._name = "root.a"
- work.run()
-
-
-class Sender(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.value_all = None
- self.value_b = None
- self.value_c = None
-
- def run(self):
- self.value_all = Payload(["A", "B", "C"])
- self.value_b = Payload("B")
- self.value_c = Payload("C")
-
-
-class WorkReceive(LightningWork):
- def __init__(self, expected):
- super().__init__(parallel=True)
- self.expected = expected
-
- def run(self, generated):
- assert generated.value == self.expected
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.sender = Sender()
- self.receiver_all = WorkReceive(["A", "B", "C"])
- self.receiver_b = WorkReceive("B")
- self.receiver_c = WorkReceive("C")
-
- def run(self):
- self.sender.run()
- if self.sender.value_all:
- self.receiver_all.run(self.sender.value_all)
- if self.sender.value_b:
- self.receiver_b.run(self.sender.value_b)
- if self.sender.value_c:
- self.receiver_c.run(self.sender.value_c)
- if self.receiver_all.has_succeeded and self.receiver_b.has_succeeded and self.receiver_c.has_succeeded:
- self.stop()
-
-
-@pytest.mark.xfail(strict=False, reason="flaky")
-def test_payload_works(tmpdir):
- """This tests validates the payload api can be used to transfer return values from a work to another."""
- with mock.patch("lightning.app.storage.path._storage_root_dir", return_value=pathlib.Path(tmpdir)):
- app = LightningApp(Flow(), log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- os.remove("value_all")
- os.remove("value_b")
- os.remove("value_c")
diff --git a/tests/tests_app/structures/__init__.py b/tests/tests_app/structures/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/structures/test_structures.py b/tests/tests_app/structures/test_structures.py
deleted file mode 100644
index cd6fc7ae9571f..0000000000000
--- a/tests/tests_app/structures/test_structures.py
+++ /dev/null
@@ -1,563 +0,0 @@
-import os
-from copy import deepcopy
-
-import pytest
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage.payload import Payload
-from lightning.app.structures import Dict, List
-from lightning.app.testing.helpers import EmptyFlow
-from lightning.app.utilities.enum import CacheCallsKeys, WorkStageStatus
-
-
-def test_dict():
- class WorkA(LightningWork):
- def __init__(self):
- super().__init__(port=1)
- self.c = 0
-
- def run(self):
- pass
-
- class A(LightningFlow):
- def __init__(self):
- super().__init__()
- self.dict = Dict(**{"work_a": WorkA(), "work_b": WorkA(), "work_c": WorkA(), "work_d": WorkA()})
-
- def run(self):
- pass
-
- flow = A()
-
- # TODO: these assertions are wrong, the works are getting added under "flows" instead of "works"
- # state
- assert len(flow.state["structures"]["dict"]["works"]) == len(flow.dict) == 4
- assert list(flow.state["structures"]["dict"]["works"].keys()) == ["work_a", "work_b", "work_c", "work_d"]
- assert all(
- flow.state["structures"]["dict"]["works"][f"work_{k}"]["vars"]
- == {
- "c": 0,
- "_url": "",
- "_future_url": "",
- "_port": 1,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_display_name": "",
- "_internal_ip": "",
- "_public_ip": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- }
- for k in ("a", "b", "c", "d")
- )
- assert all(
- flow.state["structures"]["dict"]["works"][f"work_{k}"]["calls"] == {CacheCallsKeys.LATEST_CALL_HASH: None}
- for k in ("a", "b", "c", "d")
- )
- assert all(flow.state["structures"]["dict"]["works"][f"work_{k}"]["changes"] == {} for k in ("a", "b", "c", "d"))
-
- # state_vars
- assert len(flow.state_vars["structures"]["dict"]["works"]) == len(flow.dict) == 4
- assert list(flow.state_vars["structures"]["dict"]["works"].keys()) == ["work_a", "work_b", "work_c", "work_d"]
- assert all(
- flow.state_vars["structures"]["dict"]["works"][f"work_{k}"]["vars"]
- == {
- "c": 0,
- "_url": "",
- "_future_url": "",
- "_port": 1,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_display_name": "",
- "_internal_ip": "",
- "_public_ip": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- }
- for k in ("a", "b", "c", "d")
- )
-
- # state_with_changes
- assert len(flow.state_with_changes["structures"]["dict"]["works"]) == len(flow.dict) == 4
- assert list(flow.state_with_changes["structures"]["dict"]["works"].keys()) == [
- "work_a",
- "work_b",
- "work_c",
- "work_d",
- ]
- assert all(
- flow.state_with_changes["structures"]["dict"]["works"][f"work_{k}"]["vars"]
- == {
- "c": 0,
- "_url": "",
- "_future_url": "",
- "_port": 1,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_display_name": "",
- "_internal_ip": "",
- "_public_ip": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- }
- for k in ("a", "b", "c", "d")
- )
- assert all(
- flow.state_with_changes["structures"]["dict"]["works"][f"work_{k}"]["calls"]
- == {CacheCallsKeys.LATEST_CALL_HASH: None}
- for k in ("a", "b", "c", "d")
- )
- assert all(
- flow.state_with_changes["structures"]["dict"]["works"][f"work_{k}"]["changes"] == {}
- for k in ("a", "b", "c", "d")
- )
-
- # set_state
- state = deepcopy(flow.state)
- state["structures"]["dict"]["works"]["work_b"]["vars"]["c"] = 1
- flow.set_state(state)
- assert flow.dict["work_b"].c == 1
-
-
-def test_dict_name():
- d = Dict(a=EmptyFlow(), b=EmptyFlow())
- assert d.name == "root"
- assert d["a"].name == "root.a"
- assert d["b"].name == "root.b"
-
- class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.dict = Dict(x=EmptyFlow(), y=EmptyFlow())
-
- def run(self):
- pass
-
- root = RootFlow()
- assert root.name == "root"
- assert root.dict.name == "root.dict"
- assert root.dict["x"].name == "root.dict.x"
- assert root.dict["y"].name == "root.dict.y"
-
-
-def test_list():
- class WorkA(LightningWork):
- def __init__(self):
- super().__init__(port=1)
- self.c = 0
-
- def run(self):
- pass
-
- class A(LightningFlow):
- def __init__(self):
- super().__init__()
- self.list = List(WorkA(), WorkA(), WorkA(), WorkA())
-
- def run(self):
- pass
-
- flow = A()
-
- # TODO: these assertions are wrong, the works are getting added under "flows" instead of "works"
- # state
- assert len(flow.state["structures"]["list"]["works"]) == len(flow.list) == 4
- assert list(flow.state["structures"]["list"]["works"].keys()) == ["0", "1", "2", "3"]
- assert all(
- flow.state["structures"]["list"]["works"][str(i)]["vars"]
- == {
- "c": 0,
- "_url": "",
- "_future_url": "",
- "_port": 1,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- }
- for i in range(4)
- )
- assert all(
- flow.state["structures"]["list"]["works"][str(i)]["calls"] == {CacheCallsKeys.LATEST_CALL_HASH: None}
- for i in range(4)
- )
- assert all(flow.state["structures"]["list"]["works"][str(i)]["changes"] == {} for i in range(4))
-
- # state_vars
- assert len(flow.state_vars["structures"]["list"]["works"]) == len(flow.list) == 4
- assert list(flow.state_vars["structures"]["list"]["works"].keys()) == ["0", "1", "2", "3"]
- assert all(
- flow.state_vars["structures"]["list"]["works"][str(i)]["vars"]
- == {
- "c": 0,
- "_url": "",
- "_future_url": "",
- "_port": 1,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- }
- for i in range(4)
- )
-
- # state_with_changes
- assert len(flow.state_with_changes["structures"]["list"]["works"]) == len(flow.list) == 4
- assert list(flow.state_with_changes["structures"]["list"]["works"].keys()) == ["0", "1", "2", "3"]
- assert all(
- flow.state_with_changes["structures"]["list"]["works"][str(i)]["vars"]
- == {
- "c": 0,
- "_url": "",
- "_future_url": "",
- "_port": 1,
- "_host": "127.0.0.1",
- "_paths": {},
- "_restarting": False,
- "_internal_ip": "",
- "_public_ip": "",
- "_display_name": "",
- "_cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "mounts": None,
- "shm_size": 0,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- }
- for i in range(4)
- )
- assert all(
- flow.state_with_changes["structures"]["list"]["works"][str(i)]["calls"]
- == {CacheCallsKeys.LATEST_CALL_HASH: None}
- for i in range(4)
- )
- assert all(flow.state_with_changes["structures"]["list"]["works"][str(i)]["changes"] == {} for i in range(4))
-
- # set_state
- state = deepcopy(flow.state)
- state["structures"]["list"]["works"]["0"]["vars"]["c"] = 1
- flow.set_state(state)
- assert flow.list[0].c == 1
-
-
-def test_list_name():
- lst = List(EmptyFlow(), EmptyFlow())
- assert lst.name == "root"
- assert lst[0].name == "root.0"
- assert lst[1].name == "root.1"
-
- class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.list = List(EmptyFlow(), EmptyFlow())
-
- def run(self):
- pass
-
- root = RootFlow()
- assert root.name == "root"
- assert root.list.name == "root.list"
- assert root.list[0].name == "root.list.0"
- assert root.list[1].name == "root.list.1"
-
-
-class CounterWork(LightningWork):
- def __init__(self, cache_calls, parallel=False):
- super().__init__(cache_calls=cache_calls, parallel=parallel)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-@pytest.mark.skipif(True, reason="out-dated")
-@pytest.mark.xfail(strict=False, reason="tchaton: Resolve this test.")
-@pytest.mark.parametrize("run_once_iterable", [False, True])
-@pytest.mark.parametrize("cache_calls", [False, True])
-@pytest.mark.parametrize("use_list", [False, True])
-def test_structure_with_iterate_and_fault_tolerance(run_once_iterable, cache_calls, use_list):
- class DummyFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- pass
-
- class RootFlow(LightningFlow):
- def __init__(self, use_list, run_once_iterable, cache_calls):
- super().__init__()
- self.looping = 0
- self.run_once_iterable = run_once_iterable
- self.restarting = False
- if use_list:
- self.iter = List(
- CounterWork(cache_calls),
- CounterWork(cache_calls),
- CounterWork(cache_calls),
- CounterWork(cache_calls),
- DummyFlow(),
- )
- else:
- self.iter = Dict(**{
- "0": CounterWork(cache_calls),
- "1": CounterWork(cache_calls),
- "2": CounterWork(cache_calls),
- "3": CounterWork(cache_calls),
- "4": DummyFlow(),
- })
-
- def run(self):
- for work_idx, work in self.experimental_iterate(enumerate(self.iter), run_once=self.run_once_iterable):
- if not self.restarting and work_idx == 1:
- # gives time to the delta to be sent.
- self.stop()
- if isinstance(work, str) and isinstance(self.iter, Dict):
- work = self.iter[work]
- work.run()
- if self.looping > 0:
- self.stop()
- self.looping += 1
-
- app = LightningApp(RootFlow(use_list, run_once_iterable, cache_calls))
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.root.iter[0 if use_list else "0"].counter == 1
- assert app.root.iter[1 if use_list else "1"].counter == 0
- assert app.root.iter[2 if use_list else "2"].counter == 0
- assert app.root.iter[3 if use_list else "3"].counter == 0
-
- app = LightningApp(RootFlow(use_list, run_once_iterable, cache_calls))
- app.root.restarting = True
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- expected_value = 1 if run_once_iterable else 1 if cache_calls else 2
- assert app.root.iter[0 if use_list else "0"].counter == expected_value
- assert app.root.iter[1 if use_list else "1"].counter == expected_value
- assert app.root.iter[2 if use_list else "2"].counter == expected_value
- assert app.root.iter[3 if use_list else "3"].counter == expected_value
-
-
-class CheckpointCounter(LightningWork):
- def __init__(self):
- super().__init__(cache_calls=False)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class CheckpointFlow(LightningFlow):
- def __init__(self, collection, depth=0, exit=11):
- super().__init__()
- self.depth = depth
- self.exit = exit
- if depth == 0:
- self.counter = 0
-
- if depth >= 4:
- self.collection = collection
- else:
- self.flow = CheckpointFlow(collection, depth + 1)
-
- def run(self):
- if hasattr(self, "counter"):
- self.counter += 1
- if self.counter >= self.exit:
- self.stop()
- if self.depth >= 4:
- self.collection.run()
- else:
- self.flow.run()
-
-
-class SimpleCounterWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class FlowDict(LightningFlow):
- def __init__(self):
- super().__init__()
- self.dict = Dict()
-
- def run(self):
- if "w" not in self.dict:
- self.dict["w"] = SimpleCounterWork()
-
- if self.dict["w"].status.stage == WorkStageStatus.SUCCEEDED:
- self.stop()
-
- self.dict["w"].run()
-
-
-def test_dict_with_queues():
- app = LightningApp(FlowDict())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class FlowList(LightningFlow):
- def __init__(self):
- super().__init__()
- self.list = List()
-
- def run(self):
- if not len(self.list):
- self.list.append(SimpleCounterWork())
-
- if self.list[-1].status.stage == WorkStageStatus.SUCCEEDED:
- self.stop()
-
- self.list[-1].run()
-
-
-def test_list_with_queues():
- app = LightningApp(FlowList())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class WorkS(LightningWork):
- def __init__(self):
- super().__init__()
- self.payload = None
-
- def run(self):
- self.payload = Payload(2)
-
-
-class WorkD(LightningWork):
- def run(self, payload):
- assert payload.value == 2
-
-
-class FlowPayload(LightningFlow):
- def __init__(self):
- super().__init__()
- self.src = WorkS()
- self.dst = Dict(**{"0": WorkD(parallel=True), "1": WorkD(parallel=True)})
-
- def run(self):
- self.src.run()
- if self.src.payload:
- for work in self.dst.values():
- work.run(self.src.payload)
- if all(w.has_succeeded for w in self.dst.values()):
- self.stop()
-
-
-@pytest.mark.skipif(True, reason="out-dated")
-def test_structures_with_payload():
- app = LightningApp(FlowPayload(), log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
- os.remove("payload")
-
-
-def test_structures_have_name_on_init():
- """Test that the children in structures have the correct name assigned upon initialization."""
-
- class ChildWork(LightningWork):
- def run(self):
- pass
-
- class Collection(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.list_structure = List()
- self.list_structure.append(ChildWork())
-
- self.dict_structure = Dict()
- self.dict_structure["dict_child"] = ChildWork()
-
- flow = Collection()
- LightningApp(flow) # wrap in app to init all component names
- assert flow.list_structure[0].name == "root.list_structure.0"
- assert flow.dict_structure["dict_child"].name == "root.dict_structure.dict_child"
-
-
-class FlowWiStructures(LightningFlow):
- def __init__(self):
- super().__init__()
-
- self.ws = [EmptyFlow(), EmptyFlow()]
-
- self.ws1 = {"a": EmptyFlow(), "b": EmptyFlow()}
-
- self.ws2 = {
- "a": EmptyFlow(),
- "b": EmptyFlow(),
- "c": List(EmptyFlow(), EmptyFlow()),
- "d": Dict(**{"a": EmptyFlow()}),
- }
-
- def run(self):
- pass
-
-
-def test_flow_without_structures():
- flow = FlowWiStructures()
- assert isinstance(flow.ws, List)
- assert isinstance(flow.ws1, Dict)
diff --git a/tests/tests_app/test_imports.py b/tests/tests_app/test_imports.py
deleted file mode 100644
index 8c17a611552ec..0000000000000
--- a/tests/tests_app/test_imports.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import importlib
-import inspect
-import os
-import types
-from typing import TypeVar
-
-import lightning.app
-
-
-def _is_attribute(member, module):
- return all([
- hasattr(member, "__module__") and module.__name__ in member.__module__,
- not isinstance(member, TypeVar),
- not isinstance(member, types.ModuleType),
- ])
-
-
-def _find_exports(module):
- members = inspect.getmembers(module)
- attributes = {member[0] for member in members if _is_attribute(member[1], module)}
- public_attributes = list(filter(lambda attribute: not attribute.startswith("_"), attributes))
- exports = {attribute: module.__name__ for attribute in public_attributes}
-
- if module.__file__ is not None and "__init__.py" in module.__file__:
- root = os.path.dirname(module.__file__)
- submodule_paths = os.listdir(root)
- submodule_paths = [path for path in submodule_paths if not path.startswith("_")]
- submodules = [
- path.replace(".py", "")
- for path in submodule_paths
- if os.path.isdir(os.path.join(root, path)) or path.endswith(".py")
- ]
- for submodule in submodules:
- deeper_exports = _find_exports(importlib.import_module(f".{submodule}", module.__name__))
- exports = {**deeper_exports, **exports}
-
- return exports or {}
-
-
-def test_import_depth(
- ignore=[
- "lightning.app.cli",
- "lightning.app.components.serve.types",
- "lightning.app.core",
- "lightning.app.launcher",
- "lightning.app.runners",
- "lightning.app.utilities",
- ],
-):
- """This test ensures that any public exports (functions, classes, etc.) can be imported by users with at most a
- depth of two. This guarantees that everything user-facing can be imported with (at most) ``lightning.app.*.*``.
-
- Args:
- ignore: Sub-module paths to ignore (usually sub-modules that are not intended to be user-facing).
-
- """
- exports = _find_exports(lightning.app)
- depths = {export: len(path.replace("lightning.app", "").split(".")) for export, path in exports.items()}
- deep_exports = [export for export, depth in depths.items() if depth > 2]
- deep_exports = list(
- filter(lambda export: not any(exports[export].startswith(path) for path in ignore), deep_exports)
- )
- if len(deep_exports) > 0:
- raise RuntimeError(
- "Found exports with a depth greater than two. "
- "Either expose them at a higher level or make them private. "
- f"Found: {', '.join(sorted(f'{exports[export]}.{export}' for export in deep_exports))}"
- )
diff --git a/tests/tests_app/utilities/__init__.py b/tests/tests_app/utilities/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/__init__.py b/tests/tests_app/utilities/packaging/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/Dockerfile.cpu b/tests/tests_app/utilities/packaging/projects/Dockerfile.cpu
deleted file mode 100644
index f5bb8e842e9b6..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/Dockerfile.cpu
+++ /dev/null
@@ -1 +0,0 @@
-FROM pytorchlightning/pytorch_lightning:base-cuda-py3.9-torch1.13-cuda11.7.1
diff --git a/tests/tests_app/utilities/packaging/projects/dock/__init__.py b/tests/tests_app/utilities/packaging/projects/dock/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dock/app.py b/tests/tests_app/utilities/packaging/projects/dock/app.py
deleted file mode 100644
index 683e04c51cb30..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dock/app.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import os
-import sys
-
-from lightning.app import LightningApp
-
-if __name__ == "__main__":
- sys.path.append(os.path.dirname(__file__))
-
- from compo.a.a import AA
- from compo.b.b import BB
-
- app = LightningApp(BB(AA()))
diff --git a/tests/tests_app/utilities/packaging/projects/dock/compo/__init__.py b/tests/tests_app/utilities/packaging/projects/dock/compo/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dock/compo/a/__init__.py b/tests/tests_app/utilities/packaging/projects/dock/compo/a/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dock/compo/a/a.py b/tests/tests_app/utilities/packaging/projects/dock/compo/a/a.py
deleted file mode 100644
index 64274fa7a3a1e..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dock/compo/a/a.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import logging
-
-from lightning.app import LightningWork
-
-logger = logging.getLogger(__name__)
-
-
-class AA(LightningWork):
- def __init__(self):
- super().__init__()
- self.message = None
-
- def run(self):
- self.message = "hello world!"
diff --git a/tests/tests_app/utilities/packaging/projects/dock/compo/b/__init__.py b/tests/tests_app/utilities/packaging/projects/dock/compo/b/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dock/compo/b/b.py b/tests/tests_app/utilities/packaging/projects/dock/compo/b/b.py
deleted file mode 100644
index 8f2622363861e..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dock/compo/b/b.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from lightning.app import LightningFlow
-
-
-class BB(LightningFlow):
- def __init__(self, work):
- super().__init__()
- self.work = work
-
- def run(self):
- self.stop()
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/__init__.py b/tests/tests_app/utilities/packaging/projects/dockerfile/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/app.py b/tests/tests_app/utilities/packaging/projects/dockerfile/app.py
deleted file mode 100644
index 3481d181686d0..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dockerfile/app.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import os
-import sys
-
-from lightning.app import LightningApp
-
-if __name__ == "__main__":
- sys.path.append(os.path.dirname(__file__))
- from comp_dockerfile.a.a import AAA
- from comp_dockerfile.b.b import BBB
-
- app = LightningApp(BBB(AAA()))
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/__init__.py b/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/Dockerfile b/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/Dockerfile
deleted file mode 100644
index f5bb8e842e9b6..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-FROM pytorchlightning/pytorch_lightning:base-cuda-py3.9-torch1.13-cuda11.7.1
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/__init__.py b/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/a.py b/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/a.py
deleted file mode 100644
index d7195668bdceb..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/a/a.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from lightning.app import LightningWork
-
-
-class AAA(LightningWork):
- def run(self):
- pass
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/b/__init__.py b/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/b/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/b/b.py b/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/b/b.py
deleted file mode 100644
index 8a12b3fd9ea5d..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/dockerfile/comp_dockerfile/b/b.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from lightning.app import LightningFlow
-
-
-class BBB(LightningFlow):
- def __init__(self, work):
- super().__init__()
- self.work = work
-
- def run(self):
- self.stop()
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/__init__.py b/tests/tests_app/utilities/packaging/projects/no_req/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/app.py b/tests/tests_app/utilities/packaging/projects/no_req/app.py
deleted file mode 100644
index 5e7155e7d3e5c..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/no_req/app.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import os
-import sys
-
-from lightning.app import LightningApp
-
-if __name__ == "__main__":
- sys.path.append(os.path.dirname(__file__))
-
- from comp.a.a import AA
- from comp.b.b import BB
-
- app = LightningApp(BB(AA()))
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/comp/__init__.py b/tests/tests_app/utilities/packaging/projects/no_req/comp/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/comp/a/__init__.py b/tests/tests_app/utilities/packaging/projects/no_req/comp/a/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/comp/a/a.py b/tests/tests_app/utilities/packaging/projects/no_req/comp/a/a.py
deleted file mode 100644
index c7c529ba7c1d0..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/no_req/comp/a/a.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pandas # noqa F401
-
-from lightning.app import LightningWork
-
-
-class AA(LightningWork):
- def run(self):
- pass
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/comp/b/__init__.py b/tests/tests_app/utilities/packaging/projects/no_req/comp/b/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/no_req/comp/b/b.py b/tests/tests_app/utilities/packaging/projects/no_req/comp/b/b.py
deleted file mode 100644
index 8f2622363861e..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/no_req/comp/b/b.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from lightning.app import LightningFlow
-
-
-class BB(LightningFlow):
- def __init__(self, work):
- super().__init__()
- self.work = work
-
- def run(self):
- self.stop()
diff --git a/tests/tests_app/utilities/packaging/projects/req/__init__.py b/tests/tests_app/utilities/packaging/projects/req/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/req/app.py b/tests/tests_app/utilities/packaging/projects/req/app.py
deleted file mode 100644
index ea23603efc478..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/req/app.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import os
-import sys
-
-from lightning.app import LightningApp
-
-if __name__ == "__main__":
- sys.path.append(os.path.dirname(__file__))
-
- from comp_req.a.a import A
- from comp_req.b.b import B
-
- app = LightningApp(B(A()))
diff --git a/tests/tests_app/utilities/packaging/projects/req/comp_req/__init__.py b/tests/tests_app/utilities/packaging/projects/req/comp_req/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/req/comp_req/a/__init__.py b/tests/tests_app/utilities/packaging/projects/req/comp_req/a/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/req/comp_req/a/a.py b/tests/tests_app/utilities/packaging/projects/req/comp_req/a/a.py
deleted file mode 100644
index 6a77dc180b3b8..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/req/comp_req/a/a.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pandas # noqa F401
-
-from lightning.app import LightningWork
-
-
-class A(LightningWork):
- def run(self):
- pass
diff --git a/tests/tests_app/utilities/packaging/projects/req/comp_req/a/requirements.txt b/tests/tests_app/utilities/packaging/projects/req/comp_req/a/requirements.txt
deleted file mode 100644
index 44ccabb86bcec..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/req/comp_req/a/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-pandas
-pytorch_lightning==1.5.9
-git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0
diff --git a/tests/tests_app/utilities/packaging/projects/req/comp_req/b/__init__.py b/tests/tests_app/utilities/packaging/projects/req/comp_req/b/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_app/utilities/packaging/projects/req/comp_req/b/b.py b/tests/tests_app/utilities/packaging/projects/req/comp_req/b/b.py
deleted file mode 100644
index 6d6f8ee3ddf11..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/req/comp_req/b/b.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from lightning.app import LightningFlow
-
-
-class B(LightningFlow):
- def __init__(self, work):
- super().__init__()
- self.work = work
-
- def run(self):
- self.stop()
diff --git a/tests/tests_app/utilities/packaging/projects/requirements.txt b/tests/tests_app/utilities/packaging/projects/requirements.txt
deleted file mode 100644
index 7bf0698bf1180..0000000000000
--- a/tests/tests_app/utilities/packaging/projects/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-cloud-stars
diff --git a/tests/tests_app/utilities/packaging/test_app_config.py b/tests/tests_app/utilities/packaging/test_app_config.py
deleted file mode 100644
index d6346d67ffa31..0000000000000
--- a/tests/tests_app/utilities/packaging/test_app_config.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import pathlib
-
-from lightning.app.utilities.packaging.app_config import AppConfig, _get_config_file
-
-
-def _make_empty_config_file(folder):
- file = pathlib.Path(folder / ".lightning")
- file.parent.mkdir(parents=True, exist_ok=True)
- file.touch()
- return file
-
-
-def test_get_config_file(tmpdir):
- _ = _make_empty_config_file(tmpdir)
- config_file1 = _make_empty_config_file(tmpdir)
-
- assert _get_config_file(tmpdir) == pathlib.Path(tmpdir, ".lightning")
- assert _get_config_file(config_file1) == pathlib.Path(tmpdir, ".lightning")
-
-
-def test_app_config_save_load(tmpdir):
- config = AppConfig("my_app")
- config.save_to_file(tmpdir / ".lightning")
- loaded_config = AppConfig.load_from_file(tmpdir / ".lightning")
- assert config == loaded_config
-
- config = AppConfig("my_app2")
- config.save_to_dir(tmpdir)
- loaded_config = AppConfig.load_from_dir(tmpdir)
- assert config == loaded_config
-
-
-def test_app_config_default_name():
- """Test that the default name gets auto-generated."""
- config = AppConfig()
- assert config.name
diff --git a/tests/tests_app/utilities/packaging/test_build_spec.py b/tests/tests_app/utilities/packaging/test_build_spec.py
deleted file mode 100644
index a34409a9f568b..0000000000000
--- a/tests/tests_app/utilities/packaging/test_build_spec.py
+++ /dev/null
@@ -1,105 +0,0 @@
-import logging
-import os
-import sys
-from unittest.mock import Mock
-
-from lightning.app.testing import LightningTestApp, application_testing
-from lightning.app.utilities.packaging.build_config import BuildConfig
-
-from tests_app import _TESTS_ROOT
-
-EXTRAS_ARGS = ["--blocking", "False", "--multiprocess", "--open-ui", "False"]
-
-
-class NoRequirementsLightningTestApp(LightningTestApp):
- def on_after_run_once(self):
- assert self.root.work.local_build_config.requirements == []
- assert self.root.work.cloud_build_config.requirements == []
- return super().on_after_run_once()
-
-
-def test_build_config_no_requirements():
- command_line = [os.path.join(_TESTS_ROOT, "utilities/packaging/projects/no_req/app.py")]
- application_testing(NoRequirementsLightningTestApp, command_line + EXTRAS_ARGS)
- sys.path = sys.path[:-1]
-
-
-def test_build_config_requirements_provided():
- spec = BuildConfig(requirements=["dask", "./projects/req/comp_req/a/requirements.txt"])
- assert spec.requirements == [
- "dask",
- "pandas",
- "pytorch_lightning==1.5.9",
- "git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0",
- ]
- assert spec == BuildConfig.from_dict(spec.to_dict())
-
-
-class BuildSpecTest(BuildConfig):
- def build_commands(self):
- return super().build_commands() + ["pip install redis"]
-
-
-def test_build_config_invalid_requirements():
- spec = BuildSpecTest(requirements=["./projects/requirements.txt"])
- assert spec.requirements == ["cloud-stars"]
- assert spec.build_commands() == ["pip install redis"]
-
-
-def test_build_config_dockerfile_provided():
- spec = BuildConfig(dockerfile="./projects/Dockerfile.cpu")
- assert not spec.requirements
- # ugly hack due to replacing `pytorch_lightning string
- assert "pytorchlightning/pytorch_lightning" in spec.dockerfile.data[0]
-
-
-class DockerfileLightningTestApp(LightningTestApp):
- def on_after_run_once(self):
- print(self.root.work.local_build_config.dockerfile)
- # ugly hack due to replacing `pytorch_lightning string
- assert "pytorchlightning/pytorch_" + "lightning" in self.root.work.local_build_config.dockerfile.data[0]
- return super().on_after_run_once()
-
-
-def test_build_config_dockerfile():
- command_line = [os.path.join(_TESTS_ROOT, "utilities/packaging/projects/dockerfile/app.py")]
- application_testing(DockerfileLightningTestApp, command_line + EXTRAS_ARGS)
- sys.path = sys.path[:-1]
-
-
-class RequirementsLightningTestApp(LightningTestApp):
- def on_after_run_once(self):
- assert self.root.work.local_build_config.requirements == [
- "git+https://github.com/mit-han-lab/torchsparse.git@v1.4.0",
- "pandas",
- "pytorch_" + "lightning==1.5.9", # ugly hack due to replacing `pytorch_lightning string
- ]
- return super().on_after_run_once()
-
-
-def test_build_config_requirements():
- command_line = [os.path.join(_TESTS_ROOT, "utilities/packaging/projects/req/app.py")]
- application_testing(RequirementsLightningTestApp, command_line + EXTRAS_ARGS)
- sys.path = sys.path[:-1]
-
-
-def test_build_config_requirements_warns(monkeypatch, caplog):
- requirements = ["foo", "bar"]
- bc = BuildConfig(requirements=requirements)
- monkeypatch.setattr(bc, "_find_requirements", lambda *_, **__: ["baz"])
- work = Mock()
- with caplog.at_level(logging.INFO):
- bc.on_work_init(work)
- assert "requirements.txt' exists with ['baz'] but ['foo', 'bar']" in caplog.text
- assert bc.requirements == requirements # they are not merged or replaced
-
-
-def test_build_config_dockerfile_warns(monkeypatch, caplog):
- dockerfile = "foo"
- bc = BuildConfig(dockerfile=dockerfile)
- monkeypatch.setattr(bc, "_find_dockerfile", lambda *_, **__: "bar")
- work = Mock()
- with caplog.at_level(logging.INFO):
- bc.on_work_init(work)
- assert "exists at 'bar' but 'foo' was passed" in caplog.text
- assert bc.dockerfile == dockerfile # they are not merged or replaced
diff --git a/tests/tests_app/utilities/packaging/test_cloud_compute.py b/tests/tests_app/utilities/packaging/test_cloud_compute.py
deleted file mode 100644
index bc17c53555eb9..0000000000000
--- a/tests/tests_app/utilities/packaging/test_cloud_compute.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import pytest
-from lightning.app import CloudCompute
-from lightning.app.storage import Mount
-
-
-def test_cloud_compute_names():
- assert CloudCompute().name == "cpu-small"
- assert CloudCompute("cpu-small").name == "cpu-small"
- assert CloudCompute("coconut").name == "coconut" # the backend is responsible for validation of names
-
-
-def test_cloud_compute_shared_memory():
- cloud_compute = CloudCompute("gpu", shm_size=1100)
- assert cloud_compute.shm_size == 1100
-
- cloud_compute = CloudCompute("gpu")
- assert cloud_compute.shm_size == 1024
-
- cloud_compute = CloudCompute("cpu")
- assert cloud_compute.shm_size == 0
-
-
-def test_cloud_compute_with_mounts():
- mount_1 = Mount(source="s3://foo/", mount_path="/foo")
- mount_2 = Mount(source="s3://foo/bar/", mount_path="/bar")
-
- cloud_compute = CloudCompute("gpu", mounts=mount_1)
- assert cloud_compute.mounts == mount_1
-
- cloud_compute = CloudCompute("gpu", mounts=[mount_1, mount_2])
- assert cloud_compute.mounts == [mount_1, mount_2]
-
- cc_dict = cloud_compute.to_dict()
- assert "mounts" in cc_dict
- assert cc_dict["mounts"] == [
- {"mount_path": "/foo", "source": "s3://foo/"},
- {"mount_path": "/bar", "source": "s3://foo/bar/"},
- ]
-
- assert CloudCompute.from_dict(cc_dict) == cloud_compute
-
-
-def test_cloud_compute_with_non_unique_mount_root_dirs():
- mount_1 = Mount(source="s3://foo/", mount_path="/foo")
- mount_2 = Mount(source="s3://foo/bar/", mount_path="/foo")
-
- with pytest.raises(ValueError, match="Every Mount attached to a work must have a unique"):
- CloudCompute("gpu", mounts=[mount_1, mount_2])
-
-
-def test_cloud_compute_clone():
- c1 = CloudCompute("gpu")
- c2 = c1.clone()
-
- assert isinstance(c2, CloudCompute)
-
- c1_dict = c1.to_dict()
- c2_dict = c2.to_dict()
-
- assert len(c1_dict) == len(c2_dict)
-
- for k in c1_dict:
- if k == "_internal_id":
- assert c1_dict[k] != c2_dict[k]
- else:
- assert c1_dict[k] == c2_dict[k]
-
-
-def test_interruptible(monkeypatch):
- """Test interruptible can be enabled with env variables and for GPU only."""
- with pytest.raises(ValueError, match="isn't supported yet"):
- CloudCompute("gpu", interruptible=True)
-
- monkeypatch.setenv("LIGHTNING_INTERRUPTIBLE_WORKS", "1")
- with pytest.raises(ValueError, match="supported only with GPU"):
- CloudCompute("cpu", interruptible=True)
-
- cloud_compute = CloudCompute("gpu", interruptible=True)
- assert hasattr(cloud_compute, "interruptible")
- # TODO: To be removed once the platform is updated.
- assert hasattr(cloud_compute, "preemptible")
diff --git a/tests/tests_app/utilities/packaging/test_docker.py b/tests/tests_app/utilities/packaging/test_docker.py
deleted file mode 100644
index 95a9edb1882c5..0000000000000
--- a/tests/tests_app/utilities/packaging/test_docker.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import os
-from time import sleep, time
-
-import pytest
-from lightning.app import LightningWork
-from lightning.app.core.queues import QueuingSystem
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.imports import _is_docker_available
-from lightning.app.utilities.load_app import load_app_from_file
-from lightning.app.utilities.packaging.docker import DockerRunner
-from lightning.app.utilities.redis import check_if_redis_running
-
-
-@pytest.mark.xfail(strict=False, reason="FIXME (tchaton)")
-@pytest.mark.skipif(not _is_docker_available(), reason="docker is required for this test.")
-@pytest.mark.skipif(not check_if_redis_running(), reason="redis is required for this test.")
-@_RunIf(skip_windows=True)
-def test_docker_runner():
- """This test validates that the lightning run work is executable within a container and deltas are sent back
- through the Redis caller_queue."""
- queues = QueuingSystem.REDIS
- queue_id = f"test_docker_runner_{str(int(time()))}"
- app_file = os.path.join(os.path.dirname(__file__), "projects/dock/app.py")
- app = load_app_from_file(app_file)
- work: LightningWork = app.root.work
-
- call_hash = "run:fe3fa0f34fc1317e152e5afb023332995392071046f1ea51c34c7c9766e3676c"
- work._calls[call_hash] = {
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "run_started_counter": 1,
- "statuses": [],
- }
-
- # The script_path needs to be relative to the container.
- docker_runner = DockerRunner(
- "/home/tests/utilities/packaging/projects/dock/app.py", work, queue_id, create_base=True
- )
- docker_runner.run()
-
- caller_queue = queues.get_caller_queue(work_name=work.name, queue_id=queue_id)
- caller_queue.put({
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "state": work.state,
- })
- delta_queue = queues.get_delta_queue(queue_id=queue_id)
- delta_1 = delta_queue.get()
- delta_2 = delta_queue.get()
- delta_3 = delta_queue.get()
-
- def get_item(delta):
- return delta.delta.to_dict()["iterable_item_added"]
-
- assert delta_1.id == "root.work"
- assert delta_2.id == "root.work"
- assert delta_3.id == "root.work"
- assert get_item(delta_1)[f"root['calls']['{call_hash}']['statuses'][0]"]["stage"] == "starting"
- assert delta_2.delta.to_dict()["type_changes"]["root['vars']['message']"]["new_value"] == "hello world!"
- assert get_item(delta_3)[f"root['calls']['{call_hash}']['statuses'][1]"]["stage"] == "succeeded"
- sleep(1)
- assert "Starting WorkRunner" in docker_runner.get_container_logs()
- docker_runner.kill()
diff --git a/tests/tests_app/utilities/packaging/test_lightning_utils.py b/tests/tests_app/utilities/packaging/test_lightning_utils.py
deleted file mode 100644
index 339c75cf9f0aa..0000000000000
--- a/tests/tests_app/utilities/packaging/test_lightning_utils.py
+++ /dev/null
@@ -1,64 +0,0 @@
-import glob
-import os
-from unittest import mock
-
-import pytest
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.git import check_github_repository, get_dir_name
-from lightning.app.utilities.packaging import lightning_utils
-from lightning.app.utilities.packaging.lightning_utils import (
- _prepare_lightning_wheels_and_requirements,
- _verify_lightning_version,
- get_dist_path_if_editable_install,
-)
-from lightning_utilities.core.imports import module_available
-
-
-@pytest.mark.skipif(not module_available("lightning"), reason="TODO: should work for lightning.app too")
-def test_prepare_lightning_wheels_and_requirement(tmpdir):
- """This test ensures the lightning source gets packaged inside the lightning repo."""
- package_name = "lightning"
- if not get_dist_path_if_editable_install(package_name):
- pytest.skip("Requires --editable install")
-
- git_dir_name = get_dir_name() if check_github_repository() else None
- if git_dir_name != package_name:
- pytest.skip("Needs to be run from within the repo")
-
- cleanup_handle = _prepare_lightning_wheels_and_requirements(tmpdir, package_name=package_name)
- assert len(os.listdir(tmpdir)) == 1
- assert len(glob.glob(str(tmpdir / "lightning-*.tar.gz"))) == 1
-
- cleanup_handle()
- assert os.listdir(tmpdir) == []
-
-
-def _mocked_get_dist_path_if_editable_install(*args, **kwargs):
- return None
-
-
-@mock.patch(
- "lightning.app.utilities.packaging.lightning_utils.get_dist_path_if_editable_install",
- new=_mocked_get_dist_path_if_editable_install,
-)
-def test_prepare_lightning_wheels_and_requirement_for_packages_installed_in_editable_mode(tmpdir):
- """This test ensures the source does not get packaged inside the lightning repo if not installed in editable
- mode."""
- cleanup_handle = _prepare_lightning_wheels_and_requirements(tmpdir)
- assert cleanup_handle is None
-
-
-@pytest.mark.xfail(strict=False, reason="TODO: Find a way to check for the latest version")
-@_RunIf(skip_windows=True)
-def test_verify_lightning_version(monkeypatch):
- monkeypatch.setattr(lightning_utils, "__version__", "0.0.1")
- monkeypatch.setattr(lightning_utils, "_fetch_latest_version", lambda _: "0.0.2")
-
- # Not latest version
- with pytest.raises(Exception, match="You need to use the latest version of Lightning"):
- _verify_lightning_version()
-
- # Latest version
- monkeypatch.setattr(lightning_utils, "__version__", "0.0.1")
- monkeypatch.setattr(lightning_utils, "_fetch_latest_version", lambda _: "0.0.1")
- _verify_lightning_version()
diff --git a/tests/tests_app/utilities/test_app_commands.py b/tests/tests_app/utilities/test_app_commands.py
deleted file mode 100644
index 117e580b7c52a..0000000000000
--- a/tests/tests_app/utilities/test_app_commands.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import os
-import sys
-
-import pytest
-from lightning.app.utilities.app_commands import CommandLines, _execute_app_commands, _extract_commands_from_file
-from lightning.app.utilities.exceptions import MisconfigurationException
-
-
-@pytest.mark.parametrize(
- ("filename", "expected_commands", "expected_line_numbers"),
- [
- ("single_command.txt", ['echo "foo"'], [1]),
- ("multiple_commands.txt", ['echo "foo"', 'echo "bar"'], [1, 2]),
- ("commands_with_mixed_comments_1.txt", ['echo "foo"', 'echo "bar"'], [1, 3]),
- ("commands_with_mixed_comments_2.txt", ['echo "foo"', 'echo "bar"'], [2, 4]),
- ("command_after_first_non_comment_line.txt", ['echo "foo"', 'echo "bar"'], [2, 4]),
- ("bang_not_at_start_of_line.txt", ['echo "foo"'], [2]),
- ("space_between_bang_and_command.txt", ['echo "foo"'], [1]),
- ("multiple_spaces_between_band_and_command.txt", ['echo "foo"'], [1]),
- ("app_commands_to_ignore.txt", [], []),
- ],
-)
-def test_extract_app_commands_from_file(filename, expected_commands, expected_line_numbers):
- dir_path = os.path.dirname(os.path.realpath(__file__))
- test_file_path = os.path.join(dir_path, "testdata", "app_commands", filename)
-
- res = _extract_commands_from_file(file_name=test_file_path)
-
- assert res.file == test_file_path
- assert res.commands == expected_commands
- assert res.line_numbers == expected_line_numbers
-
-
-def test_execute_app_commands_runs_single_command(capfd):
- cl = CommandLines(
- file="foo.txt",
- commands=['echo "foo"'],
- line_numbers=[1],
- )
- _execute_app_commands(cl)
- out, _ = capfd.readouterr()
- assert "foo" in out
-
-
-def test_execute_app_commands_runs_multiple_commands(capfd):
- cl = CommandLines(
- file="foo.txt",
- commands=['echo "foo"', 'echo "bar"'],
- line_numbers=[1, 2],
- )
- _execute_app_commands(cl)
- out, _ = capfd.readouterr()
- assert "foo" in out
- assert "bar" in out
-
-
-@pytest.mark.skipif(sys.platform.startswith("win"), reason="env command is not available on windows")
-def test_execute_app_commands_runs_with_env_vars_patched(monkeypatch, capfd):
- monkeypatch.setenv("TEST_EXECUTE_APP_COMMANDS_RUNS_WITH_ENV_VARS_PATCHED", "TRUE")
- cl = CommandLines(
- file="foo.txt",
- commands=["env"],
- line_numbers=[1],
- )
- _execute_app_commands(cl)
- out, _ = capfd.readouterr()
- assert "TEST_EXECUTE_APP_COMMANDS_RUNS_WITH_ENV_VARS_PATCHED=TRUE" in out
-
-
-def test_execute_app_commands_raises_appropriate_traceback_on_error(capfd):
- cl = CommandLines(
- file="foo.txt",
- commands=['echo "foo"', 'CommandDoesNotExist "somearg"'],
- line_numbers=[1, 3],
- )
- with pytest.raises(
- MisconfigurationException,
- match='There was a problem on line 3 of foo.txt while executing the command: CommandDoesNotExist "somearg"',
- ):
- _execute_app_commands(cl)
- out, err = capfd.readouterr()
- assert "foo" in out
- if sys.platform.startswith("linux"):
- assert "CommandDoesNotExist: not found" in err
- elif sys.platform.startswith("darwin"):
- assert "CommandDoesNotExist: command not found" in err
- else:
- assert "CommandDoesNotExist' is not recognized" in err
diff --git a/tests/tests_app/utilities/test_app_helpers.py b/tests/tests_app/utilities/test_app_helpers.py
deleted file mode 100644
index 55e48cceef76c..0000000000000
--- a/tests/tests_app/utilities/test_app_helpers.py
+++ /dev/null
@@ -1,205 +0,0 @@
-from functools import partial
-from unittest import mock
-
-import pytest
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.core.flow import _RootFlow
-from lightning.app.frontend import StaticWebFrontend
-from lightning.app.utilities.app_helpers import (
- AppStatePlugin,
- BaseStatePlugin,
- InMemoryStateStore,
- StateStore,
- _is_headless,
- _MagicMockJsonSerializable,
- is_overridden,
- is_static_method,
-)
-from lightning.app.utilities.exceptions import LightningAppStateException
-
-
-class Work(LightningWork):
- def run(self):
- pass
-
-
-class Flow(LightningFlow):
- def run(self):
- pass
-
-
-def test_is_overridden():
- # edge cases
- assert not is_overridden("whatever", None)
- with pytest.raises(ValueError, match="Expected a parent"):
- is_overridden("whatever", object())
- flow = Flow()
- assert not is_overridden("whatever", flow)
- assert not is_overridden("whatever", flow, parent=Flow)
- # normal usage
- assert is_overridden("run", flow)
- work = Work()
- assert is_overridden("run", work)
-
-
-def test_simple_app_store():
- store = InMemoryStateStore()
- user_id = "1234"
- store.add(user_id)
- state = {"data": user_id}
- store.set_app_state(user_id, state)
- store.set_served_state(user_id, state)
- store.set_served_session_id(user_id, user_id)
- assert store.get_app_state(user_id) == state
- assert store.get_served_state(user_id) == state
- assert store.get_served_session_id(user_id) == user_id
- store.remove(user_id)
- assert isinstance(store, StateStore)
-
-
-@mock.patch("lightning.app.core.constants.APP_STATE_MAX_SIZE_BYTES", 120)
-def test_simple_app_store_warning():
- store = InMemoryStateStore()
- user_id = "1234"
- store.add(user_id)
- state = {"data": "I'm a state that's larger than 120 bytes"}
- with pytest.raises(LightningAppStateException, match="is larger than the"):
- store.set_app_state(user_id, state)
-
-
-def test_base_state_plugin():
- class DummyStatePlugin(BaseStatePlugin):
- def should_update_app(self, deep_diff):
- super().should_update_app(deep_diff)
-
- def get_context(self):
- super().get_context()
-
- def render_non_authorized(self):
- super().render_non_authorized()
-
- plugin = DummyStatePlugin()
- plugin.should_update_app(None)
- plugin.get_context()
- plugin.render_non_authorized()
-
- plugin = AppStatePlugin()
- plugin.should_update_app(None)
- plugin.get_context()
- plugin.render_non_authorized()
-
-
-def test_is_static_method():
- class A:
- @staticmethod
- def a(self):
- pass
-
- @staticmethod
- def b(a):
- pass
-
- def c(self):
- pass
-
- assert is_static_method(A, "a")
- assert is_static_method(A, "b")
- assert not is_static_method(A, "c")
-
-
-class FlowWithURLLayout(Flow):
- def configure_layout(self):
- return {"name": "test", "content": "https://appurl"}
-
-
-class FlowWithFrontend(Flow):
- def configure_layout(self):
- return StaticWebFrontend(".")
-
-
-class FlowWithMockedFrontend(Flow):
- def configure_layout(self):
- return _MagicMockJsonSerializable()
-
-
-class FlowWithMockedContent(Flow):
- def configure_layout(self):
- return [{"name": "test", "content": _MagicMockJsonSerializable()}]
-
-
-class NestedFlow(Flow):
- def __init__(self):
- super().__init__()
-
- self.flow = Flow()
-
-
-class NestedFlowWithURLLayout(Flow):
- def __init__(self):
- super().__init__()
-
- self.flow = FlowWithURLLayout()
-
-
-class WorkWithStringLayout(Work):
- def configure_layout(self):
- return "http://appurl"
-
-
-class WorkWithMockedFrontendLayout(Work):
- def configure_layout(self):
- return _MagicMockJsonSerializable()
-
-
-class WorkWithFrontendLayout(Work):
- def configure_layout(self):
- return StaticWebFrontend(".")
-
-
-class WorkWithNoneLayout(Work):
- def configure_layout(self):
- return None
-
-
-class FlowWithWorkLayout(Flow):
- def __init__(self, work):
- super().__init__()
-
- self.work = work()
-
- def configure_layout(self):
- return {"name": "test", "content": self.work}
-
-
-class WorkClassRootFlow(_RootFlow):
- """A ``_RootFlow`` which takes a work class rather than the work itself."""
-
- def __init__(self, work):
- super().__init__(work())
-
-
-@pytest.mark.parametrize(
- ("flow", "expected"),
- [
- (Flow, True),
- (FlowWithURLLayout, False),
- (FlowWithFrontend, False),
- (FlowWithMockedFrontend, False),
- (FlowWithMockedContent, False),
- (NestedFlow, True),
- (NestedFlowWithURLLayout, False),
- (partial(WorkClassRootFlow, WorkWithStringLayout), False),
- (partial(WorkClassRootFlow, WorkWithMockedFrontendLayout), False),
- (partial(WorkClassRootFlow, WorkWithFrontendLayout), False),
- (partial(WorkClassRootFlow, WorkWithNoneLayout), True),
- (partial(FlowWithWorkLayout, Work), False),
- (partial(FlowWithWorkLayout, WorkWithStringLayout), False),
- (partial(FlowWithWorkLayout, WorkWithMockedFrontendLayout), False),
- (partial(FlowWithWorkLayout, WorkWithFrontendLayout), False),
- (partial(FlowWithWorkLayout, WorkWithNoneLayout), True),
- ],
-)
-def test_is_headless(flow, expected):
- flow = flow()
- app = LightningApp(flow)
- assert _is_headless(app) == expected
diff --git a/tests/tests_app/utilities/test_app_logs.py b/tests/tests_app/utilities/test_app_logs.py
deleted file mode 100644
index ccbff41b7c814..0000000000000
--- a/tests/tests_app/utilities/test_app_logs.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from datetime import datetime
-from time import sleep
-from unittest.mock import MagicMock
-
-from lightning.app.utilities.app_logs import _LogEvent
-
-
-def test_log_event():
- event_1 = _LogEvent("", datetime.now(), MagicMock(), MagicMock())
- sleep(0.1)
- event_2 = _LogEvent("", datetime.now(), MagicMock(), MagicMock())
- assert event_1 < event_2
- assert event_1 <= event_2
diff --git a/tests/tests_app/utilities/test_auth.py b/tests/tests_app/utilities/test_auth.py
deleted file mode 100644
index a8d11e8f573c2..0000000000000
--- a/tests/tests_app/utilities/test_auth.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from typing import Dict
-
-import pytest
-from lightning.app.utilities.auth import _credential_string_to_basic_auth_params
-
-
-@pytest.mark.parametrize(
- ("credential_string", "expected_parsed", "exception_message"),
- [
- ("", None, "Credential string must follow the format username:password; the provided one ('') does not."),
- (":", None, "Username cannot be empty."),
- (":pass", None, "Username cannot be empty."),
- ("user:", None, "Password cannot be empty."),
- ("user:pass", {"username": "user", "password": "pass"}, ""),
- ],
-)
-def test__credential_string_to_basic_auth_params(
- credential_string: str, expected_parsed: Dict[str, str], exception_message: str
-):
- if expected_parsed is not None:
- assert _credential_string_to_basic_auth_params(credential_string) == expected_parsed
- else:
- with pytest.raises(ValueError) as exception:
- _credential_string_to_basic_auth_params(credential_string)
- assert exception_message == str(exception.value)
diff --git a/tests/tests_app/utilities/test_cli_helpers.py b/tests/tests_app/utilities/test_cli_helpers.py
deleted file mode 100644
index 8d6684080d7a5..0000000000000
--- a/tests/tests_app/utilities/test_cli_helpers.py
+++ /dev/null
@@ -1,196 +0,0 @@
-import os
-import sys
-from unittest.mock import Mock, patch
-
-import arrow
-import lightning.app
-import pytest
-from lightning.app.utilities.cli_helpers import (
- _arrow_time_callback,
- _check_environment_and_redirect,
- _format_input_env_variables,
- _get_newer_version,
-)
-
-
-def test_format_input_env_variables():
- with pytest.raises(Exception, match="Invalid format of environment variable"):
- _format_input_env_variables(("invalid-env",))
-
- with pytest.raises(Exception, match="Invalid format of environment variable"):
- _format_input_env_variables(("=invalid",))
-
- with pytest.raises(Exception, match="Invalid format of environment variable"):
- _format_input_env_variables(("=invalid=",))
-
- with pytest.raises(Exception, match="is duplicated. Please only include it once."):
- _format_input_env_variables((
- "FOO=bar",
- "FOO=bar",
- ))
-
- with pytest.raises(
- Exception,
- match="is not a valid name. It is only allowed to contain digits 0-9, letters A-Z",
- ):
- _format_input_env_variables(("*FOO#=bar",))
-
- assert _format_input_env_variables(("FOO=bar", "BLA=bloz")) == {"FOO": "bar", "BLA": "bloz"}
-
-
-def test_arrow_time_callback():
- # Check ISO 8601 variations
- assert _arrow_time_callback(Mock(), Mock(), "2022.08.23") == arrow.Arrow(2022, 8, 23)
-
- assert _arrow_time_callback(Mock(), Mock(), "2022.08.23 12:34") == arrow.Arrow(2022, 8, 23, 12, 34)
-
- assert _arrow_time_callback(Mock(), Mock(), "2022-08-23 12:34") == arrow.Arrow(2022, 8, 23, 12, 34)
-
- assert _arrow_time_callback(Mock(), Mock(), "2022-08-23 12:34:00.000") == arrow.Arrow(2022, 8, 23, 12, 34)
-
- # Just check humanized format is parsed
- assert type(_arrow_time_callback(Mock(), Mock(), "48 hours ago")) is arrow.Arrow
-
- assert type(_arrow_time_callback(Mock(), Mock(), "60 minutes ago")) is arrow.Arrow
-
- assert type(_arrow_time_callback(Mock(), Mock(), "120 seconds ago")) is arrow.Arrow
-
- # Check raising errors
- with pytest.raises(Exception, match="cannot parse time Mon"):
- _arrow_time_callback(Mock(), Mock(), "Mon")
-
- with pytest.raises(Exception, match="cannot parse time Mon Sep 08 16:41:45 2022"):
- _arrow_time_callback(Mock(), Mock(), "Mon Sep 08 16:41:45 2022")
-
- with pytest.raises(Exception, match="cannot parse time 2022.125.12"):
- _arrow_time_callback(Mock(), Mock(), "2022.125.12")
-
- with pytest.raises(Exception, match="cannot parse time 1 time unit ago"):
- _arrow_time_callback(Mock(), Mock(), "1 time unit ago")
-
-
-@pytest.mark.parametrize(
- ("response", "current_version", "newer_version"),
- [
- (
- {
- "info": {
- "version": "2.0.0",
- "yanked": False,
- },
- "releases": {
- "1.0.0": {},
- "2.0.0": {},
- },
- },
- "1.0.0",
- "2.0.0",
- ),
- (
- {
- "info": {
- "version": "2.0.0",
- "yanked": True,
- },
- "releases": {
- "1.0.0": {},
- "2.0.0": {},
- },
- },
- "1.0.0",
- None,
- ),
- (
- {
- "info": {
- "version": "1.0.0",
- "yanked": False,
- },
- "releases": {
- "1.0.0": {},
- },
- },
- "1.0.0",
- None,
- ),
- (
- {
- "info": {
- "version": "2.0.0rc0",
- "yanked": False,
- },
- "releases": {
- "1.0.0": {},
- "2.0.0": {},
- },
- },
- "1.0.0",
- None,
- ),
- (
- {
- "info": {
- "version": "2.0.0",
- "yanked": False,
- },
- "releases": {
- "1.0.0": {},
- "2.0.0": {},
- },
- },
- "1.0.0dev",
- None,
- ),
- ({"this wil trigger an error": True}, "1.0.0", None),
- ({}, "1.0.0rc0", None),
- ],
-)
-@patch("lightning.app.utilities.cli_helpers.requests")
-def test_get_newer_version(mock_requests, response, current_version, newer_version):
- mock_requests.get().json.return_value = response
-
- lightning.app.utilities.cli_helpers.__version__ = current_version
-
- _get_newer_version.cache_clear()
- assert _get_newer_version() == newer_version
-
-
-@patch("lightning.app.utilities.cli_helpers._redirect_command")
-def test_check_environment_and_redirect(mock_redirect_command, tmpdir, monkeypatch):
- # Ensure that the test fails if it tries to redirect
- mock_redirect_command.side_effect = RuntimeError
-
- # Test normal executable on the path
- # Ensure current executable is on the path
- monkeypatch.setenv("PATH", f"{os.path.dirname(sys.executable)}")
-
- assert _check_environment_and_redirect() is None
-
- # Test executable on the path with redirect
- fake_python_path = os.path.join(tmpdir, "python")
-
- os.symlink(sys.executable, fake_python_path)
-
- monkeypatch.setenv("PATH", f"{tmpdir}")
- assert _check_environment_and_redirect() is None
-
- os.remove(fake_python_path)
-
- descriptor = os.open(
- fake_python_path,
- flags=(
- os.O_WRONLY # access mode: write only
- | os.O_CREAT # create if not exists
- | os.O_TRUNC # truncate the file to zero
- ),
- mode=0o777,
- )
-
- with open(descriptor, "w") as f:
- f.writelines([
- "#!/bin/bash\n",
- f'{sys.executable} "$@"',
- ])
-
- monkeypatch.setenv("PATH", f"{tmpdir}")
- assert _check_environment_and_redirect() is None
diff --git a/tests/tests_app/utilities/test_cloud.py b/tests/tests_app/utilities/test_cloud.py
deleted file mode 100644
index 4982874ccb24a..0000000000000
--- a/tests/tests_app/utilities/test_cloud.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import os
-from unittest import mock
-
-from lightning.app.utilities.cloud import _get_project, is_running_in_cloud
-from lightning_cloud.openapi.models import V1Project
-
-
-def test_get_project_queries_by_project_id_directly_if_it_is_passed():
- lightning_client = mock.MagicMock()
- lightning_client.projects_service_get_project = mock.MagicMock(return_value=V1Project(id="project_id"))
- project = _get_project(lightning_client, project_id="project_id")
- assert project.project_id == "project_id"
- lightning_client.projects_service_get_project.assert_called_once_with("project_id")
-
-
-def test_is_running_cloud():
- """We can determine if Lightning is running in the cloud."""
- with mock.patch.dict(os.environ, {}, clear=True):
- assert not is_running_in_cloud()
-
- with mock.patch.dict(os.environ, {"LAI_RUNNING_IN_CLOUD": "0"}, clear=True):
- assert not is_running_in_cloud()
-
- # in the cloud, LIGHTNING_APP_STATE_URL is defined
- with mock.patch.dict(os.environ, {"LIGHTNING_APP_STATE_URL": "defined"}, clear=True):
- assert is_running_in_cloud()
-
- # LAI_RUNNING_IN_CLOUD is used to fake the value of `is_running_in_cloud` when loading the app for --cloud
- with mock.patch.dict(os.environ, {"LAI_RUNNING_IN_CLOUD": "1"}):
- assert is_running_in_cloud()
diff --git a/tests/tests_app/utilities/test_commands.py b/tests/tests_app/utilities/test_commands.py
deleted file mode 100644
index fb74e03ba6e56..0000000000000
--- a/tests/tests_app/utilities/test_commands.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import argparse
-import sys
-from multiprocessing import Process
-from time import sleep
-from unittest.mock import MagicMock
-
-import pytest
-import requests
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.cli.commands.app_commands import _run_app_command
-from lightning.app.cli.connect.app import connect_app, disconnect_app
-from lightning.app.core.constants import APP_SERVER_PORT
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.commands.base import ClientCommand, _download_command, _validate_client_command
-from lightning.app.utilities.state import AppState
-from pydantic import BaseModel
-
-
-class SweepConfig(BaseModel):
- sweep_name: str
- num_trials: int
-
-
-class SweepCommand(ClientCommand):
- def run(self) -> None:
- parser = argparse.ArgumentParser()
- parser.add_argument("--sweep_name", type=str)
- parser.add_argument("--num_trials", type=int)
- hparams = parser.parse_args()
-
- config = SweepConfig(sweep_name=hparams.sweep_name, num_trials=hparams.num_trials)
- response = self.invoke_handler(config=config)
- assert response is True
-
-
-class FlowCommands(LightningFlow):
- def __init__(self):
- super().__init__()
- self.names = []
- self.has_sweep = False
-
- def run(self):
- if self.has_sweep and len(self.names) == 1:
- sleep(1)
- self.stop()
-
- def trigger_method(self, name: str):
- print(name)
- self.names.append(name)
-
- def sweep(self, config: SweepConfig):
- self.has_sweep = True
- return True
-
- def configure_commands(self):
- return [{"user command": self.trigger_method}, {"sweep": SweepCommand(self.sweep)}]
-
-
-class DummyConfig(BaseModel):
- something: str
- something_else: int
-
-
-class DummyCommand(ClientCommand):
- def run(self, something: str, something_else: int) -> None:
- config = DummyConfig(something=something, something_else=something_else)
- response = self.invoke_handler(config=config)
- assert response == {"body": 0}
-
-
-def run(config: DummyConfig):
- assert isinstance(config, DummyCommand)
-
-
-def run_failure_0(name: str):
- pass
-
-
-def run_failure_1(name):
- pass
-
-
-class CustomModel(BaseModel):
- pass
-
-
-def run_failure_2(name: CustomModel):
- pass
-
-
-@_RunIf(skip_windows=True)
-def test_validate_client_command():
- with pytest.raises(Exception, match="The provided annotation for the argument name"):
- _validate_client_command(ClientCommand(run_failure_0))
-
- with pytest.raises(Exception, match="annotate your method"):
- _validate_client_command(ClientCommand(run_failure_1))
-
- starts = "lightning.app".replace(".", "/")
- with pytest.raises(Exception, match=f"{starts}/utilities/commands/base.py"):
- _validate_client_command(ClientCommand(run_failure_2))
-
-
-def test_client_commands(monkeypatch):
- import requests
-
- resp = MagicMock()
- resp.status_code = 200
- value = {"body": 0}
- resp.json = MagicMock(return_value=value)
- post = MagicMock()
- post.return_value = resp
- monkeypatch.setattr(requests, "post", post)
- url = "http//"
- kwargs = {"something": "1", "something_else": "1"}
- command = DummyCommand(run)
- _validate_client_command(command)
- client_command = _download_command(
- command_name="something",
- cls_path=__file__,
- cls_name="DummyCommand",
- )
- client_command._setup("something", app_url=url)
- client_command.run(**kwargs)
-
-
-def target():
- app = LightningApp(FlowCommands())
- MultiProcessRuntime(app).dispatch()
-
-
-@pytest.mark.xfail(strict=False, reason="failing for some reason, need to be fixed.") # fixme
-def test_configure_commands(monkeypatch):
- """This test validates command can be used locally with connect and disconnect."""
- process = Process(target=target)
- process.start()
- time_left = 15
- while time_left > 0:
- try:
- requests.get(f"http://localhost:{APP_SERVER_PORT}/healthz")
- break
- except requests.exceptions.ConnectionError:
- sleep(0.1)
- time_left -= 0.1
-
- sleep(0.5)
- monkeypatch.setattr(sys, "argv", ["lightning", "user", "command", "--name=something"])
- connect_app("localhost")
- _run_app_command("localhost", None)
- sleep(2)
- state = AppState()
- state._request_state()
- assert state.names == ["something"]
- monkeypatch.setattr(sys, "argv", ["lightning", "sweep", "--sweep_name=my_name", "--num_trials=1"])
- _run_app_command("localhost", None)
- time_left = 15
- while time_left > 0:
- if process.exitcode == 0:
- break
- sleep(0.1)
- time_left -= 0.1
- assert process.exitcode == 0
- disconnect_app()
- process.kill()
diff --git a/tests/tests_app/utilities/test_component.py b/tests/tests_app/utilities/test_component.py
deleted file mode 100644
index 050216361eb81..0000000000000
--- a/tests/tests_app/utilities/test_component.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import pytest
-from lightning.app.storage.path import Path
-from lightning.app.testing.helpers import EmptyFlow, EmptyWork
-from lightning.app.utilities.component import (
- _context,
- _convert_paths_after_init,
- _get_context,
- _is_flow_context,
- _is_work_context,
- _set_context,
- _set_work_context,
-)
-from lightning.app.utilities.enum import ComponentContext
-
-
-def test_convert_paths_after_init():
- """Test that we can convert paths after the Flow/Work initialization, i.e., when the LightningApp is fully
- instantiated."""
-
- # TODO: Add a test case for the Lightning List and Dict containers
-
- class Flow1(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.path1 = Path("a")
- self.path2 = Path("b")
-
- flow1 = Flow1()
- assert flow1._paths == {}
- _convert_paths_after_init(flow1)
- assert flow1._paths == {"path1": Path("a").to_dict(), "path2": Path("b").to_dict()}
-
- class Work1(EmptyWork):
- def __init__(self):
- super().__init__()
- self.path3 = Path("c")
-
- class Flow2(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.work1 = Work1()
- self.path4 = Path("d")
-
- flow2 = Flow2()
- assert flow2._paths == {}
- assert flow2.work1._paths == {}
- _convert_paths_after_init(flow2)
- assert flow2._paths == {"path4": Path("d").to_dict()}
- assert set(flow2.work1._paths.keys()) == {"path3"}
- assert flow2.work1._paths["path3"]["origin_name"] == "root.work1"
- assert flow2.work1._paths["path3"]["consumer_name"] == "root.work1"
-
-
-@pytest.mark.parametrize("ctx", [c.value for c in ComponentContext])
-def test_context_context_manager(ctx):
- with _context("flow"):
- assert _get_context().value == "flow"
- assert _get_context() is None
-
-
-@pytest.mark.parametrize("ctx", [c.value for c in ComponentContext])
-def test_set_get_context(ctx):
- assert _get_context() is None
- _set_context(ctx)
- assert _get_context().value == ctx
-
-
-def test_is_context():
- _set_context("flow")
- assert _is_flow_context()
-
- _set_work_context()
- assert _is_work_context()
-
- _set_context(None)
- assert not _is_flow_context()
- assert not _is_work_context()
diff --git a/tests/tests_app/utilities/test_dependency_caching.py b/tests/tests_app/utilities/test_dependency_caching.py
deleted file mode 100644
index ef63e254f29a0..0000000000000
--- a/tests/tests_app/utilities/test_dependency_caching.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pathlib import Path
-
-from lightning.app.utilities.dependency_caching import get_hash
-
-
-def test_get_hash(tmpdir):
- req_path = tmpdir / "requirements.txt"
- Path(req_path).touch()
-
- # empty requirements file
- assert get_hash(req_path) == "3345524abf6bbe1809449224b5972c41790b6cf2"
-
- # requirements file with one dependency
- req_path.write_text("lightning==1.0", encoding="utf-8")
- assert get_hash(req_path) == "6177677a74b5d256e331cb9e390af58106e20220"
diff --git a/tests/tests_app/utilities/test_exceptions.py b/tests/tests_app/utilities/test_exceptions.py
deleted file mode 100644
index 96ac20deb9fdc..0000000000000
--- a/tests/tests_app/utilities/test_exceptions.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from json import dumps
-from unittest.mock import MagicMock
-
-import pytest
-from click import ClickException, group
-from click.testing import CliRunner
-from lightning.app.utilities.exceptions import _ApiExceptionHandler
-from lightning_cloud.openapi.rest import ApiException
-from urllib3 import HTTPResponse
-
-
-@pytest.fixture()
-def mock_api_handled_group():
- @group(cls=_ApiExceptionHandler)
- def g():
- pass
-
- return g
-
-
-@pytest.fixture()
-def mock_subcommand(mock_api_handled_group):
- @mock_api_handled_group.command()
- def cmd():
- pass
-
- return cmd
-
-
-@pytest.fixture()
-def api_error_msg():
- return "This is an internal error message"
-
-
-class Test_ApiExceptionHandler:
- def test_4xx_exceptions_caught_in_subcommands(self, mock_api_handled_group, mock_subcommand, api_error_msg):
- mock_subcommand.invoke = MagicMock(
- side_effect=ApiException(
- http_resp=HTTPResponse(
- status=400,
- reason="Bad Request",
- body=dumps(
- {
- "code": 3,
- "message": api_error_msg,
- "details": [],
- },
- ),
- )
- )
- )
-
- runner = CliRunner()
- result = runner.invoke(
- mock_api_handled_group,
- [mock_subcommand.name],
- standalone_mode=False, # stop runner from raising SystemExit on ClickException
- )
-
- mock_subcommand.invoke.assert_called
- assert result.exit_code == 1
- assert type(result.exception) is ClickException
- assert api_error_msg == str(result.exception)
-
- def test_original_thrown_if_cannot_decode_body(self, mock_api_handled_group, mock_subcommand):
- mock_subcommand.invoke = MagicMock(
- side_effect=ApiException(
- http_resp=HTTPResponse(
- status=400,
- reason="Bad Request",
- body="message from server is not json encoded!",
- )
- )
- )
-
- runner = CliRunner()
- result = runner.invoke(
- mock_api_handled_group,
- [mock_subcommand.name],
- )
-
- mock_subcommand.invoke.assert_called
- assert result.exit_code == 1
- assert type(result.exception) is ApiException
diff --git a/tests/tests_app/utilities/test_git.py b/tests/tests_app/utilities/test_git.py
deleted file mode 100644
index b655baae5f947..0000000000000
--- a/tests/tests_app/utilities/test_git.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import os
-import sys
-from unittest.mock import patch
-
-from lightning.app.utilities.git import (
- check_github_repository,
- check_if_remote_head_is_different,
- get_dir_name,
- get_git_relative_path,
- has_uncommitted_files,
-)
-
-
-def mock_execute_git_command(args, cwd=None) -> str:
- if args == ["config", "--get", "remote.origin.url"]:
- return "https://github.com/Lightning-AI/lightning.git"
-
- if args == ["rev-parse", "--show-toplevel"]:
- return os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
-
- if args == ["update-index", "--refresh"]:
- return ""
-
- if args == ["rev-parse", "@"]:
- return "local-sha"
-
- if args == ["rev-parse", r"@{u}"] or args == ["merge-base", "@", r"@{u}"]:
- return "remote-sha"
-
- return "Error: Unexpected call"
-
-
-@patch("lightning.app.utilities.git.execute_git_command", mock_execute_git_command)
-def test_execute_git_command():
- assert get_dir_name() == "lightning"
-
- assert check_github_repository()
-
- if sys.platform == "win32":
- assert get_git_relative_path(__file__) == "tests\\tests_app\\utilities\\test_git.py"
- else:
- assert get_git_relative_path(__file__) == "tests/tests_app/utilities/test_git.py"
-
- assert check_if_remote_head_is_different()
-
- assert not has_uncommitted_files()
diff --git a/tests/tests_app/utilities/test_imports.py b/tests/tests_app/utilities/test_imports.py
deleted file mode 100644
index bf124ac6ed198..0000000000000
--- a/tests/tests_app/utilities/test_imports.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import os
-from unittest import mock
-
-import pytest
-from lightning.app import __package_name__
-from lightning.app.utilities.imports import _get_extras, requires
-
-
-def test_get_extras():
- extras = "app-cloud" if __package_name__ == "lightning" else "cloud"
- extras = _get_extras(extras)
- assert "docker" in extras
- assert "redis" in extras
-
- assert _get_extras("fake-extras") == ""
-
-
-@mock.patch.dict(os.environ, {"LIGHTING_TESTING": "0"})
-def test_requires():
- @requires("lightning.app")
- def fn():
- pass
-
- fn()
-
- @requires("shouldnotexist")
- def fn_raise():
- pass
-
- with pytest.raises(ModuleNotFoundError, match="Please run: pip install 'shouldnotexist'"):
- fn_raise()
-
- class ClassRaise:
- @requires("shouldnotexist")
- def __init__(self):
- pass
-
- with pytest.raises(ModuleNotFoundError, match="Please run: pip install 'shouldnotexist'"):
- ClassRaise()
-
-
-@mock.patch.dict(os.environ, {"LIGHTING_TESTING": "0"})
-def test_requires_multiple():
- @requires(["shouldnotexist1", "shouldnotexist2"])
- def fn_raise():
- pass
-
- with pytest.raises(ModuleNotFoundError, match="Please run: pip install 'shouldnotexist1' 'shouldnotexist2'"):
- fn_raise()
diff --git a/tests/tests_app/utilities/test_introspection.py b/tests/tests_app/utilities/test_introspection.py
deleted file mode 100644
index ce5d54f4e0746..0000000000000
--- a/tests/tests_app/utilities/test_introspection.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import os
-from numbers import Rational
-
-from lightning.app import LightningApp, LightningFlow
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.imports import _is_pytorch_lightning_available
-from lightning.app.utilities.introspection import Scanner
-
-if _is_pytorch_lightning_available():
- from pytorch_lightning import Trainer
- from pytorch_lightning.cli import LightningCLI
-
-from tests_app import _PROJECT_ROOT
-
-
-def test_introspection():
- """This test validates the scanner can find some class within the provided files."""
- scanner = Scanner(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/example_1.py")))
- assert scanner.has_class(Rational)
- assert not scanner.has_class(LightningApp)
-
- scanner = Scanner(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/example_2.py")))
- assert scanner.has_class(LightningApp)
- assert not scanner.has_class(LightningFlow)
-
-
-@_RunIf(pl=True)
-def test_introspection_lightning():
- """This test validates the scanner can find some PyTorch Lightning class within the provided files."""
- scanner = Scanner(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/lightning_cli.py")))
- assert not scanner.has_class(Trainer)
- assert scanner.has_class(LightningCLI)
-
- scanner = Scanner(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/lightning_trainer.py")))
- assert scanner.has_class(Trainer)
- assert not scanner.has_class(LightningCLI)
-
-
-@_RunIf(pl=True)
-def test_introspection_lightning_overrides():
- """This test validates the scanner can find all the subclasses from primitives classes from PyTorch Lightning in
- the provided files."""
- scanner = Scanner(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/lightning_cli.py")))
- scan = scanner.scan()
- assert set(scan) == {"LightningDataModule", "LightningModule"}
-
- scanner = Scanner(str(os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/lightning_overrides.py")))
- scan = scanner.scan()
- assert set(scan) == {
- "Accelerator",
- "Profiler",
- "Callback",
- "LightningDataModule",
- "Fabric",
- "Logger",
- "LightningModule",
- "Metric",
- "PrecisionPlugin",
- "Trainer",
- }
diff --git a/tests/tests_app/utilities/test_layout.py b/tests/tests_app/utilities/test_layout.py
deleted file mode 100644
index 49d9a75ce2e9e..0000000000000
--- a/tests/tests_app/utilities/test_layout.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import pytest
-from lightning.app.core.flow import LightningFlow
-from lightning.app.core.work import LightningWork
-from lightning.app.frontend.web import StaticWebFrontend
-from lightning.app.utilities.layout import _collect_layout
-
-
-class _MockApp:
- def __init__(self) -> None:
- self.frontends = {}
-
-
-class FlowWithFrontend(LightningFlow):
- def configure_layout(self):
- return StaticWebFrontend(".")
-
-
-class WorkWithFrontend(LightningWork):
- def run(self):
- pass
-
- def configure_layout(self):
- return StaticWebFrontend(".")
-
-
-class FlowWithWorkWithFrontend(LightningFlow):
- def __init__(self):
- super().__init__()
-
- self.work = WorkWithFrontend()
-
- def configure_layout(self):
- return {"name": "work", "content": self.work}
-
-
-class FlowWithUrl(LightningFlow):
- def configure_layout(self):
- return {"name": "test", "content": "https://test"}
-
-
-class WorkWithUrl(LightningWork):
- def run(self):
- pass
-
- def configure_layout(self):
- return "https://test"
-
-
-class FlowWithWorkWithUrl(LightningFlow):
- def __init__(self):
- super().__init__()
-
- self.work = WorkWithUrl()
-
- def configure_layout(self):
- return {"name": "test", "content": self.work}
-
-
-@pytest.mark.parametrize(
- ("flow", "expected_layout", "expected_frontends"),
- [
- (FlowWithFrontend, {}, [("root", StaticWebFrontend)]),
- (FlowWithWorkWithFrontend, {}, [("root", StaticWebFrontend)]),
- (FlowWithUrl, [{"name": "test", "content": "https://test", "target": "https://test"}], []),
- (FlowWithWorkWithUrl, [{"name": "test", "content": "https://test", "target": "https://test"}], []),
- ],
-)
-def test_collect_layout(flow, expected_layout, expected_frontends):
- app = _MockApp()
- flow = flow()
- layout = _collect_layout(app, flow)
-
- assert layout == expected_layout
- assert set(app.frontends.keys()) == {key for key, _ in expected_frontends}
- for key, frontend_type in expected_frontends:
- assert isinstance(app.frontends[key], frontend_type)
-
-
-class FlowWithBadLayout(LightningFlow):
- def configure_layout(self):
- return 100
-
-
-class FlowWithBadLayoutDict(LightningFlow):
- def configure_layout(self):
- return {"this_key_should_not_be_here": "http://appurl"}
-
-
-class FlowWithBadContent(LightningFlow):
- def configure_layout(self):
- return {"content": 100}
-
-
-class WorkWithBadLayout(LightningWork):
- def run(self):
- pass
-
- def configure_layout(self):
- return 100
-
-
-class FlowWithWorkWithBadLayout(LightningFlow):
- def __init__(self):
- super().__init__()
-
- self.work = WorkWithBadLayout()
-
- def configure_layout(self):
- return {"name": "test", "content": self.work}
-
-
-class FlowWithMultipleWorksWithFrontends(LightningFlow):
- def __init__(self):
- super().__init__()
-
- self.work1 = WorkWithFrontend()
- self.work2 = WorkWithFrontend()
-
- def configure_layout(self):
- return [{"name": "test1", "content": self.work1}, {"name": "test2", "content": self.work2}]
-
-
-@pytest.mark.parametrize(
- ("flow", "error_type", "match"),
- [
- (FlowWithBadLayout, TypeError, "is an unsupported layout format"),
- (FlowWithBadLayoutDict, ValueError, "missing a key 'content'."),
- (FlowWithBadContent, ValueError, "contains an unsupported entry."),
- (FlowWithWorkWithBadLayout, TypeError, "is of an unsupported type."),
- (
- FlowWithMultipleWorksWithFrontends,
- TypeError,
- "The tab containing a `WorkWithFrontend` must be the only tab",
- ),
- ],
-)
-def test_collect_layout_errors(flow, error_type, match):
- app = _MockApp()
- flow = flow()
-
- with pytest.raises(error_type, match=match):
- _collect_layout(app, flow)
diff --git a/tests/tests_app/utilities/test_load_app.py b/tests/tests_app/utilities/test_load_app.py
deleted file mode 100644
index 62e09764c1b3c..0000000000000
--- a/tests/tests_app/utilities/test_load_app.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import os
-import sys
-from unittest.mock import ANY
-
-import pytest
-from lightning.app.utilities.exceptions import MisconfigurationException
-from lightning.app.utilities.load_app import extract_metadata_from_app, load_app_from_file
-
-
-def test_load_app_from_file_errors():
- test_script_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "core", "scripts")
- with pytest.raises(MisconfigurationException, match="There should not be multiple apps instantiated within a file"):
- load_app_from_file(os.path.join(test_script_dir, "two_apps.py"))
-
- with pytest.raises(MisconfigurationException, match="The provided file .* does not contain a LightningApp"):
- load_app_from_file(os.path.join(test_script_dir, "empty.py"))
-
- with pytest.raises(SystemExit, match="1"):
- load_app_from_file(os.path.join(test_script_dir, "script_with_error.py"))
-
-
-@pytest.mark.parametrize("app_path", ["app_metadata.py", "app_with_local_import.py"])
-def test_load_app_from_file(app_path):
- """Test that apps load without error and that sys.path and main module are set."""
- original_main = sys.modules["__main__"]
- test_script_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "core", "scripts")
- load_app_from_file(os.path.join(test_script_dir, app_path), raise_exception=True)
-
- assert test_script_dir in sys.path
- assert sys.modules["__main__"] != original_main
-
-
-def test_extract_metadata_from_component():
- test_script_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "core", "scripts")
- app = load_app_from_file(os.path.join(test_script_dir, "app_metadata.py"))
- metadata = extract_metadata_from_app(app)
- assert metadata == [
- {"affiliation": ["root"], "cls_name": "RootFlow", "module": "__main__", "docstring": "RootFlow."},
- {
- "affiliation": ["root", "flow_a_1"],
- "cls_name": "FlowA",
- "module": "__main__",
- "docstring": "FlowA Component.",
- },
- {
- "affiliation": ["root", "flow_a_1", "work_a"],
- "cls_name": "WorkA",
- "module": "__main__",
- "docstring": "WorkA.",
- "local_build_config": {"__build_config__": {"requirements": [], "dockerfile": None, "image": None}},
- "cloud_build_config": {"__build_config__": {"requirements": [], "dockerfile": None, "image": None}},
- "cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "shm_size": 0,
- "mounts": None,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- {
- "affiliation": ["root", "flow_a_2"],
- "cls_name": "FlowA",
- "module": "__main__",
- "docstring": "FlowA Component.",
- },
- {
- "affiliation": ["root", "flow_a_2", "work_a"],
- "cls_name": "WorkA",
- "module": "__main__",
- "docstring": "WorkA.",
- "local_build_config": {"__build_config__": {"requirements": [], "dockerfile": None, "image": None}},
- "cloud_build_config": {"__build_config__": {"requirements": [], "dockerfile": None, "image": None}},
- "cloud_compute": {
- "type": "__cloud_compute__",
- "name": "cpu-small",
- "disk_size": 0,
- "idle_timeout": None,
- "shm_size": 0,
- "mounts": None,
- "_internal_id": "default",
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- {"affiliation": ["root", "flow_b"], "cls_name": "FlowB", "module": "__main__", "docstring": "FlowB."},
- {
- "affiliation": ["root", "flow_b", "work_b"],
- "cls_name": "WorkB",
- "module": "__main__",
- "docstring": "WorkB.",
- "local_build_config": {"__build_config__": {"requirements": [], "dockerfile": None, "image": None}},
- "cloud_build_config": {"__build_config__": {"requirements": [], "dockerfile": None, "image": None}},
- "cloud_compute": {
- "type": "__cloud_compute__",
- "name": "gpu",
- "disk_size": 0,
- "idle_timeout": None,
- "shm_size": 1024,
- "mounts": None,
- "_internal_id": ANY,
- "interruptible": False,
- "colocation_group_id": None,
- },
- },
- ]
diff --git a/tests/tests_app/utilities/test_log_helpers.py b/tests/tests_app/utilities/test_log_helpers.py
deleted file mode 100644
index 6eae52388383c..0000000000000
--- a/tests/tests_app/utilities/test_log_helpers.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from unittest import TestCase, mock
-
-from lightning.app.utilities.log_helpers import _error_callback
-
-
-class TestErrorCallback(TestCase):
- def test_known_error(self):
- websocket = mock.Mock()
- with self.assertLogs("lightning.app.utilities.log_helpers") as captured:
- _error_callback(websocket, ValueError())
- # check that there is only one log message
- assert len(captured.records) == 1
- # and it contains the error message expected
- assert "Error while reading logs (Malformed date format)" in captured.records[0].getMessage()
-
- def test_unknown_error(self):
- websocket = mock.Mock()
- with self.assertLogs("lightning.app.utilities.log_helpers") as captured:
- _error_callback(websocket, OSError())
- # check that there is only one log message
- assert len(captured.records) == 1
- # and it contains the error message expected
- assert "Error while reading logs (Unknown)" in captured.records[0].getMessage()
diff --git a/tests/tests_app/utilities/test_login.py b/tests/tests_app/utilities/test_login.py
deleted file mode 100644
index d8f6d3d558707..0000000000000
--- a/tests/tests_app/utilities/test_login.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import os
-from unittest import mock
-
-import pytest
-import requests
-from lightning.app.utilities import login
-
-LIGHTNING_CLOUD_URL = os.getenv("LIGHTNING_CLOUD_URL", "https://lightning.ai")
-
-
-@pytest.fixture(autouse=True)
-def before_each():
- for key in login.Keys:
- os.environ.pop(key.value, None)
- login.Auth().clear()
-
-
-class TestAuthentication:
- def test_can_store_credentials(self):
- auth = login.Auth()
- auth.save(username="superman", user_id="kr-1234")
- assert auth.secrets_file.exists()
-
- auth.clear()
- assert not auth.secrets_file.exists()
-
- def test_e2e(self):
- auth = login.Auth()
- auth.save(username="superman", user_id="kr-1234")
- assert auth.secrets_file.exists()
-
- def test_get_auth_header_should_raise_error(self):
- with pytest.raises(AttributeError):
- login.Auth().auth_header
-
- def test_credentials_file_io(self):
- auth = login.Auth()
- assert not auth.secrets_file.exists()
- assert auth.load() is False
- auth.save(username="", user_id="123", api_key="123")
- assert auth.secrets_file.exists()
- assert auth.load() is True
-
- def test_auth_header(self):
- # fake credentials
- os.environ.setdefault("LIGHTNING_USER_ID", "7c8455e3-7c5f-4697-8a6d-105971d6b9bd")
- os.environ.setdefault("LIGHTNING_API_KEY", "e63fae57-2b50-498b-bc46-d6204cbf330e")
- auth = login.Auth()
- auth.clear()
- auth.authenticate()
-
- assert "Basic" in auth.auth_header
- assert (
- auth.auth_header
- == "Basic N2M4NDU1ZTMtN2M1Zi00Njk3LThhNmQtMTA1OTcxZDZiOWJkOmU2M2ZhZTU3LTJiNTAtNDk4Yi1iYzQ2LWQ2MjA0Y2JmMzMwZQ==" # noqa E501
- )
-
-
-def test_authentication_with_invalid_environment_vars():
- # if api key is passed without user id
- os.environ.setdefault("LIGHTNING_API_KEY", "123")
- with pytest.raises(ValueError):
- auth = login.Auth()
- auth.clear()
- auth.authenticate()
-
-
-@mock.patch("lightning.app.utilities.login.AuthServer.login_with_browser")
-def test_authentication_with_environment_vars(browser_login: mock.MagicMock):
- os.environ.setdefault("LIGHTNING_USER_ID", "abc")
- os.environ.setdefault("LIGHTNING_API_KEY", "abc")
-
- auth = login.Auth()
- auth.clear()
- auth.authenticate()
-
- assert auth.user_id == "abc"
- assert auth.auth_header == "Basic YWJjOmFiYw=="
- assert auth.authenticate() == auth.auth_header
- # should not run login flow when env vars are passed
- browser_login.assert_not_called()
-
- # Check credentials file
- assert auth.secrets_file.exists()
- assert auth.load() is True
-
-
-def test_get_auth_url():
- auth_url = login.AuthServer().get_auth_url(1234)
- assert (
- auth_url == f"{LIGHTNING_CLOUD_URL}/sign-in?redirectTo=http%3A%2F%2Flocalhost%3A1234%2Flogin-complete"
- ) # E501
-
-
-@mock.patch("lightning.app.utilities.login.find_free_network_port")
-@mock.patch("uvicorn.Server.run")
-@mock.patch("requests.head")
-@mock.patch("click.launch")
-def test_login_with_browser(
- click_launch: mock.MagicMock, head: mock.MagicMock, run: mock.MagicMock, port: mock.MagicMock
-):
- port.return_value = 1234
- login.Auth()._run_server()
- url = f"{LIGHTNING_CLOUD_URL}/sign-in?redirectTo=http%3A%2F%2Flocalhost%3A1234%2Flogin-complete" # E501
- head.assert_called_once_with(url)
- click_launch.assert_called_once_with(url)
- run.assert_called_once()
-
-
-@mock.patch("lightning.app.utilities.login.find_free_network_port")
-@mock.patch("uvicorn.Server.run")
-@mock.patch("requests.head")
-@mock.patch("click.launch")
-def test_authenticate(click_launch: mock.MagicMock, head: mock.MagicMock, run: mock.MagicMock, port: mock.MagicMock):
- port.return_value = 1234
- auth = login.Auth()
- auth.clear()
-
- click_launch.side_effect = lambda _: auth.save("", "user_id", "api_key", "user_id")
-
- auth.authenticate()
- url = f"{LIGHTNING_CLOUD_URL}/sign-in?redirectTo=http%3A%2F%2Flocalhost%3A1234%2Flogin-complete" # E501
- head.assert_called_with(url)
- click_launch.assert_called_with(url)
- run.assert_called()
-
- assert auth.auth_header == "Basic dXNlcl9pZDphcGlfa2V5"
-
- auth.authenticate()
- assert auth.auth_header == "Basic dXNlcl9pZDphcGlfa2V5"
-
-
-@mock.patch("uvicorn.Server.run")
-@mock.patch("requests.head")
-def test_network_failure(
- head: mock.MagicMock,
- run: mock.MagicMock,
-):
- head.side_effect = requests.ConnectionError()
- with pytest.raises(requests.ConnectionError):
- login.Auth()._run_server()
- run.assert_not_called()
-
- head.side_effect = requests.RequestException()
- with pytest.raises(requests.RequestException):
- login.Auth()._run_server()
- run.assert_not_called()
-
-
-def test_with_api_key_only():
- auth = login.Auth()
- auth.save(user_id="7c8455e3-7c5f-4697-8a6d-105971d6b9bd", api_key="e63fae57-2b50-498b-bc46-d6204cbf330e")
- hash_ = "N2M4NDU1ZTMtN2M1Zi00Njk3LThhNmQtMTA1OTcxZDZiOWJkOmU2M2ZhZTU3LTJiNTAtNDk4Yi1iYzQ2LWQ2MjA0Y2JmMzMwZQ"
- assert auth.authenticate() == f"Basic {hash_}==" # E501
diff --git a/tests/tests_app/utilities/test_network.py b/tests/tests_app/utilities/test_network.py
deleted file mode 100644
index 999c75da57dd7..0000000000000
--- a/tests/tests_app/utilities/test_network.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from http.client import HTTPMessage
-from unittest import mock
-
-import pytest
-from lightning.app.core import constants
-from lightning.app.utilities.network import HTTPClient, find_free_network_port
-
-
-def test_find_free_network_port():
- """Tests that `find_free_network_port` gives expected outputs and raises if a free port couldn't be found."""
- assert find_free_network_port()
-
- with mock.patch("lightning.app.utilities.network.socket") as mock_socket:
- mock_socket.socket().getsockname.return_value = [0, 8888]
- assert find_free_network_port() == 8888
-
- with pytest.raises(RuntimeError, match="Couldn't find a free port."):
- find_free_network_port()
-
- mock_socket.socket().getsockname.return_value = [0, 9999]
- assert find_free_network_port() == 9999
-
-
-@mock.patch("lightning.app.utilities.network.socket")
-@pytest.mark.parametrize(
- "patch_constants",
- [{"LIGHTNING_CLOUDSPACE_HOST": "any", "LIGHTNING_CLOUDSPACE_EXPOSED_PORT_COUNT": 10}],
- indirect=True,
-)
-def test_find_free_network_port_cloudspace(_, patch_constants):
- """Tests that `find_free_network_port` gives expected outputs and raises if a free port couldn't be found when
- cloudspace env variables are set."""
- ports = set()
- num_ports = 0
-
- with pytest.raises(RuntimeError, match="All 10 ports are already in use."):
- for _ in range(11):
- ports.add(find_free_network_port())
- num_ports = num_ports + 1
-
- # Check that all ports are unique
- assert len(ports) == num_ports
-
- # Shouldn't use the APP_SERVER_PORT
- assert constants.APP_SERVER_PORT not in ports
-
-
-@mock.patch("urllib3.connectionpool.HTTPConnectionPool._get_conn")
-def test_http_client_retry_post(getconn_mock):
- getconn_mock.return_value.getresponse.side_effect = [
- mock.Mock(status=500, msg=HTTPMessage(), headers={}),
- mock.Mock(status=599, msg=HTTPMessage(), headers={}),
- mock.Mock(status=405, msg=HTTPMessage(), headers={}),
- mock.Mock(status=200, msg=HTTPMessage(), headers={}),
- ]
-
- client = HTTPClient(base_url="http://test.url")
- r = client.post("/test")
- r.raise_for_status()
-
- assert getconn_mock.return_value.request.mock_calls == [
- mock.call("POST", "/test", body=None, headers=mock.ANY),
- mock.call("POST", "/test", body=None, headers=mock.ANY),
- mock.call("POST", "/test", body=None, headers=mock.ANY),
- mock.call("POST", "/test", body=None, headers=mock.ANY),
- ]
-
-
-@mock.patch("urllib3.connectionpool.HTTPConnectionPool._get_conn")
-def test_http_client_retry_get(getconn_mock):
- getconn_mock.return_value.getresponse.side_effect = [
- mock.Mock(status=500, msg=HTTPMessage(), headers={}),
- mock.Mock(status=599, msg=HTTPMessage(), headers={}),
- mock.Mock(status=405, msg=HTTPMessage(), headers={}),
- mock.Mock(status=200, msg=HTTPMessage(), headers={}),
- ]
-
- client = HTTPClient(base_url="http://test.url")
- r = client.get("/test")
- r.raise_for_status()
-
- assert getconn_mock.return_value.request.mock_calls == [
- mock.call("GET", "/test", body=None, headers=mock.ANY),
- mock.call("GET", "/test", body=None, headers=mock.ANY),
- mock.call("GET", "/test", body=None, headers=mock.ANY),
- mock.call("GET", "/test", body=None, headers=mock.ANY),
- ]
diff --git a/tests/tests_app/utilities/test_port.py b/tests/tests_app/utilities/test_port.py
deleted file mode 100644
index 68a19e3f46c0a..0000000000000
--- a/tests/tests_app/utilities/test_port.py
+++ /dev/null
@@ -1,108 +0,0 @@
-from unittest.mock import MagicMock
-
-import pytest
-from lightning.app.utilities import port
-from lightning.app.utilities.port import _find_lit_app_port, disable_port, enable_port
-from lightning_cloud.openapi import V1NetworkConfig
-
-
-def test_find_lit_app_port(monkeypatch):
- client = MagicMock()
- monkeypatch.setattr(port, "LightningClient", MagicMock(return_value=client))
-
- assert _find_lit_app_port(5701) == 5701
-
- resp = MagicMock()
- lit_app = MagicMock()
- lit_app.id = "a"
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=False),
- ]
- resp.lightningapps = [lit_app]
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = resp
-
- monkeypatch.setenv("LIGHTNING_CLOUD_APP_ID", "a")
- monkeypatch.setenv("LIGHTNING_CLOUD_PROJECT_ID", "a")
- monkeypatch.setenv("ENABLE_MULTIPLE_WORKS_IN_DEFAULT_CONTAINER", "1")
-
- assert _find_lit_app_port(5701) == 1
-
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=True),
- ]
-
- with pytest.raises(RuntimeError, match="No available port was found. Please"):
- _find_lit_app_port(5701)
-
-
-def test_enable_port(monkeypatch):
- client = MagicMock()
- monkeypatch.setattr(port, "LightningClient", MagicMock(return_value=client))
-
- assert _find_lit_app_port(5701) == 5701
-
- resp = MagicMock()
- lit_app = MagicMock()
- lit_app.id = "a"
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=False),
- ]
- resp.lightningapps = [lit_app]
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = resp
-
- monkeypatch.setenv("LIGHTNING_CLOUD_APP_ID", "a")
- monkeypatch.setenv("LIGHTNING_CLOUD_PROJECT_ID", "a")
- monkeypatch.setenv("ENABLE_MULTIPLE_WORKS_IN_DEFAULT_CONTAINER", "1")
-
- assert enable_port()
-
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=True),
- ]
-
- with pytest.raises(RuntimeError, match="No available port was found. Please"):
- assert enable_port()
-
-
-def test_disable_port(monkeypatch):
- client = MagicMock()
- monkeypatch.setattr(port, "LightningClient", MagicMock(return_value=client))
-
- assert _find_lit_app_port(5701) == 5701
-
- resp = MagicMock()
- lit_app = MagicMock()
- lit_app.id = "a"
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=False),
- ]
- resp.lightningapps = [lit_app]
- client.lightningapp_instance_service_list_lightningapp_instances.return_value = resp
-
- monkeypatch.setenv("LIGHTNING_CLOUD_APP_ID", "a")
- monkeypatch.setenv("LIGHTNING_CLOUD_PROJECT_ID", "a")
- monkeypatch.setenv("ENABLE_MULTIPLE_WORKS_IN_DEFAULT_CONTAINER", "1")
-
- disable_port(0)
- assert not lit_app.spec.network_config[0].enable
-
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=False),
- ]
-
- with pytest.raises(RuntimeError, match="The port 1 was already disabled."):
- disable_port(1, ignore_disabled=False)
-
- lit_app.spec.network_config = [
- V1NetworkConfig(host="a", port=0, enable=True),
- V1NetworkConfig(host="a", port=1, enable=False),
- ]
-
- with pytest.raises(ValueError, match="[0, 1]"):
- assert disable_port(10)
diff --git a/tests/tests_app/utilities/test_proxies.py b/tests/tests_app/utilities/test_proxies.py
deleted file mode 100644
index 1c883f935004b..0000000000000
--- a/tests/tests_app/utilities/test_proxies.py
+++ /dev/null
@@ -1,790 +0,0 @@
-import contextlib
-import logging
-import os
-import pathlib
-import sys
-import time
-import traceback
-from copy import deepcopy
-from queue import Empty
-from unittest import mock
-from unittest.mock import MagicMock, Mock
-
-import pytest
-from deepdiff import DeepDiff, Delta
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.runners import MultiProcessRuntime
-from lightning.app.storage import Drive
-from lightning.app.storage.path import Path, _artifacts_path
-from lightning.app.storage.requests import _GetRequest
-from lightning.app.testing.helpers import EmptyFlow, _MockQueue
-from lightning.app.utilities.component import _convert_paths_after_init
-from lightning.app.utilities.enum import AppStage, CacheCallsKeys, WorkFailureReasons, WorkStageStatus
-from lightning.app.utilities.exceptions import CacheMissException, ExitAppException
-from lightning.app.utilities.proxies import (
- ComponentDelta,
- LightningWorkSetAttrProxy,
- ProxyWorkRun,
- WorkRunner,
- WorkStateObserver,
- persist_artifacts,
-)
-
-logger = logging.getLogger(__name__)
-
-
-class Work(LightningWork):
- def __init__(self, cache_calls=True, parallel=True):
- super().__init__(cache_calls=cache_calls, parallel=parallel)
- self.counter = 0
-
- def run(self):
- self.counter = 1
- return 1
-
-
-def test_lightning_work_setattr():
- """This test valides that the `LightningWorkSetAttrProxy` would push a delta to the `caller_queue` everytime an
- attribute from the work state is being changed."""
-
- w = Work()
- # prepare
- w._name = "root.b"
- # create queue
- caller_queue = _MockQueue("caller_queue")
-
- def proxy_setattr():
- w._setattr_replacement = LightningWorkSetAttrProxy(w._name, w, caller_queue, MagicMock())
-
- proxy_setattr()
- w.run()
- assert len(caller_queue) == 1
- work_proxy_output = caller_queue._queue[0]
- assert isinstance(work_proxy_output, ComponentDelta)
- assert work_proxy_output.id == w._name
- assert work_proxy_output.delta.to_dict() == {"values_changed": {"root['vars']['counter']": {"new_value": 1}}}
-
-
-@pytest.mark.parametrize(
- ("parallel", "cache_calls"),
- [
- (True, True),
- (True, False),
- (False, True),
- pytest.param(False, False, marks=pytest.mark.xfail(strict=False, reason="failing...")), # fixme
- ],
-)
-@mock.patch("lightning.app.utilities.proxies._Copier", MagicMock())
-@pytest.mark.flaky(reruns=3)
-@pytest.mark.xfail(sys.platform == "win32", strict=False, reason="Fix this on Windows") # TODO @ethanwharris
-def test_work_runner(parallel, cache_calls, *_):
- """This test validates the `WorkRunner` runs the work.run method and properly populates the `delta_queue`,
- `error_queue` and `readiness_queue`."""
-
- class Work(LightningWork):
- def __init__(self, cache_calls=True, parallel=True):
- super().__init__(cache_calls=cache_calls, parallel=parallel)
- self.counter = 0
- self.dummy_path = "lit://test"
-
- def run(self):
- self.counter = 1
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = Work(cache_calls=cache_calls, parallel=parallel)
-
- def run(self):
- pass
-
- class BlockingQueue(_MockQueue):
- """A Mock for the file copier queues that keeps blocking until we want to end the thread."""
-
- keep_blocking = True
-
- def get(self, timeout: int = 0):
- while BlockingQueue.keep_blocking:
- pass
- # A dummy request so the Copier gets something to process without an error
- return _GetRequest(source="src", name="dummy_path", path="test", hash="123", destination="dst")
-
- app = LightningApp(Flow())
- work = app.root.w
- caller_queue = _MockQueue("caller_queue")
- delta_queue = _MockQueue("delta_queue")
- readiness_queue = _MockQueue("readiness_queue")
- error_queue = _MockQueue("error_queue")
- request_queue = _MockQueue("request_queue")
- response_queue = _MockQueue("response_queue")
- copy_request_queue = BlockingQueue("copy_request_queue")
- copy_response_queue = BlockingQueue("copy_response_queue")
-
- call_hash = "run:fe3fa0f34fc1317e152e5afb023332995392071046f1ea51c34c7c9766e3676c"
- work._calls[call_hash] = {
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "run_started_counter": 1,
- "statuses": [],
- }
- caller_queue.put({
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "state": work.state,
- })
- work_runner = WorkRunner(
- work,
- work.name,
- caller_queue,
- delta_queue,
- readiness_queue,
- error_queue,
- request_queue,
- response_queue,
- copy_request_queue,
- copy_response_queue,
- )
- with contextlib.suppress(Empty, Exception):
- work_runner()
-
- if parallel:
- assert isinstance(error_queue._queue[0], Exception)
- else:
- assert isinstance(error_queue._queue[0], Empty)
- assert len(delta_queue._queue) in [3, 4, 5]
- res = delta_queue._queue[1].delta.to_dict()["iterable_item_added"]
- assert res[f"root['calls']['{call_hash}']['statuses'][0]"]["stage"] == "running"
- assert delta_queue._queue[2].delta.to_dict() == {
- "values_changed": {"root['vars']['counter']": {"new_value": 1}}
- }
- index = 4 if len(delta_queue._queue) == 5 else 2
- res = delta_queue._queue[index].delta.to_dict()["dictionary_item_added"]
- assert res[f"root['calls']['{call_hash}']['ret']"] is None
-
- # Stop blocking and let the thread join
- BlockingQueue.keep_blocking = False
- work_runner.copier.join()
-
-
-def test_pathlike_as_argument_to_run_method_warns(tmpdir):
- """Test that Lightning Produces a special warning for strings that look like paths."""
- # all these paths are not proper paths or don't have a file or folder that exists
- no_warning_expected = (
- "looks/like/path",
- pathlib.Path("looks/like/path"),
- "i am not a path",
- 1,
- Path("lightning/path"),
- )
- for path in no_warning_expected:
- _pass_path_argument_to_work_and_test_warning(path=path, warning_expected=False)
-
- # warn if it looks like a folder and the folder exists
- _pass_path_argument_to_work_and_test_warning(path=tmpdir, warning_expected=True)
-
- # warn if it looks like a string or pathlib Path and the file exists
- file = pathlib.Path(tmpdir, "file_exists.txt")
- file.write_text("test")
- assert os.path.exists(file)
- _pass_path_argument_to_work_and_test_warning(path=file, warning_expected=True)
- _pass_path_argument_to_work_and_test_warning(path=str(file), warning_expected=True)
-
- # do not warn if the path is wrapped in Lightning Path (and the file exists)
- file = Path(tmpdir, "file_exists.txt")
- file.write_text("test")
- assert os.path.exists(file)
- _pass_path_argument_to_work_and_test_warning(path=file, warning_expected=False)
-
-
-def _pass_path_argument_to_work_and_test_warning(path, warning_expected):
- class WarnRunPathWork(LightningWork):
- def run(self, *args, **kwargs):
- pass
-
- class Flow(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.work = WarnRunPathWork()
-
- flow = Flow()
- work = flow.work
- proxy_run = ProxyWorkRun(work.run, "some", work, Mock())
-
- warn_ctx = pytest.warns(UserWarning, match="You passed a the value") if warning_expected else pytest.warns(None)
- with warn_ctx as record, pytest.raises(CacheMissException):
- proxy_run(path)
-
- assert warning_expected or all("You passed a the value" not in str(msg.message) for msg in record)
-
-
-class WorkTimeout(LightningWork):
- def __init__(self):
- super().__init__(parallel=True, start_with_flow=False)
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class FlowTimeout(LightningFlow):
- def __init__(self):
- super().__init__()
- self.counter = 0
- self.work = WorkTimeout()
-
- def run(self):
- if not self.work.has_started:
- self.work.run()
- if self.work.has_timeout:
- self.stop()
-
-
-class WorkRunnerPatch(WorkRunner):
- counter = 0
-
- def __call__(self):
- call_hash = "fe3fa0f"
- while True:
- try:
- called = self.caller_queue.get()
- self.work.set_state(called["state"])
- state = deepcopy(self.work.state)
- self.work._calls[call_hash]["statuses"].append({
- "name": self.work.name,
- "stage": WorkStageStatus.FAILED,
- "reason": WorkFailureReasons.TIMEOUT,
- "timestamp": time.time(),
- "message": None,
- })
- self.delta_queue.put(
- ComponentDelta(id=self.work_name, delta=Delta(DeepDiff(state, self.work.state, verbose_level=2)))
- )
- self.counter += 1
- except Exception as ex:
- logger.error(traceback.format_exc())
- self.error_queue.put(ex)
- raise ExitAppException
-
-
-@mock.patch("lightning.app.runners.backends.mp_process.WorkRunner", WorkRunnerPatch)
-def test_proxy_timeout():
- app = LightningApp(FlowTimeout(), log_level="debug")
- MultiProcessRuntime(app, start_server=False).dispatch()
-
- call_hash = app.root.work._calls[CacheCallsKeys.LATEST_CALL_HASH]
- assert len(app.root.work._calls[call_hash]["statuses"]) == 3
- assert app.root.work._calls[call_hash]["statuses"][0]["stage"] == "pending"
- assert app.root.work._calls[call_hash]["statuses"][1]["stage"] == "failed"
- assert app.root.work._calls[call_hash]["statuses"][2]["stage"] == "stopped"
-
-
-@mock.patch("lightning.app.utilities.proxies._Copier")
-def test_path_argument_to_transfer(*_):
- """Test that any Lightning Path objects passed to the run method get transferred automatically (if they exist)."""
-
- class TransferPathWork(LightningWork):
- def run(self, *args, **kwargs):
- raise ExitAppException
-
- work = TransferPathWork()
-
- path1 = Path("exists-locally.txt")
- path2 = Path("exists-remotely.txt")
- path3 = Path("exists-nowhere.txt")
-
- path1.get = Mock()
- path2.get = Mock()
- path3.get = Mock()
-
- path1.exists_remote = Mock(return_value=False)
- path2.exists_remote = Mock(return_value=True)
- path3.exists_remote = Mock(return_value=False)
-
- path1._origin = "origin"
- path2._origin = "origin"
- path3._origin = "origin"
-
- call = {
- "args": (path1, path2),
- "kwargs": {"path3": path3},
- "call_hash": "any",
- "state": {
- "vars": {"_paths": {}, "_urls": {}},
- "calls": {
- CacheCallsKeys.LATEST_CALL_HASH: "any",
- "any": {
- "name": "run",
- "call_hash": "any",
- "use_args": False,
- "statuses": [{"stage": "requesting", "message": None, "reason": None, "timestamp": 1}],
- },
- },
- "changes": {},
- },
- }
-
- caller_queue = _MockQueue()
- caller_queue.put(call)
-
- runner = WorkRunner(
- work=work,
- work_name="name",
- caller_queue=caller_queue,
- delta_queue=_MockQueue(),
- readiness_queue=_MockQueue(),
- error_queue=_MockQueue(),
- request_queue=_MockQueue(),
- response_queue=_MockQueue(),
- copy_request_queue=_MockQueue(),
- copy_response_queue=_MockQueue(),
- )
-
- with contextlib.suppress(ExitAppException):
- runner()
-
- path1.exists_remote.assert_called_once()
- path1.get.assert_not_called()
-
- path2.exists_remote.assert_called_once()
- path2.get.assert_called_once()
-
- path3.exists_remote.assert_called()
- path3.get.assert_not_called()
-
-
-@pytest.mark.parametrize(
- ("origin", "exists_remote", "expected_get"),
- [
- (None, False, False),
- ("root.work", True, False),
- ("root.work", False, False),
- ("origin", True, True),
- ],
-)
-@mock.patch("lightning.app.utilities.proxies._Copier")
-def test_path_attributes_to_transfer(_, origin, exists_remote, expected_get):
- """Test that any Lightning Path objects passed to the run method get transferred automatically (if they exist)."""
- path_mock = Mock()
- path_mock.origin_name = origin
- path_mock.exists_remote = Mock(return_value=exists_remote)
-
- class TransferPathWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.path = Path("test-path.txt")
-
- def run(self):
- raise ExitAppException
-
- def __getattr__(self, item):
- if item == "path":
- return path_mock
- return super().__getattr__(item)
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.work = TransferPathWork()
-
- def run(self):
- self.work.run()
-
- flow = Flow()
- _convert_paths_after_init(flow)
-
- call = {
- "args": (),
- "kwargs": {},
- "call_hash": "any",
- "state": {
- "vars": {"_paths": flow.work._paths, "_urls": {}},
- "calls": {
- CacheCallsKeys.LATEST_CALL_HASH: "any",
- "any": {
- "name": "run",
- "call_hash": "any",
- "use_args": False,
- "statuses": [{"stage": "requesting", "message": None, "reason": None, "timestamp": 1}],
- },
- },
- "changes": {},
- },
- }
-
- caller_queue = _MockQueue()
- caller_queue.put(call)
-
- runner = WorkRunner(
- work=flow.work,
- work_name=flow.work.name,
- caller_queue=caller_queue,
- delta_queue=_MockQueue(),
- readiness_queue=_MockQueue(),
- error_queue=_MockQueue(),
- request_queue=_MockQueue(),
- response_queue=_MockQueue(),
- copy_request_queue=_MockQueue(),
- copy_response_queue=_MockQueue(),
- )
- with contextlib.suppress(ExitAppException):
- runner()
-
- assert path_mock.get.call_count == expected_get
-
-
-def test_proxy_work_run_paths_replace_origin_lightning_work_by_their_name():
- class Work(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.path = None
-
- def run(self, path):
- assert isinstance(path._origin, str)
-
- class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w1 = Work()
- self.w = Work()
-
- def run(self):
- pass
-
- app = LightningApp(Flow())
- work = app.root.w
- caller_queue = _MockQueue("caller_queue")
- app.root.w1.path = Path(__file__)
- assert app.root.w1.path._origin == app.root.w1
- ProxyWorkRun(work.run, work.name, work, caller_queue)(path=app.root.w1.path)
- assert caller_queue._queue[0]["kwargs"]["path"]._origin == app.root.w1.name
-
-
-def test_persist_artifacts(tmp_path):
- """Test that the `persist_artifacts` utility copies the artifacts that exist to the persistent storage."""
-
- class ArtifactWork(LightningWork):
- def __init__(self):
- super().__init__()
- self.file = None
- self.folder = None
- self.not_my_path = None
- self.not_exists = None
-
- def run(self):
- # single file
- self.file = Path(tmp_path, "file.txt")
- self.file.write_text("single file")
- # folder with files
- self.folder = Path(tmp_path, "folder")
- self.folder.mkdir()
- Path(tmp_path, "folder", "file1.txt").write_text("file 1")
- Path(tmp_path, "folder", "file2.txt").write_text("file 2")
-
- # simulate a Path that was synced to this Work from another Work
- self.not_my_path = Path(tmp_path, "external.txt")
- self.not_my_path.touch()
- self.not_my_path._origin = Mock()
-
- self.not_exists = Path(tmp_path, "not-exists")
-
- work = ArtifactWork()
- work._name = "root.work"
-
- rel_tmpdir_path = Path(*tmp_path.parts[1:])
-
- assert not os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "file.txt")
- assert not os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "folder")
- assert not os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "not-exists")
-
- work.run()
-
- with pytest.warns(UserWarning, match="1 artifacts could not be saved because they don't exist"):
- persist_artifacts(work)
-
- assert os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "file.txt")
- assert os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "folder")
- assert not os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "not-exists")
- assert not os.path.exists(_artifacts_path(work) / rel_tmpdir_path / "external.txt")
-
-
-def test_work_state_observer():
- """Tests that the WorkStateObserver sends deltas to the queue when state residuals remain that haven't been handled
- by the setattr."""
-
- class WorkWithoutSetattr(LightningWork):
- def __init__(self):
- super().__init__()
- self.var = 1
- self.list = []
- self.dict = {"counter": 0}
-
- def run(self, use_setattr=False, use_containers=False):
- if use_setattr:
- self.var += 1
- if use_containers:
- self.list.append(1)
- self.dict["counter"] += 1
-
- work = WorkWithoutSetattr()
- delta_queue = _MockQueue()
- observer = WorkStateObserver(work, delta_queue)
- setattr_proxy = LightningWorkSetAttrProxy(
- work=work,
- work_name="work_name",
- delta_queue=delta_queue,
- state_observer=observer,
- )
- work._setattr_replacement = setattr_proxy
-
- ##############################
- # 1. Simulate no state changes
- ##############################
- work.run(use_setattr=False, use_containers=False)
- assert len(delta_queue) == 0
-
- ############################
- # 2. Simulate a setattr call
- ############################
- work.run(use_setattr=True, use_containers=False)
-
- # this is necessary only in this test where we simulate the calls
- work._calls.clear()
- work._calls.update({CacheCallsKeys.LATEST_CALL_HASH: None})
-
- delta = delta_queue.get().delta.to_dict()
- assert delta["values_changed"] == {"root['vars']['var']": {"new_value": 2}}
- assert len(observer._delta_memory) == 1
-
- # The observer should not trigger any deltas being sent and only consume the delta memory
- assert len(delta_queue) == 0
- observer.run_once()
- assert len(delta_queue) == 0
- assert not observer._delta_memory
-
- ################################
- # 3. Simulate a container update
- ################################
- work.run(use_setattr=False, use_containers=True)
- assert len(delta_queue) == 0
- assert not observer._delta_memory
- observer.run_once()
- observer.run_once() # multiple runs should not affect how many deltas are sent unless there are changes
- delta = delta_queue.get().delta.to_dict()
- assert delta["values_changed"] == {"root['vars']['dict']['counter']": {"new_value": 1}}
- assert delta["iterable_item_added"] == {"root['vars']['list'][0]": 1}
-
- ##########################
- # 4. Simulate both updates
- ##########################
- work.run(use_setattr=True, use_containers=True)
-
- # this is necessary only in this test where we siumulate the calls
- work._calls.clear()
- work._calls.update({CacheCallsKeys.LATEST_CALL_HASH: None})
-
- delta = delta_queue.get().delta.to_dict()
- assert delta == {"values_changed": {"root['vars']['var']": {"new_value": 3}}}
- assert len(delta_queue) == 0
- assert len(observer._delta_memory) == 1
- observer.run_once()
-
- delta = delta_queue.get().delta.to_dict()
- assert delta["values_changed"] == {"root['vars']['dict']['counter']": {"new_value": 2}}
- assert delta["iterable_item_added"] == {"root['vars']['list'][1]": 1}
-
- assert len(delta_queue) == 0
- assert not observer._delta_memory
-
-
-class WorkState(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.vars = []
- self.counter = 0
-
- def run(self, *args):
- for counter in range(1, 11):
- self.vars.append(counter)
- self.counter = counter
-
-
-class FlowState(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkState()
- self.counter = 1
-
- def run(self):
- self.w.run()
- if self.counter == 1:
- if len(self.w.vars) == 10 and self.w.counter == 10:
- self.w.vars = []
- self.w.counter = 0
- self.w.run("")
- self.counter = 2
- elif self.counter == 2 and len(self.w.vars) == 10 and self.w.counter == 10:
- self.stop()
-
-
-def test_state_observer():
- app = LightningApp(FlowState())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-@pytest.mark.parametrize(
- ("patch_constants", "environment", "expected_public_ip", "expected_private_ip"),
- [
- ({}, {}, "", "127.0.0.1"),
- ({"LIGHTNING_CLOUDSPACE_HOST": "any"}, {}, "", "0.0.0.0"), # noqa: S104
- (
- {},
- {"LIGHTNING_NODE_IP": "85.44.2.25", "LIGHTNING_NODE_PRIVATE_IP": "10.10.10.5"},
- "85.44.2.25",
- "10.10.10.5",
- ),
- ],
- indirect=["patch_constants"],
-)
-def test_work_runner_sets_public_and_private_ip(patch_constants, environment, expected_public_ip, expected_private_ip):
- """Test that the WorkRunner updates the public and private address as soon as the Work starts running."""
-
- class Work(LightningWork):
- def run(self):
- pass
-
- work = Work()
- work_runner = WorkRunner(
- work,
- work.name,
- caller_queue=_MockQueue("caller_queue"),
- delta_queue=Mock(),
- readiness_queue=Mock(),
- error_queue=Mock(),
- request_queue=Mock(),
- response_queue=Mock(),
- copy_request_queue=Mock(),
- copy_response_queue=Mock(),
- enable_copier=False,
- )
-
- # Make a fake call
- call_hash = "run:fe3fa0f34fc1317e152e5afb023332995392071046f1ea51c34c7c9766e3676c"
- work._calls[call_hash] = {
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "run_started_counter": 1,
- "statuses": [],
- }
- work_runner.caller_queue.put({
- "args": (),
- "kwargs": {},
- "call_hash": call_hash,
- "state": work.state,
- })
-
- with mock.patch.dict(os.environ, environment, clear=True):
- work_runner.setup()
- assert work.public_ip == expected_public_ip
- assert work.internal_ip == expected_private_ip
-
-
-class WorkBi(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.finished = False
- self.counter = 0
- self.counter_2 = 0
-
- def run(self):
- while not self.finished:
- self.counter_2 += 1
- time.sleep(0.1)
- self.counter = -1
- time.sleep(1)
-
-
-class FlowBi(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkBi()
-
- def run(self):
- self.w.run()
- if not self.w.finished:
- self.w.counter += 1
- if self.w.counter > 3:
- self.w.finished = True
- if self.w.counter == -1 and self.w.has_succeeded:
- self.stop()
-
-
-def test_bi_directional_proxy():
- app = LightningApp(FlowBi())
- MultiProcessRuntime(app, start_server=False).dispatch()
-
-
-class WorkBi2(LightningWork):
- def __init__(self):
- super().__init__(parallel=True)
- self.finished = False
- self.counter = 0
- self.d = {}
-
- def run(self):
- self.counter -= 1
- while not self.finished:
- self.counter -= 1
- time.sleep(1)
-
-
-class FlowBi2(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w = WorkBi2()
-
- def run(self):
- self.w.run()
- if self.w.counter == 1:
- self.w.d["self.w.counter"] = 0
- if not self.w.finished:
- self.w.counter += 1
-
-
-def test_bi_directional_proxy_forbidden(monkeypatch):
- mock = MagicMock()
- monkeypatch.setattr(sys, "exit", mock)
- app = LightningApp(FlowBi2())
- MultiProcessRuntime(app, start_server=False).dispatch()
- assert app.stage == AppStage.FAILED
- assert "A forbidden operation to update the work" in str(app.exception)
-
-
-class WorkDrive(LightningFlow):
- def __init__(self, drive):
- super().__init__()
- self.drive = drive
- self.path = Path("data")
-
- def run(self):
- pass
-
-
-class FlowDrive(LightningFlow):
- def __init__(self):
- super().__init__()
- self.data = Drive("lit://data")
- self.counter = 0
-
- def run(self):
- if not hasattr(self, "w"):
- self.w = WorkDrive(self.data)
- self.counter += 1
-
-
-def test_bi_directional_proxy_filtering():
- app = LightningApp(FlowDrive())
- app.root.run()
- assert app._extract_vars_from_component_name(app.root.w.name, app.state) == {}
diff --git a/tests/tests_app/utilities/test_safe_pickle.py b/tests/tests_app/utilities/test_safe_pickle.py
deleted file mode 100644
index 2c9e6d49a2448..0000000000000
--- a/tests/tests_app/utilities/test_safe_pickle.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import subprocess
-from pathlib import Path
-
-
-def test_safe_pickle_app():
- test_dir = Path(__file__).parent / "testdata"
- proc = subprocess.Popen(
- ["lightning_app", "run", "app", "safe_pickle_app.py", "--open-ui", "false"],
- stdout=subprocess.PIPE,
- cwd=test_dir,
- )
- stdout, _ = proc.communicate()
- assert "Exiting the pickling app successfully" in stdout.decode("UTF-8")
diff --git a/tests/tests_app/utilities/test_secrets.py b/tests/tests_app/utilities/test_secrets.py
deleted file mode 100644
index 1d28af4531b1a..0000000000000
--- a/tests/tests_app/utilities/test_secrets.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from typing import Dict, List
-from unittest import mock
-from unittest.mock import MagicMock
-
-import lightning.app
-import pytest
-from lightning.app.utilities.secrets import _names_to_ids
-from lightning_cloud.openapi import V1ListMembershipsResponse, V1ListSecretsResponse, V1Membership, V1Secret
-
-
-@pytest.mark.parametrize(
- ("secret_names", "secrets", "expected", "expected_exception"),
- [
- ([], [], {}, False),
- (
- ["first-secret", "second-secret"],
- [
- V1Secret(name="first-secret", id="1234"),
- ],
- {},
- True,
- ),
- (
- ["first-secret", "second-secret"],
- [V1Secret(name="first-secret", id="1234"), V1Secret(name="second-secret", id="5678")],
- {"first-secret": "1234", "second-secret": "5678"},
- False,
- ),
- ],
-)
-@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
-def test_names_to_ids(
- secret_names: List[str],
- secrets: List[V1Secret],
- expected: Dict[str, str],
- expected_exception: bool,
- monkeypatch,
-):
- class FakeLightningClient:
- def projects_service_list_memberships(self, *_, **__):
- return V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
-
- def secret_service_list_secrets(self, *_, **__):
- return V1ListSecretsResponse(secrets=secrets)
-
- monkeypatch.setattr(lightning.app.utilities.secrets, "LightningClient", FakeLightningClient)
-
- if expected_exception:
- with pytest.raises(ValueError):
- _names_to_ids(secret_names)
- else:
- assert _names_to_ids(secret_names) == expected
diff --git a/tests/tests_app/utilities/test_state.py b/tests/tests_app/utilities/test_state.py
deleted file mode 100644
index ec96c17339375..0000000000000
--- a/tests/tests_app/utilities/test_state.py
+++ /dev/null
@@ -1,335 +0,0 @@
-import os
-from re import escape
-from unittest import mock
-
-import lightning.app
-import pytest
-import requests
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.structures import Dict, List
-from lightning.app.utilities.app_helpers import AppStatePlugin, BaseStatePlugin
-from lightning.app.utilities.state import AppState
-from lightning_cloud.openapi import Externalv1LightningappInstance, V1LightningappInstanceStatus
-
-
-@mock.patch("lightning.app.utilities.state._configure_session", return_value=requests)
-def test_app_state_not_connected(_):
- """Test an error message when a disconnected AppState tries to access attributes."""
- state = AppState(port=8000)
- with pytest.raises(AttributeError, match="Failed to connect and fetch the app state"):
- _ = state.value
- with pytest.raises(AttributeError, match="Failed to connect and fetch the app state"):
- state.value = 1
-
-
-@pytest.mark.parametrize(
- ("my_affiliation", "global_affiliation", "expected"),
- [
- (None, (), ()),
- ((), (), ()),
- ((), ("a", "b"), ()),
- (None, ("a", "b"), ("a", "b")),
- ],
-)
-@mock.patch("lightning.app.utilities.state._configure_session", return_value=requests)
-def test_app_state_affiliation(_, my_affiliation, global_affiliation, expected):
- AppState._MY_AFFILIATION = global_affiliation
- state = AppState(my_affiliation=my_affiliation)
- assert state._my_affiliation == expected
- AppState._MY_AFFILIATION = ()
-
-
-def test_app_state_state_access():
- """Test the many ways an AppState object can be accessed to set or get attributes on the state."""
- mocked_state = {
- "vars": {"root_var": "root"},
- "flows": {
- "child0": {
- "vars": {"child_var": 1},
- "flows": {},
- "works": {},
- }
- },
- "works": {
- "work0": {
- "vars": {"work_var": 2},
- "flows": {},
- "works": {},
- }
- },
- }
-
- state = AppState()
- state._state = state._last_state = mocked_state
-
- assert state.root_var == "root"
- assert isinstance(state.child0, AppState)
- assert isinstance(state.work0, AppState)
- assert state.child0.child_var == 1
- assert state.work0.work_var == 2
-
- with pytest.raises(AttributeError, match="Failed to access 'non_existent_var' through `AppState`."):
- _ = state.work0.non_existent_var
-
- with pytest.raises(AttributeError, match="Failed to access 'non_existent_var' through `AppState`."):
- state.work0.non_existent_var = 22
-
- # TODO: improve msg
- with pytest.raises(AttributeError, match="You shouldn't set the flows"):
- state.child0 = "child0"
-
- # TODO: verify with tchaton
- with pytest.raises(AttributeError, match="You shouldn't set the works"):
- state.work0 = "work0"
-
-
-@mock.patch("lightning.app.utilities.state.AppState.send_delta")
-def test_app_state_state_access_under_affiliation(*_):
- """Test the access to attributes when the state is restricted under the given affiliation."""
- mocked_state = {
- "vars": {"root_var": "root"},
- "flows": {
- "child0": {
- "vars": {"child0_var": 0},
- "flows": {
- "child1": {
- "vars": {"child1_var": 1},
- "flows": {
- "child2": {
- "vars": {"child2_var": 2},
- "flows": {},
- "works": {},
- },
- },
- "works": {},
- },
- },
- "works": {
- "work1": {
- "vars": {"work1_var": 11},
- },
- },
- },
- },
- "works": {},
- }
-
- # root-level affiliation
- state = AppState(my_affiliation=())
- state._store_state(mocked_state)
- assert isinstance(state.child0, AppState)
- assert state.child0.child0_var == 0
- assert state.child0.child1.child1_var == 1
- assert state.child0.child1.child2.child2_var == 2
-
- # one child deep
- state = AppState(my_affiliation=("child0",))
- state._store_state(mocked_state)
- assert state._state == mocked_state["flows"]["child0"]
- with pytest.raises(AttributeError, match="Failed to access 'child0' through `AppState`"):
- _ = state.child0
- assert state.child0_var == 0
- assert state.child1.child1_var == 1
- assert state.child1.child2.child2_var == 2
-
- # two flows deep
- state = AppState(my_affiliation=("child0", "child1"))
- state._store_state(mocked_state)
- assert state._state == mocked_state["flows"]["child0"]["flows"]["child1"]
- with pytest.raises(AttributeError, match="Failed to access 'child1' through `AppState`"):
- _ = state.child1
- state.child1_var = 111
- assert state.child1_var == 111
- assert state.child2.child2_var == 2
-
- # access to work
- state = AppState(my_affiliation=("child0", "work1"))
- state._store_state(mocked_state)
- assert state._state == mocked_state["flows"]["child0"]["works"]["work1"]
- with pytest.raises(AttributeError, match="Failed to access 'child1' through `AppState`"):
- _ = state.child1
- assert state.work1_var == 11
- state.work1_var = 111
- assert state.work1_var == 111
-
- # affiliation does not match state
- state = AppState(my_affiliation=("child1", "child0"))
- with pytest.raises(
- ValueError, match=escape("Failed to extract the state under the affiliation '('child1', 'child0')'")
- ):
- state._store_state(mocked_state)
-
-
-def test_app_state_repr():
- app_state = AppState()
- assert repr(app_state) == "None"
-
- app_state = AppState()
- app_state._store_state({"vars": {"x": 1, "y": 2}})
- assert repr(app_state) == "{'vars': {'x': 1, 'y': 2}}"
-
- app_state = AppState()
- app_state._store_state({"vars": {"x": 1, "y": 2}})
- assert repr(app_state.y) == "2"
-
- app_state = AppState()
- app_state._store_state({"vars": {}, "flows": {"child": {"vars": {"child_var": "child_val"}}}})
- assert repr(app_state.child) == "{'vars': {'child_var': 'child_val'}}"
-
-
-def test_app_state_bool():
- app_state = AppState()
- assert not bool(app_state)
-
- app_state = AppState()
- app_state._store_state({"vars": {"x": 1, "y": 2}})
- assert bool(app_state)
-
-
-class _CustomAppStatePlugin(BaseStatePlugin):
- def should_update_app(self, deep_diff):
- pass
-
- def get_context(self):
- pass
-
- def render_non_authorized(self):
- pass
-
-
-def test_attach_plugin():
- """Test how plugins get attached to the AppState and the default behavior when no plugin is specified."""
- app_state = AppState()
- assert isinstance(app_state._plugin, AppStatePlugin)
-
- app_state = AppState(plugin=_CustomAppStatePlugin())
- assert isinstance(app_state._plugin, _CustomAppStatePlugin)
-
-
-@mock.patch("lightning.app.utilities.state._configure_session", return_value=requests)
-def test_app_state_connection_error(_):
- """Test an error message when a connection to retrieve the state can't be established."""
- app_state = AppState(port=8000)
- with pytest.raises(AttributeError, match=r"Failed to connect and fetch the app state\. Is the app running?"):
- app_state._request_state()
-
- with pytest.raises(AttributeError, match=r"Failed to connect and fetch the app state\. Is the app running?"):
- app_state.var = 1
-
-
-class Work(LightningWork):
- def __init__(self):
- super().__init__()
- self.counter = 0
-
- def run(self):
- self.counter += 1
-
-
-class Flow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.should_start = False
- self.w = Work()
-
- def run(self):
- if self.should_start:
- self.w.run()
- self.stop()
-
-
-class MockResponse:
- def __init__(self, state, status_code):
- self._state = state
- self.status_code = status_code
-
- def json(self):
- return self._state
-
-
-def test_get_send_request(monkeypatch):
- app = LightningApp(Flow())
- monkeypatch.setattr(lightning.app.utilities.state, "_configure_session", mock.MagicMock())
-
- state = AppState(plugin=AppStatePlugin())
- state._session.get._mock_return_value = MockResponse(app.state_with_changes, 500)
- state._request_state()
- state._session.get._mock_return_value = MockResponse(app.state_with_changes, 200)
- state._request_state()
- assert state._my_affiliation == ()
- with pytest.raises(Exception, match="The response from"):
- state._session.post._mock_return_value = MockResponse(app.state_with_changes, 500)
- state.w.counter = 1
- state._session.post._mock_return_value = MockResponse(app.state_with_changes, 200)
- state.w.counter = 1
-
-
-@mock.patch.dict(
- os.environ,
- {
- "LIGHTNING_APP_STATE_URL": "https://lightning-cloud.com",
- "LIGHTNING_CLOUD_PROJECT_ID": "test-project-id",
- "LIGHTNING_CLOUD_APP_ID": "test-app-id",
- },
-)
-@mock.patch("lightning.app.utilities.state.LightningClient")
-def test_app_state_with_env_var(mock_client):
- mock_client().lightningapp_instance_service_get_lightningapp_instance.return_value = Externalv1LightningappInstance(
- status=V1LightningappInstanceStatus(ip_address="test-ip"),
- )
- state = AppState()
- url = state._url
-
- mock_client().lightningapp_instance_service_get_lightningapp_instance.assert_called_once_with(
- "test-project-id",
- "test-app-id",
- )
-
- assert url == "http://test-ip:8080"
- assert not state._port
-
-
-@mock.patch.dict(os.environ, {})
-def test_app_state_with_no_env_var(**__):
- state = AppState()
- assert state._host == "http://127.0.0.1"
- assert state._port == 7501
- assert state._url == "http://127.0.0.1:7501"
-
-
-class FlowStructures(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_list = List(Work(), Work())
- self.w_dict = Dict(**{"toto": Work(), "toto_2": Work()})
-
- def run(self):
- self.stop()
-
-
-class FlowStructuresEmpty(LightningFlow):
- def __init__(self):
- super().__init__()
- self.w_list = List()
- self.w_dict = Dict()
-
- def run(self):
- self.stop()
-
-
-def test_app_state_with_structures():
- app = LightningApp(FlowStructures())
- state = AppState()
- state._last_state = app.state
- state._state = app.state
- assert state.w_list["0"].counter == 0
- assert len(state.w_list) == 2
- assert state.w_dict["toto"].counter == 0
- assert [k for k, _ in state.w_dict.items()] == ["toto", "toto_2"]
- assert [k for k, _ in state.w_list.items()] == ["0", "1"]
-
- app = LightningApp(FlowStructuresEmpty())
- state = AppState()
- state._last_state = app.state
- state._state = app.state
- assert state.w_list
diff --git a/tests/tests_app/utilities/test_tracer.py b/tests/tests_app/utilities/test_tracer.py
deleted file mode 100644
index a48d2ca4e2566..0000000000000
--- a/tests/tests_app/utilities/test_tracer.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import os
-import sys
-
-from lightning.app.testing.helpers import _RunIf
-from lightning.app.utilities.tracer import Tracer
-
-from tests_app import _PROJECT_ROOT
-
-
-@_RunIf(pl=True)
-def test_tracer():
- from pytorch_lightning import Trainer
-
- def pre_fn(self, *args, **kwargs):
- kwargs["fast_dev_run"] = True
- return {}, args, kwargs
-
- def post_fn(self, ret):
- return {}, ret
-
- tracer = Tracer()
- tracer.add_traced(Trainer, "__init__", pre_fn=pre_fn, post_fn=post_fn)
- traced_file = os.path.join(_PROJECT_ROOT, "tests/tests_app/core/scripts/lightning_trainer.py")
- assert os.path.exists(traced_file)
- # This is required to get the right sys.argv for `runpy``.
- sys.argv = [traced_file]
- tracer.trace(traced_file)
diff --git a/tests/tests_app/utilities/test_tree.py b/tests/tests_app/utilities/test_tree.py
deleted file mode 100644
index 62ee29e946f21..0000000000000
--- a/tests/tests_app/utilities/test_tree.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import pytest
-from lightning.app import LightningFlow, LightningWork
-from lightning.app.testing.helpers import EmptyFlow, EmptyWork
-from lightning.app.utilities.tree import breadth_first
-
-
-class LeafFlow(EmptyFlow):
- pass
-
-
-class LeafWork(EmptyWork):
- pass
-
-
-class SimpleFlowTree(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.simple_flow_left = LeafFlow()
- self.simple_flow_right = LeafFlow()
-
-
-class SimpleWorkTree(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.simple_work_left = LeafWork()
- self.simple_work_right = LeafWork()
-
-
-class MixedTree(EmptyFlow):
- def __init__(self):
- super().__init__()
- self.mixed_left = SimpleFlowTree()
- self.work_tree = SimpleWorkTree()
- self.mixed_right = SimpleFlowTree()
-
-
-@pytest.mark.parametrize(
- ("input_tree", "types", "expected_sequence"),
- [
- (LeafFlow(), (LightningFlow,), ["root"]),
- (LeafWork(), (LightningFlow,), []),
- (
- SimpleFlowTree(),
- (LightningFlow,),
- [
- "root",
- "root.simple_flow_left",
- "root.simple_flow_right",
- ],
- ),
- (SimpleWorkTree(), (LightningFlow,), ["root"]),
- (
- SimpleWorkTree(),
- (LightningFlow, LightningWork),
- [
- "root",
- "root.simple_work_left",
- "root.simple_work_right",
- ],
- ),
- (
- MixedTree(),
- (LightningFlow,),
- [
- "root",
- "root.mixed_left",
- "root.mixed_right",
- "root.work_tree",
- "root.mixed_left.simple_flow_left",
- "root.mixed_left.simple_flow_right",
- "root.mixed_right.simple_flow_left",
- "root.mixed_right.simple_flow_right",
- ],
- ),
- (
- MixedTree(),
- (LightningWork,),
- [
- "root.work_tree.simple_work_left",
- "root.work_tree.simple_work_right",
- ],
- ),
- (
- MixedTree(),
- (LightningFlow, LightningWork),
- [
- "root",
- "root.mixed_left",
- "root.mixed_right",
- "root.work_tree",
- "root.mixed_left.simple_flow_left",
- "root.mixed_left.simple_flow_right",
- "root.mixed_right.simple_flow_left",
- "root.mixed_right.simple_flow_right",
- "root.work_tree.simple_work_left",
- "root.work_tree.simple_work_right",
- ],
- ),
- ],
-)
-def test_breadth_first(input_tree, types, expected_sequence):
- assert [node.name for node in breadth_first(input_tree, types=types)] == expected_sequence
diff --git a/tests/tests_app/utilities/testdata/app_commands/app_commands_to_ignore.txt b/tests/tests_app/utilities/testdata/app_commands/app_commands_to_ignore.txt
deleted file mode 100644
index 60ee57ca921a3..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/app_commands_to_ignore.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/python
-#!/usr/local/bin/python
-#!/usr/bin/env python
-#!/usr/bin/env python3
diff --git a/tests/tests_app/utilities/testdata/app_commands/bang_not_at_start_of_line.txt b/tests/tests_app/utilities/testdata/app_commands/bang_not_at_start_of_line.txt
deleted file mode 100644
index a937beff29d1f..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/bang_not_at_start_of_line.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# This is somefile.py! should not execute this!
-# !echo "foo"
diff --git a/tests/tests_app/utilities/testdata/app_commands/command_after_first_non_comment_line.txt b/tests/tests_app/utilities/testdata/app_commands/command_after_first_non_comment_line.txt
deleted file mode 100644
index 1cd80f15779df..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/command_after_first_non_comment_line.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
-# !echo "foo"
-import lighting
-# !echo "bar"
diff --git a/tests/tests_app/utilities/testdata/app_commands/commands_with_mixed_comments_1.txt b/tests/tests_app/utilities/testdata/app_commands/commands_with_mixed_comments_1.txt
deleted file mode 100644
index f98505df2bef6..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/commands_with_mixed_comments_1.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# !echo "foo"
-# some other explanation
-# !echo "bar"
-# another comment
diff --git a/tests/tests_app/utilities/testdata/app_commands/commands_with_mixed_comments_2.txt b/tests/tests_app/utilities/testdata/app_commands/commands_with_mixed_comments_2.txt
deleted file mode 100644
index 0c1641c234f8d..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/commands_with_mixed_comments_2.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# filename.py
-# !echo "foo"
-# some other explanation
-# !echo "bar"
-# another comment
diff --git a/tests/tests_app/utilities/testdata/app_commands/multiple_commands.txt b/tests/tests_app/utilities/testdata/app_commands/multiple_commands.txt
deleted file mode 100644
index 52e45d039fa44..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/multiple_commands.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# !echo "foo"
-# !echo "bar"
diff --git a/tests/tests_app/utilities/testdata/app_commands/multiple_spaces_between_band_and_command.txt b/tests/tests_app/utilities/testdata/app_commands/multiple_spaces_between_band_and_command.txt
deleted file mode 100644
index cf2599bd9ae49..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/multiple_spaces_between_band_and_command.txt
+++ /dev/null
@@ -1 +0,0 @@
-# ! echo "foo"
diff --git a/tests/tests_app/utilities/testdata/app_commands/single_command.txt b/tests/tests_app/utilities/testdata/app_commands/single_command.txt
deleted file mode 100644
index 46ff010622e9c..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/single_command.txt
+++ /dev/null
@@ -1 +0,0 @@
-# !echo "foo"
diff --git a/tests/tests_app/utilities/testdata/app_commands/space_between_bang_and_command.txt b/tests/tests_app/utilities/testdata/app_commands/space_between_bang_and_command.txt
deleted file mode 100644
index d2df15d9c19ec..0000000000000
--- a/tests/tests_app/utilities/testdata/app_commands/space_between_bang_and_command.txt
+++ /dev/null
@@ -1 +0,0 @@
-# ! echo "foo"
diff --git a/tests/tests_app/utilities/testdata/safe_pickle_app.py b/tests/tests_app/utilities/testdata/safe_pickle_app.py
deleted file mode 100644
index 7a02b0d1ade52..0000000000000
--- a/tests/tests_app/utilities/testdata/safe_pickle_app.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-This app tests three things
-1. Can a work pickle `self`
-2. Can the pickled work be unpickled in another work
-3. Can the pickled work be unpickled from a script
-"""
-
-import subprocess
-from pathlib import Path
-
-from lightning.app import LightningApp, LightningFlow, LightningWork
-from lightning.app.utilities import safe_pickle
-
-
-class SelfPicklingWork(LightningWork):
- def run(self):
- with open("work.pkl", "wb") as f:
- safe_pickle.dump(self, f)
-
- def get_test_string(self):
- return f"Hello from {self.__class__.__name__}!"
-
-
-class WorkThatLoadsPickledWork(LightningWork):
- def run(self):
- with open("work.pkl", "rb") as f:
- work = safe_pickle.load(f)
- assert work.get_test_string() == "Hello from SelfPicklingWork!"
-
-
-script_load_pickled_work = """
-import pickle
-work = pickle.load(open("work.pkl", "rb"))
-print(work.get_test_string())
-"""
-
-
-class RootFlow(LightningFlow):
- def __init__(self):
- super().__init__()
- self.self_pickling_work = SelfPicklingWork()
- self.work_that_loads_pickled_work = WorkThatLoadsPickledWork()
-
- def run(self):
- self.self_pickling_work.run()
- self.work_that_loads_pickled_work.run()
-
- with open("script_that_loads_pickled_work.py", "w") as f:
- f.write(script_load_pickled_work)
-
- # read the output from subprocess
- proc = subprocess.Popen(["python", "script_that_loads_pickled_work.py"], stdout=subprocess.PIPE)
- assert "Hello from SelfPicklingWork" in proc.stdout.read().decode("UTF-8")
-
- # deleting the script
- Path("script_that_loads_pickled_work.py").unlink()
- # deleting the pkl file
- Path("work.pkl").unlink()
-
- self.stop("Exiting the pickling app successfully!!")
-
-
-app = LightningApp(RootFlow())
diff --git a/tests/tests_store/__init__.py b/tests/tests_store/__init__.py
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/tests/tests_store/test_store.py b/tests/tests_store/test_store.py
deleted file mode 100644
index 308ccda30c489..0000000000000
--- a/tests/tests_store/test_store.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import os
-from unittest import mock
-
-from lightning.store import download_model, list_models, upload_model
-from lightning_cloud.openapi import (
- V1DownloadModelResponse,
- V1GetUserResponse,
- V1ListMembershipsResponse,
- V1ListModelsResponse,
- V1Membership,
- V1Model,
- V1Project,
- V1UploadModelRequest,
- V1UploadModelResponse,
-)
-
-
-@mock.patch("lightning.store.store._Client")
-@mock.patch("lightning.store.store._upload_file_to_url")
-def test_upload_model(mock_upload_file_to_url, mock_client):
- mock_client = mock_client()
-
- mock_client.auth_service_get_user.return_value = V1GetUserResponse(username="test-username")
-
- # either one of these project APIs could be called
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(project_id="test-project-id")],
- )
- mock_client.projects_service_get_project.return_value = V1Project(id="test-project-id")
-
- mock_client.models_store_upload_model.return_value = V1UploadModelResponse(
- upload_url="https://test",
- )
-
- upload_model("test-model", "test.ckpt", version="0.0.1")
-
- mock_client.auth_service_get_user.assert_called_once()
- mock_client.models_store_upload_model.assert_called_once_with(
- V1UploadModelRequest(
- name="test-username/test-model",
- version="0.0.1",
- project_id="test-project-id",
- )
- )
-
- mock_upload_file_to_url.assert_called_once_with("https://test", "test.ckpt", progress_bar=True)
-
-
-@mock.patch("lightning.store.store._Client")
-@mock.patch("lightning.store.store._download_file_from_url")
-def test_download_model(mock_download_file_from_url, mock_client):
- mock_client = mock_client()
-
- mock_client.models_store_download_model.return_value = V1DownloadModelResponse(
- download_url="https://test",
- )
-
- download_model("test-username/test-model", "test.ckpt", version="0.0.1")
-
- mock_client.models_store_download_model.assert_called_once_with(
- name="test-username/test-model",
- version="0.0.1",
- )
-
- mock_download_file_from_url.assert_called_once_with("https://test", os.path.abspath("test.ckpt"), progress_bar=True)
-
-
-@mock.patch("lightning.store.store._Client")
-def test_list_models(mock_client):
- mock_client = mock_client()
-
- # either one of these project APIs could be called
- mock_client.projects_service_list_memberships.return_value = V1ListMembershipsResponse(
- memberships=[V1Membership(project_id="test-project-id")],
- )
- mock_client.projects_service_get_project.return_value = V1Project(id="test-project-id")
-
- mock_client.models_store_list_models.return_value = V1ListModelsResponse(models=[V1Model(name="test-model")])
-
- res = list_models()
- assert res[0].name == "test-model"
-
- mock_client.models_store_list_models.assert_called_once_with(project_id="test-project-id")