fastapi-cloud-cli 0.2.1__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/PKG-INFO +1 -1
  2. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/pyproject.toml +1 -1
  3. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/requirements-tests.txt +1 -1
  4. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/requirements.txt +1 -1
  5. fastapi_cloud_cli-0.3.1/src/fastapi_cloud_cli/__init__.py +1 -0
  6. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/deploy.py +6 -50
  7. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/env.py +5 -3
  8. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/login.py +1 -1
  9. fastapi_cloud_cli-0.3.1/tests/test_archive.py +103 -0
  10. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_cli_deploy.py +0 -95
  11. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_env_set.py +3 -9
  12. fastapi_cloud_cli-0.2.1/src/fastapi_cloud_cli/__init__.py +0 -1
  13. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/LICENSE +0 -0
  14. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/README.md +0 -0
  15. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/scripts/format.sh +0 -0
  16. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/scripts/lint.sh +0 -0
  17. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/scripts/test-cov-html.sh +0 -0
  18. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/scripts/test.sh +0 -0
  19. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/__main__.py +0 -0
  20. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/cli.py +0 -0
  21. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
  22. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/logout.py +0 -0
  23. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/unlink.py +0 -0
  24. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/commands/whoami.py +0 -0
  25. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/config.py +0 -0
  26. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/logging.py +0 -0
  27. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/py.typed +0 -0
  28. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
  29. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/api.py +0 -0
  30. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/apps.py +0 -0
  31. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/auth.py +0 -0
  32. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/cli.py +0 -0
  33. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/config.py +0 -0
  34. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/env.py +0 -0
  35. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
  36. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/__init__.py +0 -0
  37. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/broken_package/mod/__init__.py +0 -0
  38. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/broken_package/mod/app.py +0 -0
  39. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/broken_package/utils.py +0 -0
  40. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_api/api.py +0 -0
  41. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app/api.py +0 -0
  42. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app/app.py +0 -0
  43. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_api/app/__init__.py +0 -0
  44. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_api/app/api.py +0 -0
  45. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_app/app/__init__.py +0 -0
  46. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_app/app/api.py +0 -0
  47. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_app/app/app.py +0 -0
  48. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_main/app/__init__.py +0 -0
  49. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_main/app/api.py +0 -0
  50. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_main/app/app.py +0 -0
  51. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_main/app/main.py +0 -0
  52. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_non_default/app/__init__.py +0 -0
  53. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_app_dir_non_default/app/nondefault.py +0 -0
  54. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_main/api.py +0 -0
  55. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_main/app.py +0 -0
  56. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/default_main/main.py +0 -0
  57. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/default_files/non_default/nonstandard.py +0 -0
  58. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/__init__.py +0 -0
  59. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/core/__init__.py +0 -0
  60. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/core/utils.py +0 -0
  61. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/mod/__init__.py +0 -0
  62. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/mod/api.py +0 -0
  63. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/mod/app.py +0 -0
  64. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/package/mod/other.py +0 -0
  65. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/single_file_api.py +0 -0
  66. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/single_file_app.py +0 -0
  67. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/assets/single_file_other.py +0 -0
  68. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/conftest.py +0 -0
  69. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_cli.py +0 -0
  70. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_cli_login.py +0 -0
  71. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_cli_logout.py +0 -0
  72. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_cli_unlink.py +0 -0
  73. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_cli_whoami.py +0 -0
  74. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_config.py +0 -0
  75. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_deploy_utils.py +0 -0
  76. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_env_delete.py +0 -0
  77. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_env_list.py +0 -0
  78. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/test_sentry.py +0 -0
  79. {fastapi_cloud_cli-0.2.1 → fastapi_cloud_cli-0.3.1}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-cloud-cli
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: Deploy and manage FastAPI Cloud apps from the command line 🚀
5
5
  Author-Email: Patrick Arminio <patrick@fastapilabs.com>
6
6
  License: MIT
@@ -40,7 +40,7 @@ dependencies = [
40
40
  "pydantic[email] >= 1.6.1",
41
41
  "sentry-sdk >= 2.20.0",
42
42
  ]
43
- version = "0.2.1"
43
+ version = "0.3.1"
44
44
 
45
45
  [project.license]
46
46
  text = "MIT"
@@ -3,5 +3,5 @@
3
3
  pytest >=4.4.0,<9.0.0
