pipecatcloud 0.3.1__tar.gz → 0.4.0__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 (65) hide show
  1. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/CHANGELOG.md +27 -0
  2. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/PKG-INFO +1 -1
  3. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/deploy_utils.py +23 -3
  4. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/api.py +0 -1
  5. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/agent.py +8 -19
  6. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/deploy.py +2 -8
  7. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/docker.py +6 -1
  8. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/config.py +5 -1
  9. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/entry_point.py +10 -4
  10. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/PKG-INFO +1 -1
  11. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/SOURCES.txt +2 -1
  12. pipecatcloud-0.4.0/tests/conftest.py +17 -0
  13. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_api_client.py +0 -2
  14. pipecatcloud-0.4.0/tests/test_config_file.py +48 -0
  15. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_krisp_viva.py +1 -9
  16. pipecatcloud-0.3.1/tests/test_managed_keys.py +0 -426
  17. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/format.yml +0 -0
  18. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/publish-pypi.yml +0 -0
  19. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/publish-test.yml +0 -0
  20. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.github/workflows/tests.yml +0 -0
  21. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.gitignore +0 -0
  22. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/.pre-commit-config.yaml +0 -0
  23. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/LICENSE +0 -0
  24. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/README.md +0 -0
  25. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/pipecat-cloud.png +0 -0
  26. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/pyproject.toml +0 -0
  27. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/pytest.ini +0 -0
  28. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/scripts/fix-ruff.sh +0 -0
  29. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/scripts/pre-commit.sh +0 -0
  30. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/setup.cfg +0 -0
  31. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/__init__.py +0 -0
  32. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/__main__.py +0 -0
  33. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/__version__.py +0 -0
  34. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/__init__.py +0 -0
  35. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
  36. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  37. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/build_utils.py +0 -0
  38. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/console_utils.py +0 -0
  39. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/_utils/regions.py +0 -0
  40. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/agent.py +0 -0
  41. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/__init__.py +0 -0
  42. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/api.py +0 -0
  43. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  44. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/auth.py +0 -0
  45. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/build.py +0 -0
  46. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  47. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
  48. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  49. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/config.py +0 -0
  50. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/constants.py +0 -0
  51. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/exception.py +0 -0
  52. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/py.typed +0 -0
  53. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/session.py +0 -0
  54. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  55. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  56. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  57. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/requires.txt +0 -0
  58. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  59. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/__init__.py +0 -0
  60. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_agent_sessions.py +0 -0
  61. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_agent_stop.py +0 -0
  62. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_deployment_status.py +0 -0
  63. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_docker_commands.py +0 -0
  64. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/tests/test_regions.py +0 -0
  65. {pipecatcloud-0.3.1 → pipecatcloud-0.4.0}/uv.lock +0 -0
@@ -5,6 +5,33 @@ All notable changes to **Pipecat Cloud** will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2026-03-29
9
+
10
+ ### Added
11
+
12
+ - **`--config-file` flag**: Specify an alternate deploy config file on any
13
+ command that uses a config file (`deploy`, `agent sessions`, `agent start`,
14
+ `agent stop`, `docker build-push`). Enables multi-environment workflows
15
+ without renaming files or setting environment variables.
16
+
17
+ ### Changed
18
+
19
+ - Renamed `--config` to `--show-cli-config` for clarity — it displays internal
20
+ CLI state (credentials, active org), not deploy configuration.
21
+
22
+ ### Removed
23
+
24
+ - Removed `--enable-managed-keys` CLI option from `deploy` command.
25
+
26
+ - Removed `enable_managed_keys` from `pcc-deploy.toml` configuration.
27
+
28
+ - Removed "Managed Keys" status display from `agent status` output.
29
+
30
+ ### Fixed
31
+
32
+ - CLI no longer crashes on a malformed credentials file. It warns and allows
33
+ `auth login` to run so the user can fix it.
34
+
8
35
  ## [0.3.1] - 2026-03-13
9
36
 
10
37
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Cloud hosting for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Website, https://docs.pipecat.daily.co/
@@ -10,10 +10,13 @@ from datetime import datetime, timezone
10
10
  from enum import Enum
11
11
  from typing import Callable, List, Optional
12
12
 
13
+
13
14
  import toml
15
+ import typer
14
16
  from attr import dataclass, field
15
17
  from loguru import logger
16
18
 
19
+ from pipecatcloud.cli import PIPECAT_DEPLOY_CONFIG_PATH
17
20
  from pipecatcloud.constants import KRISP_VIVA_MODELS, KrispVivaAudioFilter
18
21
  from pipecatcloud.exception import ConfigFileError
19
22
 
@@ -280,7 +283,6 @@ class DeployConfigParams:
280
283
  region: Optional[str] = None
281
284
  scaling: ScalingParams = ScalingParams()
