From 7a3927257af0748bf7664c11e8bed849b6e6fb11 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 11:08:47 +0200 Subject: [PATCH 01/12] Show list of files larger than threshold --- src/fastapi_cloud_cli/commands/deploy.py | 50 ++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index d41cbd0..6c5cd09 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -108,15 +108,19 @@ def _should_exclude_entry(path: Path) -> bool: return False -def archive(path: Path, tar_path: Path) -> Path: - logger.debug("Starting archive creation for path: %s", path) - files = rignore.walk( +def _rignore_walk(path: Path) -> rignore.Walker: + return rignore.walk( path, should_exclude_entry=_should_exclude_entry, additional_ignore_paths=[".fastapicloudignore"], ignore_hidden=False, ) + +def archive(path: Path, tar_path: Path) -> Path: + logger.debug("Starting archive creation for path: %s", path) + files = _rignore_walk(path) + logger.debug("Archive will be created at: %s", tar_path) file_count = 0 @@ -134,6 +138,19 @@ def archive(path: Path, tar_path: Path) -> Path: return tar_path +def _get_large_files(path: Path, threshold: int) -> list[tuple[Path, int]]: + large_files = [] + files = _rignore_walk(path) + for filename in files: + if filename.is_dir(): + continue + file_size = filename.stat().st_size + if file_size >= threshold: + large_files.append((filename.relative_to(path), file_size)) + + return large_files + + class Team(BaseModel): id: str slug: str @@ -804,10 +821,35 @@ def deploy( ) raise typer.Exit(1) + large_file_threshold = 10 * 1024 * 1024 # 10 MB + app_path = path or Path.cwd() + + large_files = _get_large_files(app_path, threshold=large_file_threshold) + if large_files: + threshold_mb = large_file_threshold // (1024 * 1024) + toolkit.print( + f"⚠️ Some uploaded files are larger than {threshold_mb} MB ⚖️ :", + tag="warning", + ) + top_3 = sorted(large_files, key=lambda x: x[1], reverse=True)[:3] + for fname, fsize in top_3: + fsize_mb = fsize // (1024 * 1024) + toolkit.print(f" • {fname} [yellow]({fsize_mb} MB)[/yellow]") + is_more = len(large_files) > 3 + if is_more: + toolkit.print(f" [dim]...and {len(large_files) - 3} more[/dim]") + + large_files_docs_url = "https://fastapicloud.com/docs/deployment#control-what-is-uploaded" + toolkit.print( + f"Read more: [link={large_files_docs_url}]{large_files_docs_url}[/link]", + tag="tip", + ) + toolkit.print_line() + with tempfile.TemporaryDirectory() as temp_dir: logger.debug("Creating archive for deployment") archive_path = Path(temp_dir) / "archive.tar" - archive(path or Path.cwd(), archive_path) + archive(app_path, archive_path) with ( toolkit.progress( From dccea9b4cdfb4d8ad915d2ec56bab36ee150f497 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 11:17:04 +0200 Subject: [PATCH 02/12] Make `large_file_threshold` configurable --- src/fastapi_cloud_cli/commands/deploy.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 6c5cd09..e54cba5 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -138,14 +138,15 @@ def archive(path: Path, tar_path: Path) -> Path: return tar_path -def _get_large_files(path: Path, threshold: int) -> list[tuple[Path, int]]: +def _get_large_files(path: Path, threshold_mb: int) -> list[tuple[Path, int]]: + threshold_bytes = threshold_mb * 1024 * 1024 large_files = [] files = _rignore_walk(path) for filename in files: if filename.is_dir(): continue file_size = filename.stat().st_size - if file_size >= threshold: + if file_size >= threshold_bytes: large_files.append((filename.relative_to(path), file_size)) return large_files @@ -696,6 +697,10 @@ def deploy( envvar="FASTAPI_CLOUD_APP_ID", ), ] = None, + large_file_threshold: Annotated[ + int, + typer.Option(help="File size threshold in MB for warning about large files"), + ] = 10, # 10 MB ) -> Any: """ Deploy a [bold]FastAPI[/bold] app to FastAPI Cloud. 🚀 @@ -821,14 +826,12 @@ def deploy( ) raise typer.Exit(1) - large_file_threshold = 10 * 1024 * 1024 # 10 MB app_path = path or Path.cwd() - large_files = _get_large_files(app_path, threshold=large_file_threshold) + large_files = _get_large_files(app_path, threshold_mb=large_file_threshold) if large_files: - threshold_mb = large_file_threshold // (1024 * 1024) toolkit.print( - f"⚠️ Some uploaded files are larger than {threshold_mb} MB ⚖️ :", + f"⚠️ Some uploaded files are larger than {large_file_threshold} MB ⚖️ :", tag="warning", ) top_3 = sorted(large_files, key=lambda x: x[1], reverse=True)[:3] @@ -839,7 +842,9 @@ def deploy( if is_more: toolkit.print(f" [dim]...and {len(large_files) - 3} more[/dim]") - large_files_docs_url = "https://fastapicloud.com/docs/deployment#control-what-is-uploaded" + large_files_docs_url = ( + "https://fastapicloud.com/docs/deployment#control-what-is-uploaded" + ) toolkit.print( f"Read more: [link={large_files_docs_url}]{large_files_docs_url}[/link]", tag="tip", From 539f028a27d5917b368f812a85ec4ce34270c5e7 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 12:42:57 +0200 Subject: [PATCH 03/12] Add tests for `large_file_threshold` --- tests/test_cli_deploy.py | 125 +++++++++++++++++++++++++++++++++++++ tests/test_deploy_utils.py | 54 ++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 6588d60..58526d4 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -2194,3 +2194,128 @@ def test_ctrl_c_during_build_streaming_shows_cancelled( assert "🟡" in result.output assert "Cancelled." in result.output + + +def _create_file(path: Path, size_bytes: int) -> None: + """Create a file of the given size.""" + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, "wb") as f: + if size_bytes > 0: + f.seek(size_bytes - 1) + f.write(b"\0") + + +@pytest.mark.respx +def test_large_file_threshold_warning( + logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + app_data = _get_random_app() + app_id = app_data["id"] + team_id = "some-team-id" + deployment_data = _get_random_deployment(app_id=app_id) + + _setup_deployment_mocks( + respx_mock, app_id, team_id, deployment_data, tmp_path + ) + respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( + return_value=Response(200, json={**deployment_data, "status": "success"}) + ) + + _create_file(tmp_path / "model.bin", 12 * 1024 * 1024) # 12 MB + _create_file(tmp_path / "data.csv", 11 * 1024 * 1024) # 11 MB + + with changing_dir(tmp_path): + result = runner.invoke(app, ["deploy"]) + + assert result.exit_code == 0 + assert "Some uploaded files are larger than 10 MB" in result.output + assert "model.bin" in result.output + assert "12 MB" in result.output + assert "data.csv" in result.output + assert "11 MB" in result.output + + +@pytest.mark.respx +def test_large_file_threshold_only_top_three_files_with_more_indicator( + logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + app_data = _get_random_app() + app_id = app_data["id"] + team_id = "some-team-id" + deployment_data = _get_random_deployment(app_id=app_id) + + _setup_deployment_mocks( + respx_mock, app_id, team_id, deployment_data, tmp_path + ) + respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( + return_value=Response(200, json={**deployment_data, "status": "success"}) + ) + + _create_file(tmp_path / "huge.bin", 50 * 1024 * 1024) + _create_file(tmp_path / "big.bin", 40 * 1024 * 1024) + _create_file(tmp_path / "medium.bin", 30 * 1024 * 1024) + _create_file(tmp_path / "smaller.bin", 20 * 1024 * 1024) + _create_file(tmp_path / "smallest.bin", 15 * 1024 * 1024) + + with changing_dir(tmp_path): + result = runner.invoke(app, ["deploy"]) + + assert result.exit_code == 0 + assert "huge.bin" in result.output + assert "big.bin" in result.output + assert "medium.bin" in result.output + assert "smaller.bin" not in result.output + assert "smallest.bin" not in result.output + assert "...and 2 more" in result.output + + +@pytest.mark.respx +def test_large_file_threshold_does_not_warn_when_no_large_files( + logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + app_data = _get_random_app() + app_id = app_data["id"] + team_id = "some-team-id" + deployment_data = _get_random_deployment(app_id=app_id) + + _setup_deployment_mocks( + respx_mock, app_id, team_id, deployment_data, tmp_path + ) + respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( + return_value=Response(200, json={**deployment_data, "status": "success"}) + ) + + (tmp_path / "main.py").write_text("print('hello')") + + with changing_dir(tmp_path): + result = runner.invoke(app, ["deploy"]) + + assert result.exit_code == 0 + assert "Some uploaded files are larger than" not in result.output + + +@pytest.mark.respx +def test_large_file_threshold_custom_threshold( + logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + app_data = _get_random_app() + app_id = app_data["id"] + team_id = "some-team-id" + deployment_data = _get_random_deployment(app_id=app_id) + + _setup_deployment_mocks( + respx_mock, app_id, team_id, deployment_data, tmp_path + ) + respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( + return_value=Response(200, json={**deployment_data, "status": "success"}) + ) + + # 5 MB file: above a 1 MB threshold, below the default 10 MB threshold + _create_file(tmp_path / "data.bin", 5 * 1024 * 1024) + + with changing_dir(tmp_path): + result = runner.invoke(app, ["deploy", "--large-file-threshold", "1"]) + + assert result.exit_code == 0 + assert "Some uploaded files are larger than 1 MB" in result.output + assert "data.bin" in result.output diff --git a/tests/test_deploy_utils.py b/tests/test_deploy_utils.py index 893a40b..f78dfd8 100644 --- a/tests/test_deploy_utils.py +++ b/tests/test_deploy_utils.py @@ -3,12 +3,21 @@ import pytest from fastapi_cloud_cli.commands.deploy import ( + _get_large_files, _should_exclude_entry, validate_app_directory, ) from fastapi_cloud_cli.utils.api import DeploymentStatus +def _create_file(path: Path, size_bytes: int) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, "wb") as f: + if size_bytes > 0: + f.seek(size_bytes - 1) + f.write(b"\0") + + @pytest.mark.parametrize( "path", [ @@ -135,3 +144,48 @@ def test_validate_app_directory_invalid(value: str, expected_message: str) -> No validate_app_directory(value) assert str(exc_info.value) == expected_message + + +def test_get_large_files_no_files_above_threshold(tmp_path: Path) -> None: + """Should not return files smaller than the threshold.""" + _create_file(tmp_path / "small.bin", 512 * 1024) # 0.5 MB + + assert _get_large_files(tmp_path, threshold_mb=1) == [] + + +def test_get_large_files_returns_files_at_or_above_threshold(tmp_path: Path) -> None: + """Should return files at or above the threshold with sizes and relative paths.""" + _create_file(tmp_path / "big.bin", 2 * 1024 * 1024) # 2 MB + _create_file(tmp_path / "subdir" / "huge.bin", 5 * 1024 * 1024) # 5 MB + _create_file(tmp_path / "small.bin", 100 * 1024) # 0.1 MB + + result = _get_large_files(tmp_path, threshold_mb=1) + + assert sorted(result, key=lambda x: x[1]) == [ + (Path("big.bin"), 2 * 1024 * 1024), + (Path("subdir") / "huge.bin", 5 * 1024 * 1024), + ] + + +def test_get_large_files_excludes_default_exclusions(tmp_path: Path) -> None: + """Should not count files in excluded directories like .venv or __pycache__.""" + _create_file(tmp_path / ".venv" / "lib" / "huge.so", 5 * 1024 * 1024) + _create_file( + tmp_path / "__pycache__" / "module.cpython-311.pyc", 5 * 1024 * 1024 + ) + _create_file(tmp_path / "main.py", 5 * 1024 * 1024) + + assert _get_large_files(tmp_path, threshold_mb=1) == [ + (Path("main.py"), 5 * 1024 * 1024) + ] + + +def test_get_large_files_respects_fastapicloudignore(tmp_path: Path) -> None: + """Should not count files matching .fastapicloudignore patterns.""" + _create_file(tmp_path / "data" / "huge.bin", 5 * 1024 * 1024) + _create_file(tmp_path / "main.bin", 5 * 1024 * 1024) + (tmp_path / ".fastapicloudignore").write_text("data/\n") + + assert _get_large_files(tmp_path, threshold_mb=1) == [ + (Path("main.bin"), 5 * 1024 * 1024) + ] From 5005f99ed5d3f5bda743ddfdd25cc3e1eb8b99b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Apr 2026 10:47:17 +0000 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_cli_deploy.py | 16 ++++------------ tests/test_deploy_utils.py | 4 +--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 58526d4..1324d13 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -2214,9 +2214,7 @@ def test_large_file_threshold_warning( team_id = "some-team-id" deployment_data = _get_random_deployment(app_id=app_id) - _setup_deployment_mocks( - respx_mock, app_id, team_id, deployment_data, tmp_path - ) + _setup_deployment_mocks(respx_mock, app_id, team_id, deployment_data, tmp_path) respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( return_value=Response(200, json={**deployment_data, "status": "success"}) ) @@ -2244,9 +2242,7 @@ def test_large_file_threshold_only_top_three_files_with_more_indicator( team_id = "some-team-id" deployment_data = _get_random_deployment(app_id=app_id) - _setup_deployment_mocks( - respx_mock, app_id, team_id, deployment_data, tmp_path - ) + _setup_deployment_mocks(respx_mock, app_id, team_id, deployment_data, tmp_path) respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( return_value=Response(200, json={**deployment_data, "status": "success"}) ) @@ -2278,9 +2274,7 @@ def test_large_file_threshold_does_not_warn_when_no_large_files( team_id = "some-team-id" deployment_data = _get_random_deployment(app_id=app_id) - _setup_deployment_mocks( - respx_mock, app_id, team_id, deployment_data, tmp_path - ) + _setup_deployment_mocks(respx_mock, app_id, team_id, deployment_data, tmp_path) respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( return_value=Response(200, json={**deployment_data, "status": "success"}) ) @@ -2303,9 +2297,7 @@ def test_large_file_threshold_custom_threshold( team_id = "some-team-id" deployment_data = _get_random_deployment(app_id=app_id) - _setup_deployment_mocks( - respx_mock, app_id, team_id, deployment_data, tmp_path - ) + _setup_deployment_mocks(respx_mock, app_id, team_id, deployment_data, tmp_path) respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( return_value=Response(200, json={**deployment_data, "status": "success"}) ) diff --git a/tests/test_deploy_utils.py b/tests/test_deploy_utils.py index f78dfd8..f77ac73 100644 --- a/tests/test_deploy_utils.py +++ b/tests/test_deploy_utils.py @@ -170,9 +170,7 @@ def test_get_large_files_returns_files_at_or_above_threshold(tmp_path: Path) -> def test_get_large_files_excludes_default_exclusions(tmp_path: Path) -> None: """Should not count files in excluded directories like .venv or __pycache__.""" _create_file(tmp_path / ".venv" / "lib" / "huge.so", 5 * 1024 * 1024) - _create_file( - tmp_path / "__pycache__" / "module.cpython-311.pyc", 5 * 1024 * 1024 - ) + _create_file(tmp_path / "__pycache__" / "module.cpython-311.pyc", 5 * 1024 * 1024) _create_file(tmp_path / "main.py", 5 * 1024 * 1024) assert _get_large_files(tmp_path, threshold_mb=1) == [ From 36b5751ecc1065777a6b540303a983b357e26a23 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 13:46:30 +0200 Subject: [PATCH 05/12] Always use `_create_file` in tests for consistency --- tests/test_cli_deploy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 1324d13..e7f0398 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -2279,13 +2279,15 @@ def test_large_file_threshold_does_not_warn_when_no_large_files( return_value=Response(200, json={**deployment_data, "status": "success"}) ) - (tmp_path / "main.py").write_text("print('hello')") + # 5 MB file: below the default 10 MB threshold + _create_file(tmp_path / "data.bin", 5 * 1024 * 1024) with changing_dir(tmp_path): result = runner.invoke(app, ["deploy"]) assert result.exit_code == 0 assert "Some uploaded files are larger than" not in result.output + assert "data.bin" not in result.output @pytest.mark.respx From 1d2ed14f50cfb67e063516dd0dc27ca6f35fb714 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 14:03:45 +0200 Subject: [PATCH 06/12] Return sorted list from `_get_large_files` --- src/fastapi_cloud_cli/commands/deploy.py | 5 ++--- tests/test_deploy_utils.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index e54cba5..9b8e4f5 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -149,7 +149,7 @@ def _get_large_files(path: Path, threshold_mb: int) -> list[tuple[Path, int]]: if file_size >= threshold_bytes: large_files.append((filename.relative_to(path), file_size)) - return large_files + return sorted(large_files, key=lambda x: x[1], reverse=True) class Team(BaseModel): @@ -834,8 +834,7 @@ def deploy( f"⚠️ Some uploaded files are larger than {large_file_threshold} MB ⚖️ :", tag="warning", ) - top_3 = sorted(large_files, key=lambda x: x[1], reverse=True)[:3] - for fname, fsize in top_3: + for fname, fsize in large_files[:3]: fsize_mb = fsize // (1024 * 1024) toolkit.print(f" • {fname} [yellow]({fsize_mb} MB)[/yellow]") is_more = len(large_files) > 3 diff --git a/tests/test_deploy_utils.py b/tests/test_deploy_utils.py index f77ac73..37c20c3 100644 --- a/tests/test_deploy_utils.py +++ b/tests/test_deploy_utils.py @@ -161,9 +161,9 @@ def test_get_large_files_returns_files_at_or_above_threshold(tmp_path: Path) -> result = _get_large_files(tmp_path, threshold_mb=1) - assert sorted(result, key=lambda x: x[1]) == [ - (Path("big.bin"), 2 * 1024 * 1024), + assert result == [ (Path("subdir") / "huge.bin", 5 * 1024 * 1024), + (Path("big.bin"), 2 * 1024 * 1024), ] From 5a13cc883320614ada5dbd8f71dc28acfaca1fa7 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 14:34:35 +0200 Subject: [PATCH 07/12] Use existing `path_to_deploy` --- src/fastapi_cloud_cli/commands/deploy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 9b8e4f5..28fd347 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -826,9 +826,9 @@ def deploy( ) raise typer.Exit(1) - app_path = path or Path.cwd() - - large_files = _get_large_files(app_path, threshold_mb=large_file_threshold) + large_files = _get_large_files( + path_to_deploy, threshold_mb=large_file_threshold + ) if large_files: toolkit.print( f"⚠️ Some uploaded files are larger than {large_file_threshold} MB ⚖️ :", @@ -853,7 +853,7 @@ def deploy( with tempfile.TemporaryDirectory() as temp_dir: logger.debug("Creating archive for deployment") archive_path = Path(temp_dir) / "archive.tar" - archive(app_path, archive_path) + archive(path_to_deploy, archive_path) with ( toolkit.progress( From d166877e590db622d83aa138d67c5df0835f52cd Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 14:41:32 +0200 Subject: [PATCH 08/12] Only warn if size is more than threshold (no warning if exactly at threshold) --- src/fastapi_cloud_cli/commands/deploy.py | 2 +- tests/test_cli_deploy.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 28fd347..19147d7 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -146,7 +146,7 @@ def _get_large_files(path: Path, threshold_mb: int) -> list[tuple[Path, int]]: if filename.is_dir(): continue file_size = filename.stat().st_size - if file_size >= threshold_bytes: + if file_size > threshold_bytes: large_files.append((filename.relative_to(path), file_size)) return sorted(large_files, key=lambda x: x[1], reverse=True) diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index e7f0398..1227ee1 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -2220,7 +2220,7 @@ def test_large_file_threshold_warning( ) _create_file(tmp_path / "model.bin", 12 * 1024 * 1024) # 12 MB - _create_file(tmp_path / "data.csv", 11 * 1024 * 1024) # 11 MB + _create_file(tmp_path / "data.csv", 10 * 1024 * 1024 + 1) # 10+ MB with changing_dir(tmp_path): result = runner.invoke(app, ["deploy"]) @@ -2230,7 +2230,7 @@ def test_large_file_threshold_warning( assert "model.bin" in result.output assert "12 MB" in result.output assert "data.csv" in result.output - assert "11 MB" in result.output + assert "10 MB" in result.output @pytest.mark.respx @@ -2279,8 +2279,9 @@ def test_large_file_threshold_does_not_warn_when_no_large_files( return_value=Response(200, json={**deployment_data, "status": "success"}) ) - # 5 MB file: below the default 10 MB threshold + # Files are less or equal to 10 MB (default threshold), so no warning should be shown _create_file(tmp_path / "data.bin", 5 * 1024 * 1024) + _create_file(tmp_path / "data.bin", 10 * 1024 * 1024) with changing_dir(tmp_path): result = runner.invoke(app, ["deploy"]) From 1e611ca1075fad94ea69c2f1b6d8a682aa22c5e5 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Wed, 29 Apr 2026 14:47:04 +0200 Subject: [PATCH 09/12] Specify min value for `--large-file-threshold` option --- src/fastapi_cloud_cli/commands/deploy.py | 5 ++++- tests/test_cli_deploy.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 19147d7..d6667a2 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -699,7 +699,10 @@ def deploy( ] = None, large_file_threshold: Annotated[ int, - typer.Option(help="File size threshold in MB for warning about large files"), + typer.Option( + help="File size threshold in MB for warning about large files", + min=1, + ), ] = 10, # 10 MB ) -> Any: """ diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 1227ee1..a1d9c78 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -2314,3 +2314,14 @@ def test_large_file_threshold_custom_threshold( assert result.exit_code == 0 assert "Some uploaded files are larger than 1 MB" in result.output assert "data.bin" in result.output + + +@pytest.mark.respx +def test_invalid_large_file_threshold( + logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + with changing_dir(tmp_path): + result = runner.invoke(app, ["deploy", "--large-file-threshold", "0"]) + + assert result.exit_code == 2 + assert "Invalid value for '--large-file-threshold'" in result.output From cd445ff5bbbf8b5b567bf2b3491a23aead5ba9d9 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Mon, 4 May 2026 11:28:02 +0200 Subject: [PATCH 10/12] Add env var for `--large-file-threshold` --- src/fastapi_cloud_cli/commands/deploy.py | 1 + tests/test_cli_deploy.py | 27 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index d6667a2..042d771 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -702,6 +702,7 @@ def deploy( typer.Option( help="File size threshold in MB for warning about large files", min=1, + envvar="FASTAPI_CLOUD_LARGE_FILE_THRESHOLD", ), ] = 10, # 10 MB ) -> Any: diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index a1d9c78..1c5e77f 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -2316,6 +2316,33 @@ def test_large_file_threshold_custom_threshold( assert "data.bin" in result.output +@pytest.mark.respx +def test_large_file_threshold_custom_threshold_envvar( + logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter +) -> None: + app_data = _get_random_app() + app_id = app_data["id"] + team_id = "some-team-id" + deployment_data = _get_random_deployment(app_id=app_id) + + _setup_deployment_mocks(respx_mock, app_id, team_id, deployment_data, tmp_path) + respx_mock.get(f"/apps/{app_id}/deployments/{deployment_data['id']}").mock( + return_value=Response(200, json={**deployment_data, "status": "success"}) + ) + + # 5 MB file: above a 1 MB threshold, below the default 10 MB threshold + _create_file(tmp_path / "data.bin", 5 * 1024 * 1024) + + with changing_dir(tmp_path): + result = runner.invoke( + app, ["deploy"], env={"FASTAPI_CLOUD_LARGE_FILE_THRESHOLD": "1"} + ) + + assert result.exit_code == 0 + assert "Some uploaded files are larger than 1 MB" in result.output + assert "data.bin" in result.output + + @pytest.mark.respx def test_invalid_large_file_threshold( logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter From 81799ee019404805384fa141a7fa7bcdb4bf2d26 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Mon, 4 May 2026 11:32:27 +0200 Subject: [PATCH 11/12] Update docs URL --- src/fastapi_cloud_cli/commands/deploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 042d771..c4c2083 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -846,7 +846,7 @@ def deploy( toolkit.print(f" [dim]...and {len(large_files) - 3} more[/dim]") large_files_docs_url = ( - "https://fastapicloud.com/docs/deployment#control-what-is-uploaded" + "https://fastapicloud.com/docs/fastapi-cloud-cli/deploy/#large-files-warning" ) toolkit.print( f"Read more: [link={large_files_docs_url}]{large_files_docs_url}[/link]", From 8cc01a5d317fc403843c5afc5259bcba02df9798 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 4 May 2026 09:33:35 +0000 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fastapi_cloud_cli/commands/deploy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index c4c2083..1d6e9ce 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -845,9 +845,7 @@ def deploy( if is_more: toolkit.print(f" [dim]...and {len(large_files) - 3} more[/dim]") - large_files_docs_url = ( - "https://fastapicloud.com/docs/fastapi-cloud-cli/deploy/#large-files-warning" - ) + large_files_docs_url = "https://fastapicloud.com/docs/fastapi-cloud-cli/deploy/#large-files-warning" toolkit.print( f"Read more: [link={large_files_docs_url}]{large_files_docs_url}[/link]", tag="tip",