4
4
  coverage[toml] >=6.2,<8.0
5
5
  mypy ==1.14.1
6
- ruff ==0.12.0
6
+ ruff ==0.13.0
7
7
  respx ==0.22.0
@@ -2,4 +2,4 @@
2
2
 
3
3
  -r requirements-tests.txt
4
4
 
5
- pre-commit >=2.17.0,<4.0.0
5
+ pre-commit >=2.17.0,<5.0.0
@@ -0,0 +1 @@
1
+ __version__ = "0.3.1"
@@ -25,7 +25,6 @@ from fastapi_cloud_cli.utils.api import APIClient
25
25
  from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config
26
26
  from fastapi_cloud_cli.utils.auth import is_logged_in
27
27
  from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
28
- from fastapi_cloud_cli.utils.env import validate_environment_variable_name
29
28
 
30
29
  logger = logging.getLogger(__name__)
31
30
 
@@ -49,7 +48,11 @@ def _should_exclude_entry(path: Path) -> bool:
49
48
 
50
49
  def archive(path: Path) -> Path:
51
50
  logger.debug("Starting archive creation for path: %s", path)
52
- files = rignore.walk(path, should_exclude_entry=_should_exclude_entry)
51
+ files = rignore.walk(
52
+ path,
53
+ should_exclude_entry=_should_exclude_entry,
54
+ additional_ignore_paths=[".fastapicloudignore"],
55
+ )
53
56
 
54
57
  temp_dir = tempfile.mkdtemp()
55
58
  logger.debug("Created temp directory: %s", temp_dir)
@@ -64,6 +67,7 @@ def archive(path: Path) -> Path:
64
67
  if filename.is_dir():
65
68
  continue
66
69
 
70
+ logger.debug("Adding %s to archive", filename.relative_to(path))
67
71
  tar.add(filename, arcname=filename.relative_to(path))
68
72
  file_count += 1
69
73
 
@@ -225,12 +229,6 @@ def _get_apps(team_id: str) -> List[AppResponse]:
225
229
  return [AppResponse.model_validate(app) for app in data]
226
230
 
227
231
 
228
- def _create_environment_variables(app_id: str, env_vars: Dict[str, str]) -> None:
229
- with APIClient() as client:
230
- response = client.patch(f"/apps/{app_id}/environment-variables/", json=env_vars)
231
- response.raise_for_status()
232
-
233
-
234
232
  def _stream_build_logs(deployment_id: str) -> Generator[str, None, None]:
235
233
  with APIClient() as client:
