diff --git a/src/lightning_app/runners/cloud.py b/src/lightning_app/runners/cloud.py index 738833611603b5..fcab5d30375926 100644 --- a/src/lightning_app/runners/cloud.py +++ b/src/lightning_app/runners/cloud.py @@ -216,6 +216,15 @@ def dispatch( root = Path(self.entrypoint_file).absolute().parent cleanup_handle = _prepare_lightning_wheels_and_requirements(root) self.app._update_index_file() + + # Create a default dotignore if it doesn't exist + if not (root / DOT_IGNORE_FILENAME).is_file(): + with open(root / DOT_IGNORE_FILENAME, "w") as f: + f.write("venv/\n") + if (root / "bin" / "activate").is_file() or (root / "pyvenv.cfg").is_file(): + # the user is developing inside venv + f.write("bin/\ninclude/\nlib/\npyvenv.cfg\n") + repo = LocalSourceCodeDir(path=root) self._check_uploaded_folder(root, repo) requirements_file = root / "requirements.txt" diff --git a/tests/tests_app/runners/test_cloud.py b/tests/tests_app/runners/test_cloud.py index a331465e0fc2f3..b80998c14454db 100644 --- a/tests/tests_app/runners/test_cloud.py +++ b/tests/tests_app/runners/test_cloud.py @@ -1488,6 +1488,56 @@ def run(self): _validate_build_spec_and_compute(Work()) +def test_default_lightningignore(monkeypatch, caplog, tmpdir): + 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 = V1LightningappRelease( + 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()) + + path = Path(tmpdir) + cloud_runtime = cloud.CloudRuntime(app=app, entrypoint_file=path / "entrypoint.py") + 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( "lightning_app_instance, lightning_cloud_url, expected_url", [