282
285
  enable_krisp: bool = False
283
- enable_managed_keys: bool = False
284
286
  docker_config: dict = field(factory=dict)
285
287
  build_config: BuildConfig = field(factory=BuildConfig) # Cloud build configuration
286
288
  agent_profile: Optional[str] = None
@@ -303,7 +305,6 @@ class DeployConfigParams:
303
305
  "region": self.region,
304
306
  "scaling": self.scaling.to_dict() if self.scaling else None,
305
307
  "enable_krisp": self.enable_krisp,
306
- "enable_managed_keys": self.enable_managed_keys,
307
308
  "docker_config": self.docker_config,
308
309
  "build_config": self.build_config.to_dict() if self.build_config else None,
309
310
  "agent_profile": self.agent_profile,
@@ -363,7 +364,6 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
363
364
  "region",
364
365
  "scaling",
365
366
  "enable_krisp",
366
- "enable_managed_keys",
367
367
  "docker",
368
368
  "build",
369
369
  "agent_profile",
@@ -380,14 +380,34 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
380
380
  raise ConfigFileError(str(e))
381
381
 
382
382
 
383
+ CONFIG_FILE_OPTION: Optional[str] = typer.Option(
384
+ None,
385
+ "--config-file",
386
+ help=f"Path to deploy config file (default: {PIPECAT_DEPLOY_CONFIG_PATH})",
387
+ )
388
+
389
+
383
390
  def with_deploy_config(func: Callable) -> Callable:
384
391
  """
385
392
  Decorator that loads the deploy config file and injects it into the function.
386
393
  If the config file exists, it will be loaded and passed to the function as `deploy_config`.
394
+
395
+ If the wrapped function receives a `config_file` kwarg (from a --config-file typer option),
396
+ it will override the default deploy config path before loading.
387
397
  """
388
398
 
389
399
  @functools.wraps(func)
390
400
  def wrapper(*args, **kwargs):
401
+ config_file = kwargs.pop("config_file", None)
402
+ if config_file:
403
+ import pipecatcloud.cli.config as config_module
404
+
405
+ if not os.path.exists(config_file):
406
+ from pipecatcloud._utils.console_utils import console
407
+
408
+ console.error(f"Deploy config file not found: {config_file}")
409
+ raise typer.Exit(1)
410
+ config_module.deploy_config_path = config_file
391
411
  try:
392
412
  deploy_config = load_deploy_config_file()
393
413
  kwargs["deploy_config"] = deploy_config
@@ -336,7 +336,6 @@ class _API:
336
336
  "maxAgents": deploy_config.scaling.max_agents,
337
337
  },
338
338
  "enableKrisp": deploy_config.enable_krisp,
339
- "enableIntegratedKeysProxy": deploy_config.enable_managed_keys, # API expects this field name
340
339
  "agentProfile": deploy_config.agent_profile,