236
234
  with client.stream(
@@ -395,45 +393,6 @@ def _wait_for_deployment(
395
393
  last_message_changed_at = time.monotonic() # pragma: no cover
396
394
 
397
395
 
398
- def _setup_environment_variables(toolkit: RichToolkit, app_id: str) -> None:
399
- if not toolkit.confirm("Do you want to setup environment variables?", tag="env"):
400
- return
401
-
402
- toolkit.print_line()
403
-
404
- env_vars = {}
405
-
406
- while True:
407
- key = toolkit.input(
408
- "Enter the environment variable name: [ENTER to skip]", required=False
409
- )
410
-
411
- if key.strip() == "":
412
- break
413
-
414
- if not validate_environment_variable_name(key):
415
- toolkit.print(
416
- "[error]Invalid environment variable name.",
417
- )
418
-
419
- else:
420
- value = toolkit.input(
421
- "Enter the environment variable value:", password=True
422
- )
423
-
424
- env_vars[key] = value
425
-
426
- toolkit.print_line()
427
-
428
- toolkit.print_line()
429
-
430
- with toolkit.progress("Setting up environment variables...") as progress:
431
- with handle_http_errors(progress):
432
- _create_environment_variables(app_id, env_vars)
433
-
434
- progress.log("Environment variables set up successfully!")
435
-
436
-
437
396
  class SignupToWaitingList(BaseModel):
438
397
  email: EmailStr
439
398
  name: Optional[str] = None
@@ -601,9 +560,6 @@ def deploy(
601
560
  logger.debug("No app config found, configuring new app")
602
561
  app_config = _configure_app(toolkit, path_to_deploy=path_to_deploy)
603
562
  toolkit.print_line()
604
-
605
- _setup_environment_variables(toolkit, app_config.app_id)
606
- toolkit.print_line()
607
563
  else:
608
564
  logger.debug("Existing app config found, proceeding with deployment")
609
565
  toolkit.print("Deploying app...")
@@ -44,11 +44,13 @@ def _delete_environment_variable(app_id: str, name: str) -> bool:
44
44
  return True
45
45
 
46
46
 
47
- def _set_environment_variable(app_id: str, name: str, value: str) -> None:
47
+ def _set_environment_variable(
48
+ app_id: str, name: str, value: str, is_secret: bool = False
49
+ ) -> None:
48
50
  with APIClient() as client:
49
- response = client.patch(
51
+ response = client.post(
50
52
  f"/apps/{app_id}/environment-variables/",
51
- json={name: value},
53
+ json={"name": name, "value": value, "is_secret": is_secret},
52
54
  )
53
55
  response.raise_for_status()
54
56
 
@@ -87,7 +87,7 @@ def login() -> Any:
87
87
 
88
88
  url = authorization_data.verification_uri_complete
89
89
 
90
- progress.log(f"Opening {url}")
90
+ progress.log(f"Opening [link={url}]{url}[/link]")
91
91
 
92
92
  toolkit.print_line()
93
93
 
@@ -0,0 +1,103 @@
1
+ import tarfile
2
+ from pathlib import Path
3
+
4
+ from fastapi_cloud_cli.commands.deploy import archive
5
+
6
+
7
+ def test_archive_creates_tar_file(tmp_path: Path) -> None:
8
+ (tmp_path / "main.py").write_text("print('hello')")
9
+ (tmp_path / "config.json").write_text('{"key": "value"}')
10
+ (tmp_path / "subdir").mkdir()
11
+ (tmp_path / "subdir" / "utils.py").write_text("def helper(): pass")
12
+
13
+ tar_path = archive(tmp_path)
14
+
15
+ assert tar_path.exists()
16
+ assert tar_path.suffix == ".tar"
17
+ assert tar_path.name.startswith("fastapi-cloud-deploy-")
18
+
19
+
20
+ def test_archive_excludes_venv_and_similar_folders(tmp_path: Path) -> None:
21
+ """Should exclude .venv directory from archive."""
22
+ # the only files we want to include
23
+ (tmp_path / "main.py").write_text("print('hello')")
24
+ (tmp_path / "static").mkdir()
25
+ (tmp_path / "static" / "index.html").write_text("<html></html>")
26
+ # virtualenv
27
+ (tmp_path / ".venv").mkdir()
28
+ (tmp_path / ".venv" / "lib").mkdir()
29
+ (tmp_path / ".venv" / "lib" / "package.py").write_text("# package")
30
+ # pycache
31
+ (tmp_path / "__pycache__").mkdir()
32
+ (tmp_path / "__pycache__" / "main.cpython-311.pyc").write_text("bytecode")
33
+ # pyc files
34
+ (tmp_path / "main.pyc").write_text("bytecode")
35
+ # mypy/pytest
36
+ (tmp_path / ".mypy_cache").mkdir()
37
+ (tmp_path / ".mypy_cache" / "file.json").write_text("{}")
38
+ (tmp_path / ".pytest_cache").mkdir()
39
+ (tmp_path / ".pytest_cache" / "cache.db").write_text("data")
40
+
41
+ tar_path = archive(tmp_path)
42
+
43
+ with tarfile.open(tar_path, "r") as tar:
44
+ names = tar.getnames()
45
+ assert set(names) == {"main.py", "static/index.html"}
46
+
47
+
48
+ def test_archive_preserves_relative_paths(tmp_path: Path) -> None:
49
+ (tmp_path / "src").mkdir()
50
+ (tmp_path / "src" / "app").mkdir()
51
+ (tmp_path / "src" / "app" / "main.py").write_text("print('hello')")
52
+
53
+ tar_path = archive(tmp_path)
54
+
55
+ with tarfile.open(tar_path, "r") as tar:
56
+ names = tar.getnames()
57
+ assert names == ["src/app/main.py"]
58
+
59
+
60
+ def test_archive_respects_fastapicloudignore(tmp_path: Path) -> None:
61
+ """Should exclude files specified in .fastapicloudignore."""
62
+ # Create test files
63
+ (tmp_path / "main.py").write_text("print('hello')")
64
+ (tmp_path / "config.py").write_text("CONFIG = 'value'")
65
+ (tmp_path / "secrets.env").write_text("SECRET_KEY=xyz")
66
+ (tmp_path / "data").mkdir()
67
+ (tmp_path / "data" / "file.txt").write_text("data")
68
+
69
+ # Create .fastapicloudignore file
70
+ (tmp_path / ".fastapicloudignore").write_text("secrets.env\ndata/\n")
71
+
72
+ # Create archive
73
+ tar_path = archive(tmp_path)
74
+
75
+ # Verify ignored files are excluded
76
+ with tarfile.open(tar_path, "r") as tar:
77
+ names = tar.getnames()
78
+ assert set(names) == {
79
+ "main.py",
80
+ "config.py",
81
+ }
82
+
83
+
84
+ def test_archive_respects_fastapicloudignore_unignore(tmp_path: Path) -> None:
85
+ """Test we can use .fastapicloudignore to unignore files inside .gitignore"""
86
+ # Create test files
87
+ (tmp_path / "main.py").write_text("print('hello')")
88
+ (tmp_path / "static/build").mkdir(exist_ok=True, parents=True)
89
+ (tmp_path / "static/build/style.css").write_text("body { background: #bada55 }")
90
+ # Rignore needs a .git folder to make .gitignore work
91
+ (tmp_path / ".git").mkdir(exist_ok=True, parents=True)
92
+ (tmp_path / ".gitignore").write_text("build/")
93
+
94
+ # Create .fastapicloudignore file
95
+ (tmp_path / ".fastapicloudignore").write_text("!static/build")
96
+
97
+ # Create archive
98
+ tar_path = archive(tmp_path)
99
+
100
+ # Verify ignored files are excluded
101
+ with tarfile.open(tar_path, "r") as tar:
102
+ names = tar.getnames()
103
+ assert set(names) == {"main.py", "static/build/style.css"}
@@ -386,8 +386,6 @@ def test_exits_successfully_when_deployment_is_done(
386
386
  Keys.ENTER,
387
387
  *"demo",
388
388
  Keys.ENTER,
389
- Keys.RIGHT_ARROW,
390
- Keys.ENTER,
391
389
  ]
392
390
 
393
391
  team_data = _get_random_team()
@@ -642,8 +640,6 @@ def _deploy_without_waiting(respx_mock: respx.MockRouter, tmp_path: Path) -> Res
642
640
  Keys.ENTER,
643
641
  *"demo",
644
642
  Keys.ENTER,
645
- Keys.RIGHT_ARROW,
646
- Keys.ENTER,
647
643
  ]
648
644
 
649
645
  team_data = _get_random_team()
@@ -730,97 +726,6 @@ def test_does_not_duplicate_entry_in_git_ignore(
730
726
  assert git_ignore_path.read_text() == ".fastapicloud\n"
731
727
 
732
728
 
733
- @pytest.mark.respx(base_url=settings.base_api_url)
734
- def test_creates_environment_variables_during_app_setup(
735
- logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
736
- ) -> None:
737
- steps = [
738
- Keys.ENTER, # Setup and deploy
739
- Keys.ENTER, # Select team
740
- Keys.ENTER, # Create new app
741
- *"demo", # App name
742
- Keys.ENTER,
743
- Keys.ENTER, # Setup environment variables (Yes)
744
- *"API_KEY", # Environment variable name
745
- Keys.ENTER,
746
- *"secret123", # Environment variable value
747
- Keys.ENTER,
748
- Keys.ENTER, # Empty key to finish
749
- Keys.CTRL_C, # Exit before deployment
750
- ]
751
-
752
- team = _get_random_team()
753
- app_data = _get_random_app(team_id=team["id"])
754
-
755
- respx_mock.get("/teams/").mock(return_value=Response(200, json={"data": [team]}))
756
-
757
- respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"]}).mock(
758
- return_value=Response(201, json=app_data)
759
- )
760
-
761
- env_vars_request = respx_mock.patch(
762
- f"/apps/{app_data['id']}/environment-variables/", json={"API_KEY": "secret123"}
763
- ).mock(return_value=Response(200))
764
-
765
- with changing_dir(tmp_path), patch(
766
- "rich_toolkit.container.getchar"
767
- ) as mock_getchar:
768
- mock_getchar.side_effect = steps
769
-
770
- result = runner.invoke(app, ["deploy"])
771
-
772
- assert result.exit_code == 1
773
- assert env_vars_request.called
774
- assert "Environment variables set up successfully!" in result.output
775
-
776
-
777
- @pytest.mark.respx(base_url=settings.base_api_url)
778
- def test_rejects_invalid_environment_variable_names(
779
- logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
780
- ) -> None:
781
- steps = [
782
- Keys.ENTER, # Setup and deploy
783
- Keys.ENTER, # Select team
784
- Keys.ENTER, # Create new app
785
- *"demo", # App name
786
- Keys.ENTER,
787
- Keys.ENTER, # Setup environment variables (Yes)
788
- *"123-invalid", # Invalid environment variable name (starts with digit, contains hyphen)
789
- Keys.ENTER,
790
- *"VALID_KEY", # Valid environment variable name
791
- Keys.ENTER,
792
- *"value123", # Environment variable value
793
- Keys.ENTER,
794
- Keys.ENTER, # Empty key to finish
795
- Keys.CTRL_C, # Exit before deployment
796
- ]
797
-
798
- team = _get_random_team()
799
- app_data = _get_random_app(team_id=team["id"])
800
-
801
- respx_mock.get("/teams/").mock(return_value=Response(200, json={"data": [team]}))
802
-
803
- respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"]}).mock(
804
- return_value=Response(201, json=app_data)
805
- )
806
-
807
- env_vars_request = respx_mock.patch(
808
- f"/apps/{app_data['id']}/environment-variables/", json={"VALID_KEY": "value123"}
809
- ).mock(return_value=Response(200))
810
-
811
- with changing_dir(tmp_path), patch(
812
- "rich_toolkit.container.getchar"
813
- ) as mock_getchar:
814
- mock_getchar.side_effect = steps
815
-
816
- result = runner.invoke(app, ["deploy"])
817
-
818
- assert result.exit_code == 1
819
- assert env_vars_request.called
820
- assert "Invalid environment variable name." in result.output
821
- assert "Environment variables set up successfully!" in result.output
822
-
823
-
824
729
  @pytest.mark.respx(base_url=settings.base_api_url)
825
730
  def test_shows_error_for_invalid_waitlist_form_data(
826
731
  logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
@@ -47,9 +47,7 @@ def test_shows_a_message_if_app_is_not_configured(logged_in_cli: None) -> None:
47
47
  def test_shows_a_message_if_something_is_wrong(
48
48
  logged_in_cli: None, respx_mock: respx.MockRouter, configured_app: Path
49
49
  ) -> None:
50
- respx_mock.patch("/apps/123/environment-variables/").mock(
51
- return_value=Response(500)
52
- )
50
+ respx_mock.post("/apps/123/environment-variables/").mock(return_value=Response(500))
53
51
 
54
52
  with changing_dir(configured_app):
55
53
  result = runner.invoke(app, ["env", "set", "SOME_VAR", "secret"])
@@ -65,9 +63,7 @@ def test_shows_a_message_if_something_is_wrong(
65
63
  def test_shows_message_when_it_sets(
66
64
  logged_in_cli: None, respx_mock: respx.MockRouter, configured_app: Path
67
65
  ) -> None:
68
- respx_mock.patch("/apps/123/environment-variables/").mock(
69
- return_value=Response(200)
70
- )
66
+ respx_mock.post("/apps/123/environment-variables/").mock(return_value=Response(200))
71
67
 
72
68
  with changing_dir(configured_app):
73
69
  result = runner.invoke(app, ["env", "set", "SOME_VAR", "secret"])
@@ -82,9 +78,7 @@ def test_asks_for_name_and_value(
82
78
  ) -> None:
83
79
  steps = [*"SOME_VAR", Keys.ENTER, *"secret", Keys.ENTER]
84
80
 
85
- respx_mock.patch("/apps/123/environment-variables/").mock(
86
- return_value=Response(200)
87
- )
81
+ respx_mock.post("/apps/123/environment-variables/").mock(return_value=Response(200))
88
82
 
89
83
  with changing_dir(configured_app), patch(
90
84
  "rich_toolkit.container.getchar", side_effect=steps
@@ -1 +0,0 @@
1
- __version__ = "0.2.1"