From 60878aadc3e9361e896a9194fcbfab2dc06adcb8 Mon Sep 17 00:00:00 2001 From: Alejandra Date: Mon, 6 Oct 2025 13:14:15 +0200 Subject: [PATCH 1/5] Update environment variable handling to use batch API --- src/fastapi_cloud_cli/commands/deploy.py | 7 ++++++- src/fastapi_cloud_cli/commands/env.py | 6 +++--- tests/test_cli_deploy.py | 8 ++++---- tests/test_env_set.py | 6 +++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 22019c2..0515887 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -231,7 +231,11 @@ def _get_apps(team_id: str) -> List[AppResponse]: def _create_environment_variables(app_id: str, env_vars: Dict[str, str]) -> None: with APIClient() as client: - response = client.patch(f"/apps/{app_id}/environment-variables/", json=env_vars) + payload = { + name: {"value": value, "is_secret": False} + for name, value in env_vars.items() + } + response = client.post(f"/apps/{app_id}/environment-variables/batch", json=payload) response.raise_for_status() @@ -255,6 +259,7 @@ def _stream_build_logs(deployment_id: str) -> Generator[str, None, None]: "💥 Oops! We've angered the Python God. Sacrificing a rubber duck to appease it.", "🧙 Sprinkling magic deployment dust. Abracadabra!", "👀 Hoping that @tiangolo doesn't find out about this deployment.", + "⚡ Great Scott! This deployment needs more gigawatts!", "🍪 Cookie monster detected on server. Deploying anti-cookie shields.", ] diff --git a/src/fastapi_cloud_cli/commands/env.py b/src/fastapi_cloud_cli/commands/env.py index b66d2ea..faa7c6b 100644 --- a/src/fastapi_cloud_cli/commands/env.py +++ b/src/fastapi_cloud_cli/commands/env.py @@ -44,11 +44,11 @@ def _delete_environment_variable(app_id: str, name: str) -> bool: return True -def _set_environment_variable(app_id: str, name: str, value: str) -> None: +def _set_environment_variable(app_id: str, name: str, value: str, is_secret: bool = False) -> None: with APIClient() as client: - response = client.patch( + response = client.post( f"/apps/{app_id}/environment-variables/", - json={name: value}, + json={"name": name, "value": value, "is_secret": is_secret}, ) response.raise_for_status() diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index 712a011..c5b757b 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -758,8 +758,8 @@ def test_creates_environment_variables_during_app_setup( return_value=Response(201, json=app_data) ) - env_vars_request = respx_mock.patch( - f"/apps/{app_data['id']}/environment-variables/", json={"API_KEY": "secret123"} + env_vars_request = respx_mock.post( + f"/apps/{app_data['id']}/environment-variables/batch", json={"API_KEY": {"value": "fakesecret", "is_secret": False}, "TEST_ENV": {"value": "fakesecret123", "is_secret": False}} ).mock(return_value=Response(200)) with changing_dir(tmp_path), patch( @@ -804,8 +804,8 @@ def test_rejects_invalid_environment_variable_names( return_value=Response(201, json=app_data) ) - env_vars_request = respx_mock.patch( - f"/apps/{app_data['id']}/environment-variables/", json={"VALID_KEY": "value123"} + env_vars_request = respx_mock.post( + f"/apps/{app_data['id']}/environment-variables/batch", json={"VALID_KEY": {"value": "value123", "is_secret": False}} ).mock(return_value=Response(200)) with changing_dir(tmp_path), patch( diff --git a/tests/test_env_set.py b/tests/test_env_set.py index 85dc5d4..613c21f 100644 --- a/tests/test_env_set.py +++ b/tests/test_env_set.py @@ -47,7 +47,7 @@ def test_shows_a_message_if_app_is_not_configured(logged_in_cli: None) -> None: def test_shows_a_message_if_something_is_wrong( logged_in_cli: None, respx_mock: respx.MockRouter, configured_app: Path ) -> None: - respx_mock.patch("/apps/123/environment-variables/").mock( + respx_mock.post("/apps/123/environment-variables/").mock( return_value=Response(500) ) @@ -65,7 +65,7 @@ def test_shows_a_message_if_something_is_wrong( def test_shows_message_when_it_sets( logged_in_cli: None, respx_mock: respx.MockRouter, configured_app: Path ) -> None: - respx_mock.patch("/apps/123/environment-variables/").mock( + respx_mock.post("/apps/123/environment-variables/").mock( return_value=Response(200) ) @@ -82,7 +82,7 @@ def test_asks_for_name_and_value( ) -> None: steps = [*"SOME_VAR", Keys.ENTER, *"secret", Keys.ENTER] - respx_mock.patch("/apps/123/environment-variables/").mock( + respx_mock.post("/apps/123/environment-variables/").mock( return_value=Response(200) ) From 589e8c7412f1f1f9b9ae961ef62a1c32ae41dafe Mon Sep 17 00:00:00 2001 From: Alejandra Date: Mon, 6 Oct 2025 13:16:28 +0200 Subject: [PATCH 2/5] Revert change --- src/fastapi_cloud_cli/commands/deploy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 0515887..5dbe742 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -259,7 +259,6 @@ def _stream_build_logs(deployment_id: str) -> Generator[str, None, None]: "💥 Oops! We've angered the Python God. Sacrificing a rubber duck to appease it.", "🧙 Sprinkling magic deployment dust. Abracadabra!", "👀 Hoping that @tiangolo doesn't find out about this deployment.", - "⚡ Great Scott! This deployment needs more gigawatts!", "🍪 Cookie monster detected on server. Deploying anti-cookie shields.", ] From 3522da6673cce9fdee7328dae04f796fc957a496 Mon Sep 17 00:00:00 2001 From: Alejandra Date: Mon, 6 Oct 2025 14:38:22 +0200 Subject: [PATCH 3/5] Update tests --- tests/test_cli_deploy.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index c5b757b..be009a7 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -743,7 +743,11 @@ def test_creates_environment_variables_during_app_setup( Keys.ENTER, # Setup environment variables (Yes) *"API_KEY", # Environment variable name Keys.ENTER, - *"secret123", # Environment variable value + *"fakesecret", # Environment variable value + Keys.ENTER, + *"TEST_ENV", # Environment variable name + Keys.ENTER, + *"fakesecret123", # Environment variable value Keys.ENTER, Keys.ENTER, # Empty key to finish Keys.CTRL_C, # Exit before deployment From 6a69185142a212ebd1b5a477ade567a24e2d8f84 Mon Sep 17 00:00:00 2001 From: Alejandra Date: Mon, 6 Oct 2025 14:53:57 +0200 Subject: [PATCH 4/5] Format --- src/fastapi_cloud_cli/commands/deploy.py | 5 ++++- src/fastapi_cloud_cli/commands/env.py | 4 +++- tests/test_cli_deploy.py | 9 +++++++-- tests/test_env_set.py | 12 +++--------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 5dbe742..90a105e 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -235,7 +235,9 @@ def _create_environment_variables(app_id: str, env_vars: Dict[str, str]) -> None name: {"value": value, "is_secret": False} for name, value in env_vars.items() } - response = client.post(f"/apps/{app_id}/environment-variables/batch", json=payload) + response = client.post( + f"/apps/{app_id}/environment-variables/batch", json=payload + ) response.raise_for_status() @@ -259,6 +261,7 @@ def _stream_build_logs(deployment_id: str) -> Generator[str, None, None]: "💥 Oops! We've angered the Python God. Sacrificing a rubber duck to appease it.", "🧙 Sprinkling magic deployment dust. Abracadabra!", "👀 Hoping that @tiangolo doesn't find out about this deployment.", + "⚡ Great Scott! This deployment needs more gigawatts!", "🍪 Cookie monster detected on server. Deploying anti-cookie shields.", ] diff --git a/src/fastapi_cloud_cli/commands/env.py b/src/fastapi_cloud_cli/commands/env.py index faa7c6b..256f473 100644 --- a/src/fastapi_cloud_cli/commands/env.py +++ b/src/fastapi_cloud_cli/commands/env.py @@ -44,7 +44,9 @@ def _delete_environment_variable(app_id: str, name: str) -> bool: return True -def _set_environment_variable(app_id: str, name: str, value: str, is_secret: bool = False) -> None: +def _set_environment_variable( + app_id: str, name: str, value: str, is_secret: bool = False +) -> None: with APIClient() as client: response = client.post( f"/apps/{app_id}/environment-variables/", diff --git a/tests/test_cli_deploy.py b/tests/test_cli_deploy.py index be009a7..e3c83fc 100644 --- a/tests/test_cli_deploy.py +++ b/tests/test_cli_deploy.py @@ -763,7 +763,11 @@ def test_creates_environment_variables_during_app_setup( ) env_vars_request = respx_mock.post( - f"/apps/{app_data['id']}/environment-variables/batch", json={"API_KEY": {"value": "fakesecret", "is_secret": False}, "TEST_ENV": {"value": "fakesecret123", "is_secret": False}} + f"/apps/{app_data['id']}/environment-variables/batch", + json={ + "API_KEY": {"value": "fakesecret", "is_secret": False}, + "TEST_ENV": {"value": "fakesecret123", "is_secret": False}, + }, ).mock(return_value=Response(200)) with changing_dir(tmp_path), patch( @@ -809,7 +813,8 @@ def test_rejects_invalid_environment_variable_names( ) env_vars_request = respx_mock.post( - f"/apps/{app_data['id']}/environment-variables/batch", json={"VALID_KEY": {"value": "value123", "is_secret": False}} + f"/apps/{app_data['id']}/environment-variables/batch", + json={"VALID_KEY": {"value": "value123", "is_secret": False}}, ).mock(return_value=Response(200)) with changing_dir(tmp_path), patch( diff --git a/tests/test_env_set.py b/tests/test_env_set.py index 613c21f..e549d76 100644 --- a/tests/test_env_set.py +++ b/tests/test_env_set.py @@ -47,9 +47,7 @@ def test_shows_a_message_if_app_is_not_configured(logged_in_cli: None) -> None: def test_shows_a_message_if_something_is_wrong( logged_in_cli: None, respx_mock: respx.MockRouter, configured_app: Path ) -> None: - respx_mock.post("/apps/123/environment-variables/").mock( - return_value=Response(500) - ) + respx_mock.post("/apps/123/environment-variables/").mock(return_value=Response(500)) with changing_dir(configured_app): result = runner.invoke(app, ["env", "set", "SOME_VAR", "secret"]) @@ -65,9 +63,7 @@ def test_shows_a_message_if_something_is_wrong( def test_shows_message_when_it_sets( logged_in_cli: None, respx_mock: respx.MockRouter, configured_app: Path ) -> None: - respx_mock.post("/apps/123/environment-variables/").mock( - return_value=Response(200) - ) + respx_mock.post("/apps/123/environment-variables/").mock(return_value=Response(200)) with changing_dir(configured_app): result = runner.invoke(app, ["env", "set", "SOME_VAR", "secret"]) @@ -82,9 +78,7 @@ def test_asks_for_name_and_value( ) -> None: steps = [*"SOME_VAR", Keys.ENTER, *"secret", Keys.ENTER] - respx_mock.post("/apps/123/environment-variables/").mock( - return_value=Response(200) - ) + respx_mock.post("/apps/123/environment-variables/").mock(return_value=Response(200)) with changing_dir(configured_app), patch( "rich_toolkit.container.getchar", side_effect=steps From f5b21793efab858ae9160a9fb0f0b104506facee Mon Sep 17 00:00:00 2001 From: Alejandra Date: Mon, 6 Oct 2025 14:58:43 +0200 Subject: [PATCH 5/5] Update --- src/fastapi_cloud_cli/commands/deploy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fastapi_cloud_cli/commands/deploy.py b/src/fastapi_cloud_cli/commands/deploy.py index 90a105e..4d3ca02 100644 --- a/src/fastapi_cloud_cli/commands/deploy.py +++ b/src/fastapi_cloud_cli/commands/deploy.py @@ -261,7 +261,6 @@ def _stream_build_logs(deployment_id: str) -> Generator[str, None, None]: "💥 Oops! We've angered the Python God. Sacrificing a rubber duck to appease it.", "🧙 Sprinkling magic deployment dust. Abracadabra!", "👀 Hoping that @tiangolo doesn't find out about this deployment.", - "⚡ Great Scott! This deployment needs more gigawatts!", "🍪 Cookie monster detected on server. Deploying anti-cookie shields.", ]