341
340
  "krispViva": {
342
341
  "audioFilter": deploy_config.krisp_viva.audio_filter,
@@ -28,7 +28,11 @@ from pipecatcloud._utils.console_utils import (
28
28
  format_duration,
29
29
  format_timestamp,
30
30
  )
31
- from pipecatcloud._utils.deploy_utils import DeployConfigParams, with_deploy_config
31
+ from pipecatcloud._utils.deploy_utils import (
32
+ CONFIG_FILE_OPTION,
33
+ DeployConfigParams,
34
+ with_deploy_config,
35
+ )
32
36
  from pipecatcloud._utils.regions import get_region_codes, validate_region
33
37
  from pipecatcloud.cli import PIPECAT_CLI_NAME
34
38
  from pipecatcloud.cli.api import API
@@ -206,24 +210,6 @@ async def status(
206
210
  str(data.get("updatedAt", "N/A")),
207
211
  )
208
212
 
209
- # Check for Managed Keys status
210
- # API returns integratedKeysProxy but we display as "Managed Keys"
211
- integrated_keys = (
212
- data.get("deployment", {})
213
- .get("manifest", {})
214
- .get("spec", {})
215
- .get("integratedKeysProxy", {})
216
- )
217
- if isinstance(integrated_keys, dict):
218
- integrated_keys_enabled = integrated_keys.get("enabled", False)
219
- else:
220
- integrated_keys_enabled = bool(integrated_keys)
221
-
222
- deployment_table.add_row(
223
- "[bold]Managed Keys:[/bold]",
224
- "[green]Enabled[/green]" if integrated_keys_enabled else "[dim]Disabled[/dim]",
225
- )
226
-
227
213
  # Check for Krisp VIVA status (reverse-mapped by API)
228
214
  krisp_viva = data.get("krispViva")
229
215
  krisp_viva_status = "[dim]Disabled[/dim]"
@@ -346,6 +332,7 @@ async def status(
346
332
  @with_deploy_config
347
333
  async def sessions(
348
334
  deploy_config=typer.Option(None, hidden=True),
335
+ config_file: Optional[str] = CONFIG_FILE_OPTION,
349
336
  agent_name: str = typer.Argument(
350
337
  None, help="Name of the agent to list sessions for e.g. 'my-agent'", show_default=False
351
338
  ),
@@ -781,6 +768,7 @@ async def deployments(
781
768
  @with_deploy_config
782
769
  async def start(
783
770
  deploy_config=typer.Option(None, hidden=True),
771
+ config_file: Optional[str] = CONFIG_FILE_OPTION,
784
772
  agent_name: str = typer.Argument(None, help="Name of the agent to start e.g. 'my-agent'"),
785
773
  force: bool = typer.Option(
786
774
  False,
@@ -947,6 +935,7 @@ async def start(
947
935
  @with_deploy_config
948
936
  async def stop(
949
937
  deploy_config=typer.Option(None, hidden=True),
938
+ config_file: Optional[str] = CONFIG_FILE_OPTION,
950
939
  agent_name: str = typer.Argument(None, help="Name of the agent e.g. 'my-agent'"),
951
940
  session_id: str = typer.Option(
952
941
  ...,
@@ -28,6 +28,7 @@ from pipecatcloud._utils.build_utils import (
28
28
  )
29
29
  from pipecatcloud._utils.console_utils import console
30
30
  from pipecatcloud._utils.deploy_utils import (
31
+ CONFIG_FILE_OPTION,
31
32
  BuildConfig,
32
33
  DeployConfigParams,
33
34
  KrispVivaConfig,
@@ -482,6 +483,7 @@ def create_deploy_command(app: typer.Typer):
482
483
  @with_deploy_config
483
484
  async def deploy(
484
485
  deploy_config=typer.Option(None, hidden=True),
486
+ config_file: Optional[str] = CONFIG_FILE_OPTION,
485
487
  agent_name: str = typer.Argument(
486
488
  None, help="Name of the agent to deploy e.g. 'my-agent'", show_default=False
487
489
  ),
@@ -534,12 +536,6 @@ def create_deploy_command(app: typer.Typer):
534
536
  help="[DEPRECATED] Enable Krisp integration for this deployment. Use --krisp-viva-audio-filter instead",
535
537
  rich_help_panel="Deployment Configuration",
536
538
  ),
537
- managed_keys: bool = typer.Option(
538
- False,
539
- "--enable-managed-keys",
540
- help="Enable Managed Keys for this deployment",
541
- rich_help_panel="Deployment Configuration",
542
- ),
543
539
  krisp_viva_audio_filter: str = typer.Option(
544
540
  None,
545
541
  "--krisp-viva-audio-filter",
@@ -656,7 +652,6 @@ def create_deploy_command(app: typer.Typer):
656
652
  max_agents=max_agents if max_agents is not None else partial_config.scaling.max_agents,
657
653
  )
658
654
  partial_config.enable_krisp = krisp or partial_config.enable_krisp
659
- partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
660
655
  partial_config.agent_profile = profile or partial_config.agent_profile
661
656
 
662
657
  # Override build config from CLI args
@@ -797,7 +792,6 @@ def create_deploy_command(app: typer.Typer):
797
792
  f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}",
798
793
  f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}",
799
794
  f"[bold white]Krisp VIVA:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.krisp_viva.audio_filter else '[green]Enabled (' + partial_config.krisp_viva.audio_filter + ')[/green]'}",
800
- f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}",
801
795
  "\n[dim]Scaling configuration:[/dim]",
802
796
  ]
803
797
  )
@@ -13,7 +13,11 @@ from rich.panel import Panel
13
13
 
14
14
  from pipecatcloud._utils.async_utils import synchronizer
15
15
  from pipecatcloud._utils.console_utils import console
16
- from pipecatcloud._utils.deploy_utils import DeployConfigParams, with_deploy_config
16
+ from pipecatcloud._utils.deploy_utils import (
17
+ CONFIG_FILE_OPTION,
18
+ DeployConfigParams,
19
+ with_deploy_config,
20
+ )
17
21
  from pipecatcloud.cli import PIPECAT_CLI_NAME
18
22
 
19
23
  docker_cli = typer.Typer(
@@ -180,6 +184,7 @@ def _build_image_name(
180
184
  @with_deploy_config
181
185
  async def build_push(
182
186
  deploy_config=typer.Option(None, hidden=True),
187
+ config_file: Optional[str] = CONFIG_FILE_OPTION,
183
188
  agent_name: str = typer.Argument(
184
189
  None, help="Name of the agent to build image for e.g. 'my-agent'", show_default=False
185
190
  ),
@@ -62,7 +62,11 @@ def _read_user_config():
62
62
  stacklevel=2,
63
63
  )
64
64
  if config_problem:
65
- raise ConfigError(config_problem)
65
+ warnings.warn(
66
+ f"{config_problem} Run `pcc auth login` to fix.",
67
+ stacklevel=2,
68
+ )
69
+ return {}
66
70
 
67
71
  return config_data
68
72
 
@@ -35,11 +35,12 @@ def version_callback(value: bool):
35
35
  raise typer.Exit()
36
36
 
37
37
 
38
- def config_callback(value: bool):
38
+ def show_config_callback(value: bool):
39
39
  if value:
40
40
  from rich.pretty import pprint
41
41
 
42
42
  from pipecatcloud._utils.deploy_utils import load_deploy_config_file
43
+ from pipecatcloud.cli.config import deploy_config_path
43
44
 
44
45
  # Print local config
45
46
  pprint(config.to_dict())
@@ -48,11 +49,11 @@ def config_callback(value: bool):
48
49
  try:
49
50
  deploy_config = load_deploy_config_file()
50
51
  if deploy_config:
51
- console.print("Deploy config [dim](pcc-deploy.toml)[/dim]:")
52
+ console.print(f"Deploy config [dim]({deploy_config_path})[/dim]:")
52
53
  console.print_json(data=deploy_config.to_dict())
53
54
  except ConfigFileError as e:
54
55
  console.error(
55
- f"Malformed pcc-deploy.toml - Please correct errors and try again.\n\n{e}"
56
+ f"Malformed {deploy_config_path} - Please correct errors and try again.\n\n{e}"
56
57
  )
57
58
 
58
59
  raise typer.Exit()
@@ -71,7 +72,12 @@ entrypoint_cli_typer = typer.Typer(
71
72
  def cli(
72
73
  ctx: typer.Context,
73
74
  _version: bool = typer.Option(None, "--version", callback=version_callback, help="CLI version"),
74
- _config: bool = typer.Option(None, "--config", callback=config_callback, help="CLI config"),
75
+ _show_cli_config: bool = typer.Option(
76
+ None,
77
+ "--show-cli-config",
78
+ callback=show_config_callback,
79
+ help="Show CLI internal configuration (credentials, active org)",
80
+ ),
75
81
  ):
76
82
  pass
77
83
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Cloud hosting for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Website, https://docs.pipecat.daily.co/
@@ -51,11 +51,12 @@ src/pipecatcloud/cli/commands/regions.py
51
51
  src/pipecatcloud/cli/commands/secrets.py
52
52
  src/pipecatcloud/smallwebrtc/session_manager.py
53
53
  tests/__init__.py
54
+ tests/conftest.py
54
55
  tests/test_agent_sessions.py
55
56
  tests/test_agent_stop.py
56
57
  tests/test_api_client.py
58
+ tests/test_config_file.py
57
59
  tests/test_deployment_status.py
58
60
  tests/test_docker_commands.py
59
61
  tests/test_krisp_viva.py
60
- tests/test_managed_keys.py
61
62
  tests/test_regions.py
@@ -0,0 +1,17 @@
1
+ """
2
+ Global test fixtures.
3
+
4
+ Sets PIPECAT_CONFIG_PATH to an isolated temp file so tests never
5
+ read or corrupt the real user credentials. Provides a fake token
6
+ and org so @requires_login passes through.
7
+ """
8
+
9
+ import os
10
+ import tempfile
11
+
12
+ # Must be set before any pipecatcloud module is imported, since
13
+ # config.py reads the credentials file at module load time.
14
+ _tmp = tempfile.NamedTemporaryFile(suffix=".toml", delete=False, mode="w")
15
+ _tmp.write('token = "test-token"\norg = "test-org"\n')
16
+ _tmp.close()
17
+ os.environ["PIPECAT_CONFIG_PATH"] = _tmp.name
@@ -260,7 +260,6 @@ class TestAPIRegionWithOtherParameters:
260
260
  region="ap",
261
261
  secret_set="my-secrets",
262
262
  image_credentials="my-creds",
263
- enable_managed_keys=True,
264
263
  )
265
264
 
266
265
  with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
@@ -274,7 +273,6 @@ class TestAPIRegionWithOtherParameters:
274
273
  assert payload["region"] == "ap"
275
274
  assert payload["secretSet"] == "my-secrets"
276
275
  assert payload["imagePullSecretSet"] == "my-creds"
277
- assert payload["enableIntegratedKeysProxy"] is True
278
276
 
279
277
 
280
278
  class TestAPIProperties:
@@ -0,0 +1,48 @@
1
+ """
2
+ Unit tests for the --config-file flag behavior via @with_deploy_config.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+ import typer
12
+
13
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
14
+
15
+ from pipecatcloud._utils.deploy_utils import with_deploy_config
16
+
17
+
18
+ @with_deploy_config
19
+ def dummy_command(deploy_config=None, config_file=None):
20
+ """Minimal command decorated with @with_deploy_config."""
21
+ return deploy_config
22
+
23
+
24
+ class TestConfigFileFlag:
25
+ """Test --config-file override behavior in @with_deploy_config."""
26
+
27
+ def test_loads_alternate_config_file(self):
28
+ """--config-file should load the specified file instead of the default."""
29
+ with tempfile.NamedTemporaryFile(suffix=".toml", mode="w", delete=False) as f:
30
+ f.write('agent_name = "alt-agent"\n')
31
+ f.flush()
32
+ try:
33
+ result = dummy_command(config_file=f.name)
34
+ assert result is not None
35
+ assert result.agent_name == "alt-agent"
36
+ finally:
37
+ os.unlink(f.name)
38
+
39
+ def test_nonexistent_config_file_exits(self):
40
+ """--config-file pointing to missing file should exit with error."""
41
+ with pytest.raises(typer.Exit):
42
+ dummy_command(config_file="/nonexistent/path.toml")
43
+
44
+ def test_no_config_file_uses_default(self):
45
+ """Without --config-file, falls back to default pcc-deploy.toml behavior."""
46
+ result = dummy_command(config_file=None)
47
+ # No pcc-deploy.toml in test dir, so deploy_config should be None
48
+ assert result is None
@@ -132,7 +132,6 @@ class TestDeployConfigIntegration:
132
132
  agent_name="test-agent",
133
133
  image="test:latest",
134
134
  enable_krisp=True,
135
- enable_managed_keys=True,
136
135
  krisp_viva=KrispVivaConfig(audio_filter="tel"),
137
136
  )
138
137
 
@@ -143,7 +142,6 @@ class TestDeployConfigIntegration:
143
142
  assert result["agent_name"] == "test-agent"
144
143
  assert result["image"] == "test:latest"
145
144
  assert result["enable_krisp"] is True
146
- assert result["enable_managed_keys"] is True
147
145
  assert result["krisp_viva"]["audio_filter"] == "tel"
148
146
 
149
147
 
@@ -237,7 +235,6 @@ class TestTOMLConfiguration:
237
235
  agent_name = "test-agent"
238
236
  image = "test:latest"
239
237
  enable_krisp = true
240
- enable_managed_keys = true
241
238
  secret_set = "my-secrets"
242
239
 
243
240
  [scaling]
@@ -257,7 +254,6 @@ class TestTOMLConfiguration:
257
254
  assert config.agent_name == "test-agent"
258
255
  assert config.image == "test:latest"
259
256
  assert config.enable_krisp is True
260
- assert config.enable_managed_keys is True
261
257
  assert config.secret_set == "my-secrets"
262
258
  assert config.scaling.min_agents == 2
263
259
  assert config.scaling.max_agents == 10
@@ -550,7 +546,6 @@ class TestBackwardCompatibility:
550
546
  agent_name = "legacy-agent"
551
547
  image = "legacy:latest"
552
548
  enable_krisp = true
553
- enable_managed_keys = true
554
549
 
555
550
  [scaling]
556
551
  min_agents = 1
@@ -569,7 +564,6 @@ class TestBackwardCompatibility:
569
564
  assert config is not None
570
565
  assert config.agent_name == "legacy-agent"
571
566
  assert config.enable_krisp is True
572
- assert config.enable_managed_keys is True
573
567
  assert config.krisp_viva.audio_filter is None
574
568
 
575
569
 
@@ -612,13 +606,12 @@ class TestErrorHandling:
612
606
  assert result["krisp_viva"]["audio_filter"] == "tel"
613
607
 
614
608
  def test_all_features_enabled_together(self):
615
- """Krisp, Managed Keys, and Krisp VIVA can all be enabled together."""
609
+ """Krisp and Krisp VIVA can be enabled together."""
616
610
  # Arrange
617
611
  config = DeployConfigParams(
618
612
  agent_name="test-agent",
619
613
  image="test:latest",
620
614
  enable_krisp=True,
621
- enable_managed_keys=True,
622
615
  krisp_viva=KrispVivaConfig(audio_filter="pro"),
623
616
  )
624
617
 
@@ -627,7 +620,6 @@ class TestErrorHandling:
627
620
 
628
621
  # Assert
629
622
  assert result["enable_krisp"] is True
630
- assert result["enable_managed_keys"] is True
631
623
  assert result["krisp_viva"]["audio_filter"] == "pro"
632
624
 
633
625
 
@@ -1,426 +0,0 @@
1
- """
2
- Unit tests for managed keys feature.
3
-
4
- Tests follow AAA pattern and focus on behaviors/outcomes rather than implementation details.
5
- Covers data model, TOML parsing, CLI commands, and API integration.
6
- """
7
-
8
- from unittest.mock import AsyncMock, patch
9
-
10
- import pytest
11
-
12
- # Import from source, not installed package
13
- import sys
14
- from pathlib import Path
15
-
16
- sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
17
-
18
- from pipecatcloud._utils.deploy_utils import DeployConfigParams, load_deploy_config_file
19
- from pipecatcloud.api import _API
20
-
21
-
22
- class TestDeployConfigDataModel:
23
- """Test the DeployConfigParams data model with managed keys support."""
24
-
25
- def test_default_keys_is_disabled(self):
26
- """When not specified, managed keys should default to disabled."""
27
- # Arrange & Act
28
- config = DeployConfigParams()
29
-
30
- # Assert
31
- assert config.enable_managed_keys is False
32
-
33
- def test_can_enable_keys(self):
34
- """Integrated keys can be explicitly enabled."""
35
- # Arrange & Act
36
- config = DeployConfigParams(enable_managed_keys=True)
37
-
38
- # Assert
39
- assert config.enable_managed_keys is True
40
-
41
- def test_keys_included_in_dict_representation(self):
42
- """Dictionary representation should include managed keys setting."""
43
- # Arrange
44
- config = DeployConfigParams(agent_name="test-agent", enable_managed_keys=True)
45
-
46
- # Act
47
- result = config.to_dict()
48
-
49
- # Assert
50
- assert "enable_managed_keys" in result
51
- assert result["enable_managed_keys"] is True
52
-
53
- def test_keys_preserves_other_settings(self):
54
- """Adding managed keys should not affect other configuration settings."""
55
- # Arrange
56
- config = DeployConfigParams(
57
- agent_name="test-agent",
58
- image="test:latest",
59
- enable_krisp=True,
60
- enable_managed_keys=True,
61
- )
62
-
63
- # Act
64
- result = config.to_dict()
65
-
66
- # Assert
67
- assert result["agent_name"] == "test-agent"
68
- assert result["image"] == "test:latest"
69
- assert result["enable_krisp"] is True
70
- assert result["enable_managed_keys"] is True
71
-
72
-
73
- class TestTOMLConfiguration:
74
- """Test TOML configuration file parsing with managed keys."""
75
-
76
- @pytest.fixture
77
- def temp_config_file(self, tmp_path):
78
- """Create a temporary TOML config file."""
79
- config_path = tmp_path / "pcc-deploy.toml"
80
- return config_path
81
-
82
- def test_loads_keys_proxy_from_toml_when_enabled(self, temp_config_file):
83
- """TOML file with managed keys enabled should be parsed correctly."""
84
- # Arrange
85
- config_content = """
86
- agent_name = "test-agent"
87
- image = "test:latest"
88
- enable_managed_keys = true
89
- """
90
- temp_config_file.write_text(config_content)
91
-
92
- # Act
93
- with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
94
- config = load_deploy_config_file()
95
-
96
- # Assert
97
- assert config is not None
98
- assert config.enable_managed_keys is True
99
-
100
- def test_loads_keys_proxy_from_toml_when_disabled(self, temp_config_file):
101
- """TOML file with managed keys explicitly disabled should be parsed correctly."""
102
- # Arrange
103
- config_content = """
104
- agent_name = "test-agent"
105
- image = "test:latest"
106
- enable_managed_keys = false
107
- """
108
- temp_config_file.write_text(config_content)
109
-
110
- # Act
111
- with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
112
- config = load_deploy_config_file()
113
-
114
- # Assert
115
- assert config is not None
116
- assert config.enable_managed_keys is False
117
-
118
- def test_defaults_keys_proxy_when_not_in_toml(self, temp_config_file):
119
- """TOML file without managed keys setting should default to disabled."""
120
- # Arrange
121
- config_content = """
122
- agent_name = "test-agent"
123
- image = "test:latest"
124
- """
125
- temp_config_file.write_text(config_content)
126
-
127
- # Act
128
- with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
129
- config = load_deploy_config_file()
130
-
131
- # Assert
132
- assert config is not None
133
- assert config.enable_managed_keys is False
134
-
135
- def test_preserves_other_settings_with_keys_proxy(self, temp_config_file):
136
- """Keys proxy setting should not interfere with other TOML settings."""
137
- # Arrange
138
- config_content = """
139
- agent_name = "test-agent"
140
- image = "test:latest"
141
- enable_krisp = true
142
- enable_managed_keys = true
143
- secret_set = "my-secrets"
144
-
145
- [scaling]
146
- min_agents = 2
147
- max_agents = 10
148
- """
149
- temp_config_file.write_text(config_content)
150
-
151
- # Act
152
- with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
153
- config = load_deploy_config_file()
154
-
155
- # Assert
156
- assert config.agent_name == "test-agent"
157
- assert config.image == "test:latest"
158
- assert config.enable_krisp is True
159
- assert config.enable_managed_keys is True
160
- assert config.secret_set == "my-secrets"
161
- assert config.scaling.min_agents == 2
162
- assert config.scaling.max_agents == 10
163
-
164
-
165
- class TestAPIIntegration:
166
- """Test API client integration with managed keys."""
167
-
168
- @pytest.fixture
169
- def api_client(self):
170
- """Create an API client instance."""
171
- return _API(token="test-token", is_cli=True)
172
-
173
- @pytest.mark.asyncio
174
- async def test_deploy_payload_includes_keys_proxy(self, api_client):
175
- """Deploy payload should include enableIntegratedKeys field."""
176
- # Arrange
177
- config = DeployConfigParams(
178
- agent_name="test-agent", image="test:latest", enable_managed_keys=True
179
- )
180
-
181
- with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
182
- mock_request.return_value = {"success": True}
183
-
184
- # Act
185
- await api_client._deploy(config, "test-org", update=False)
186
-
187
- # Assert
188
- mock_request.assert_called_once()
189
- payload = mock_request.call_args[1]["json"]
190
- assert "enableIntegratedKeysProxy" in payload
191
- assert payload["enableIntegratedKeysProxy"] is True
192
-
193
- @pytest.mark.asyncio
194
- async def test_update_payload_includes_keys_proxy(self, api_client):
195
- """Update payload should include enableIntegratedKeys field."""
196
- # Arrange
197
- config = DeployConfigParams(
198
- agent_name="test-agent", image="test:latest", enable_managed_keys=True
199
- )
200
-
201
- with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
202
- mock_request.return_value = {"success": True}
203
-
204
- # Act
205
- await api_client._deploy(config, "test-org", update=True)
206
-
207
- # Assert
208
- mock_request.assert_called_once()
209
- payload = mock_request.call_args[1]["json"]
210
- assert "enableIntegratedKeysProxy" in payload
211
- assert payload["enableIntegratedKeysProxy"] is True
212
-
213
- @pytest.mark.asyncio
214
- async def test_disabled_keys_proxy_sends_false(self, api_client):
215
- """When managed keys is disabled, API should send false value."""
216
- # Arrange
217
- config = DeployConfigParams(
218
- agent_name="test-agent", image="test:latest", enable_managed_keys=False
219
- )
220
-
221
- with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
222
- mock_request.return_value = {"success": True}
223
-
224
- # Act
225
- await api_client._deploy(config, "test-org", update=False)
226
-
227
- # Assert
228
- mock_request.assert_called_once()
229
- payload = mock_request.call_args[1]["json"]
230
- assert "enableIntegratedKeysProxy" in payload
231
- assert payload["enableIntegratedKeysProxy"] is False
232
-
233
-
234
- class TestAgentStatusDisplay:
235
- """Test agent status command displays managed keys status."""
236
-
237
- @pytest.fixture
238
- def mock_api(self):
239
- """Mock the API agent method."""
240
- with patch("pipecatcloud.cli.commands.agent.API") as mock_api:
241
- yield mock_api
242
-
243
- def test_displays_enabled_keys_proxy(self, mock_api, capsys):
244
- """Agent status should display when managed keys is enabled."""
245
- # Arrange
246
- from pipecatcloud.cli.commands.agent import status
247
-
248
- mock_api.agent = AsyncMock(
249
- return_value=(
250
- {
251
- "ready": True,
252
- "deployment": {
253
- "manifest": {"spec": {"integratedKeysProxy": {"enabled": True}}}
254
- },
255
- "autoScaling": {
256
- "minReplicas": 0,
257
- "maxReplicas": 1,
258
- }, # Avoid undefined variable bug
259
- },
260
- None,
261
- )
262
- )
263
-
264
- # Act
265
- status(agent_name="test-agent", organization="test-org")
266
-
267
- # Assert
268
- captured = capsys.readouterr()
269
- assert "Managed Keys" in captured.out
270
- assert "Enabled" in captured.out
271
-
272
- def test_displays_disabled_keys_proxy(self, mock_api, capsys):
273
- """Agent status should display when managed keys is disabled."""
274
- # Arrange
275
- from pipecatcloud.cli.commands.agent import status
276
-
277
- mock_api.agent = AsyncMock(
278
- return_value=(
279
- {
280
- "ready": True,
281
- "deployment": {"manifest": {"spec": {}}},
282
- "autoScaling": {
283
- "minReplicas": 0,
284
- "maxReplicas": 1,
285
- }, # Avoid undefined variable bug
286
- },
287
- None,
288
- )
289
- )
290
-
291
- # Act
292
- status(agent_name="test-agent", organization="test-org")
293
-
294
- # Assert
295
- captured = capsys.readouterr()
296
- assert "Managed Keys" in captured.out
297
- assert "Disabled" in captured.out
298
-
299
- def test_handles_boolean_keys_proxy_value(self, mock_api, capsys):
300
- """Agent status should handle boolean value for backward compatibility."""
301
- # Arrange
302
- from pipecatcloud.cli.commands.agent import status
303
-
304
- mock_api.agent = AsyncMock(
305
- return_value=(
306
- {
307
- "ready": True,
308
- "deployment": {"manifest": {"spec": {"integratedKeysProxy": True}}},
309
- "autoScaling": {
310
- "minReplicas": 0,
311
- "maxReplicas": 1,
312
- }, # Avoid undefined variable bug
313
- },
314
- None,
315
- )
316
- )
317
-
318
- # Act
319
- status(agent_name="test-agent", organization="test-org")
320
-
321
- # Assert
322
- captured = capsys.readouterr()
323
- assert "Managed Keys" in captured.out
324
- assert "Enabled" in captured.out
325
-
326
-
327
- class TestBackwardCompatibility:
328
- """Test backward compatibility with existing deployments."""
329
-
330
- def test_existing_config_without_keys_proxy_works(self):
331
- """Existing configs without managed keys field should work unchanged."""
332
- # Arrange
333
- config = DeployConfigParams(agent_name="test-agent", image="test:latest", enable_krisp=True)
334
-
335
- # Act
336
- result = config.to_dict()
337
-
338
- # Assert
339
- assert result["enable_krisp"] is True
340
- assert result["enable_managed_keys"] is False
341
-
342
- def test_api_response_without_keys_proxy_doesnt_crash(self):
343
- """Agent status should handle API responses without managed keys field."""
344
- # Arrange
345
- data = {"ready": True, "deployment": {"manifest": {"spec": {"image": "test:latest"}}}}
346
-
347
- # Act - simulate the logic from agent.py
348
- integrated_keys = (
349
- data.get("deployment", {})
350
- .get("manifest", {})
351
- .get("spec", {})
352
- .get("integratedKeysProxy", {})
353
- )
354
- if isinstance(integrated_keys, dict):
355
- keys_proxy_enabled = integrated_keys.get("enabled", False)
356
- else:
357
- keys_proxy_enabled = bool(integrated_keys)
358
-
359
- # Assert
360
- assert keys_proxy_enabled is False
361
-
362
- @pytest.fixture
363
- def temp_legacy_config(self, tmp_path):
364
- """Create a legacy TOML config without managed keys."""
365
- config_path = tmp_path / "pcc-deploy.toml"
366
- config_content = """
367
- agent_name = "legacy-agent"
368
- image = "legacy:latest"
369
- enable_krisp = true
370
-
371
- [scaling]
372
- min_instances = 1
373
- max_instances = 5
374
- """
375
- config_path.write_text(config_content)
376
- return config_path
377
-
378
- def test_legacy_toml_loads_with_defaults(self, temp_legacy_config):
379
- """Legacy TOML files should load with managed keys defaulting to disabled."""
380
- # Arrange & Act
381
- with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_legacy_config)):
382
- config = load_deploy_config_file()
383
-
384
- # Assert
385
- assert config is not None
386
- assert config.agent_name == "legacy-agent"
387
- assert config.enable_krisp is True
388
- assert config.enable_managed_keys is False
389
- assert config.scaling.min_agents == 1 # Converted from min_instances
390
-
391
-
392
- class TestErrorHandling:
393
- """Test error handling and edge cases."""
394
-
395
- @pytest.mark.asyncio
396
- async def test_api_error_during_deploy_with_keys_proxy(self):
397
- """API errors during deployment should be handled properly."""
398
- # Arrange
399
- api = _API(token="test-token", is_cli=True)
400
- config = DeployConfigParams(
401
- agent_name="test-agent", image="test:latest", enable_managed_keys=True
402
- )
403
-
404
- with patch.object(api, "_base_request", new_callable=AsyncMock) as mock_request:
405
- mock_request.side_effect = Exception("Network error")
406
-
407
- # Act & Assert
408
- with pytest.raises(Exception, match="Network error"):
409
- await api._deploy(config, "test-org", update=False)
410
-
411
- def test_simultaneous_krisp_and_keys_proxy(self):
412
- """Both Krisp and Keys Proxy can be enabled simultaneously."""
413
- # Arrange
414
- config = DeployConfigParams(
415
- agent_name="test-agent",
416
- image="test:latest",
417
- enable_krisp=True,
418
- enable_managed_keys=True,
419
- )
420
-
421
- # Act
422
- result = config.to_dict()
423
-
424
- # Assert
425
- assert result["enable_krisp"] is True
426
- assert result["enable_managed_keys"] is True
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes