pipecatcloud 0.7.0__tar.gz → 0.7.2__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 (71) hide show
  1. pipecatcloud-0.7.2/.github/workflows/python-compatibility.yml +45 -0
  2. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/CHANGELOG.md +29 -0
  3. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/PKG-INFO +1 -1
  4. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/agent.py +1 -1
  5. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/deploy.py +66 -5
  6. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/organizations.py +1 -1
  7. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/secrets.py +2 -2
  8. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/PKG-INFO +1 -1
  9. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/SOURCES.txt +2 -0
  10. pipecatcloud-0.7.2/tests/test_deploy_poll.py +43 -0
  11. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_regions.py +1 -1
  12. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.claude/settings.json +0 -0
  13. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.github/workflows/format.yml +0 -0
  14. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.github/workflows/publish-pypi.yml +0 -0
  15. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.github/workflows/publish-test.yml +0 -0
  16. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.github/workflows/tests.yml +0 -0
  17. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.gitignore +0 -0
  18. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/.pre-commit-config.yaml +0 -0
  19. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/CLAUDE.md +0 -0
  20. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/LICENSE +0 -0
  21. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/README.md +0 -0
  22. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/pipecat-cloud.png +0 -0
  23. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/pyproject.toml +0 -0
  24. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/pytest.ini +0 -0
  25. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/scripts/fix-ruff.sh +0 -0
  26. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/setup.cfg +0 -0
  27. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/__init__.py +0 -0
  28. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/__main__.py +0 -0
  29. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/__version__.py +0 -0
  30. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/__init__.py +0 -0
  31. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/async_utils.py +0 -0
  32. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  33. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/build_utils.py +0 -0
  34. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/console_utils.py +0 -0
  35. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  36. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/regions.py +0 -0
  37. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/agent.py +0 -0
  38. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/api.py +0 -0
  39. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/__init__.py +0 -0
  40. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/api.py +0 -0
  41. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  42. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/auth.py +0 -0
  43. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/build.py +0 -0
  44. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/docker.py +0 -0
  45. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/regions.py +0 -0
  46. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/config.py +0 -0
  47. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/entry_point.py +0 -0
  48. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/config.py +0 -0
  49. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/constants.py +0 -0
  50. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/exception.py +0 -0
  51. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/py.typed +0 -0
  52. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/session.py +0 -0
  53. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  54. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  55. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  56. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/requires.txt +0 -0
  57. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  58. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/__init__.py +0 -0
  59. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/conftest.py +0 -0
  60. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_agent_sessions.py +0 -0
  61. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_agent_stop.py +0 -0
  62. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_api_client.py +0 -0
  63. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_auth.py +0 -0
  64. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_build_utils.py +0 -0
  65. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_config_file.py +0 -0
  66. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_deployment_status.py +0 -0
  67. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_docker_commands.py +0 -0
  68. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_krisp_viva.py +0 -0
  69. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_max_session_duration.py +0 -0
  70. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/tests/test_pat_auth.py +0 -0
  71. {pipecatcloud-0.7.0 → pipecatcloud-0.7.2}/uv.lock +0 -0
@@ -0,0 +1,45 @@
1
+ name: python-compatibility
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - '**'
11
+
12
+ concurrency:
13
+ group: python-compat-${{ github.event.pull_request.number || github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ compat:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ['3.11', '3.12', '3.13', '3.14']
23
+
24
+ name: Python ${{ matrix.python-version }}
25
+ steps:
26
+ - name: Checkout repo
27
+ uses: actions/checkout@v4
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@v3
31
+ with:
32
+ version: 'latest'
33
+
34
+ - name: Set up Python ${{ matrix.python-version }}
35
+ run: |
36
+ uv python install ${{ matrix.python-version }}
37
+ uv python pin ${{ matrix.python-version }}
38
+
39
+ - name: Install dependencies
40
+ run: uv sync --group dev
41
+
42
+ - name: Verify CLI loads
43
+ run: |
44
+ uv run python --version
45
+ uv run pcc --help
@@ -5,6 +5,35 @@ 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.7.2] - 2026-05-29
9
+
10
+ ### Fixed
11
+
12
+ - `pcc deploy` no longer crashes with `AttributeError: 'NoneType' object has
13
+ no attribute 'get'` when a status poll hits a transient API error (e.g. a
14
+ 502 from a briefly-unavailable API pod). The poll loop now checks for the
15
+ error before reading the status payload, and transient failures (5xx,
16
+ network blips, a not-yet-visible service) are retried for up to 5
17
+ consecutive attempts before giving up. A successful poll resets that
18
+ budget, so an isolated blip no longer surfaces as a traceback or fails an
19
+ otherwise-successful deploy. Non-transient errors (4xx, auth) still abort
20
+ immediately.
21
+
22
+ ## [0.7.1] - 2026-05-14
23
+
24
+ ### Fixed
25
+
26
+ - The `pcc` CLI now imports cleanly under Python 3.14. Previously, two
27
+ Typer command callbacks were defined with the names `list` and `set`,
28
+ shadowing the builtins. Under PEP 649's lazy annotation evaluation in
29
+ 3.14, a separate command annotated `secrets: list[str]` then resolved
30
+ `list` to the decorated `FunctionWithAio` object and failed with
31
+ `TypeError: 'FunctionWithAio' object is not subscriptable` at import
32
+ time, breaking every CLI entrypoint. The callbacks have been renamed
33
+ (`create_set`, `list_sets`, `list_agents`, `list_organizations`); the
34
+ user-facing command names (`pcc secrets set`, `pcc secrets list`,
35
+ `pcc agent list`, `pcc organizations list`) are unchanged.
36
+
8
37
  ## [0.7.0] - 2026-05-07
9
38
 
10
39
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.7.0
3
+ Version: 0.7.2
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/
@@ -89,7 +89,7 @@ def format_cpu(millicores: int) -> str:
89
89
  @agent_cli.command(name="list", help="List agents in an organization.")
90
90
  @synchronizer.create_blocking
91
91
  @requires_login
92
- async def list(
92
+ async def list_agents(
93
93
  organization: str = typer.Option(
94
94
  None, "--organization", "-o", help="Organization to list agents for"
95
95
  ),
@@ -43,6 +43,31 @@ from pipecatcloud.constants import KRISP_VIVA_MODELS, Region
43
43
 
44
44
  MAX_ALIVE_CHECKS = 120
45
45
  ALIVE_CHECK_SLEEP = 5
46
+ # How many *consecutive* transient status-poll failures to tolerate before
47
+ # giving up. A single successful poll resets this budget, so this only trips on
48
+ # a sustained outage, not on isolated blips.
49
+ MAX_CONSECUTIVE_POLL_FAILURES = 5
50
+
51
+
52
+ def _poll_failure_is_transient(error: dict | None) -> bool:
53
+ """Decide whether a failed status poll should be retried rather than aborting.
54
+
55
+ A status poll can fail in a few ways, and only some are worth retrying:
56
+
57
+ - **No error payload** (``error is None``): the request never received a
58
+ response (connection reset, timeout) or the service object isn't visible
59
+ yet. Always transient.
60
+ - **5xx**: a server-side blip (e.g. the 502 from PCC-867). Transient.
61
+ - **4xx or a non-numeric application code**: a real client/auth/config
62
+ problem that won't clear on its own. Not transient — abort.
63
+ """
64
+ if not error:
65
+ return True
66
+ code = str(error.get("code", "")) if isinstance(error, dict) else ""
67
+ if code.isdigit():
68
+ return 500 <= int(code) <= 599
69
+ return False
70
+
46
71
 
47
72
  # ----- Cloud Build Flow
48
73
 
@@ -356,6 +381,7 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
356
381
  is_ready = False
357
382
  last_status = None
358
383
  checks_performed = 0
384
+ consecutive_failures = 0
359
385
 
360
386
  console.print(
361
387
  f"[bold cyan]{'Updating' if existing_agent else 'Pushing'}[/bold cyan] deployment for agent '{params.agent_name}'"
@@ -375,6 +401,46 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
375
401
 
376
402
  logger.debug(f"Deployment status: {agent_status}")
377
403
 
404
+ # A failed poll returns (None, error). Handle that here, *before*
405
+ # touching agent_status, so a transient blip can't crash the
406
+ # deploy with an AttributeError on None. Transient failures (5xx,
407
+ # network, not-yet-visible service) are retried up to a small
408
+ # consecutive cap; non-transient ones (4xx/auth) abort right away.
409
+ if error or agent_status is None:
410
+ if _poll_failure_is_transient(error):
411
+ consecutive_failures += 1
412
+ if consecutive_failures <= MAX_CONSECUTIVE_POLL_FAILURES:
413
+ logger.debug(
414
+ "Transient status-poll failure "
415
+ f"({consecutive_failures}/{MAX_CONSECUTIVE_POLL_FAILURES}): "
416
+ f"{error}"
417
+ )
418
+ status.update(
419
+ "[dim]Waiting for deployment status "
420
+ "(retrying after a transient API error)...[/dim]"
421
+ )
422
+ await asyncio.sleep(ALIVE_CHECK_SLEEP)
423
+ checks_performed += 1
424
+ continue
425
+ # Sustained failures — the deploy itself may well have
426
+ # succeeded, so point the user at `agent status` rather
427
+ # than implying it failed.
428
+ status.stop()
429
+ console.error(
430
+ "Error checking deployment status: the API returned a "
431
+ f"transient error {MAX_CONSECUTIVE_POLL_FAILURES} times in a row.\n"
432
+ "Your deployment may still be in progress. Check it with "
433
+ f"`{PIPECAT_CLI_NAME} agent status {params.agent_name}`"
434
+ )
435
+ return typer.Exit()
436
+ # Non-transient error (4xx, auth, etc.) — abort immediately.
437
+ status.stop()
438
+ console.error("Error checking deployment status")
439
+ return typer.Exit()
440
+
441
+ # Successful poll — reset the transient-failure budget.
442
+ consecutive_failures = 0
443
+
378
444
  # Look for any error messages in the agent status
379
445
  # Exit out of the polling loop if we find an error
380
446
  status_errors = agent_status.get("errors", [])
@@ -388,11 +454,6 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
388
454
  console.error(f"Deployment failed with an unknown error: {status_errors}")
389
455
  return typer.Exit()
390
456
 
391
- if error:
392
- status.stop()
393
- console.error("Error checking deployment status")
394
- return typer.Exit()
395
-
396
457
  # Capture the deployment ID for display
397
458
  desired_deployment_id = agent_status.get(
398
459
  "desiredDeploymentId", agent_status.get("activeDeploymentId")
@@ -96,7 +96,7 @@ async def select(organization: str = typer.Option(None, "--organization", "-o"))
96
96
  @organization_cli.command(name="list", help="List organizations user is a member of.")
97
97
  @synchronizer.create_blocking
98
98
  @requires_login
99
- async def list():
99
+ async def list_organizations():
100
100
  current_org = config.get("org")
101
101
 
102
102
  with console.status(
@@ -76,7 +76,7 @@ def validate_secret_name(name: str):
76
76
  @secrets_cli.command(name="set", help="Create a new secret set for active organization")
77
77
  @synchronizer.create_blocking
78
78
  @requires_login
79
- async def set(
79
+ async def create_set(
80
80
  name: str = typer.Argument(help="Name of the secret set to create e.g. 'my-secret-set'"),
81
81
  secrets: list[str] = typer.Argument(
82
82
  None,
@@ -361,7 +361,7 @@ async def unset(
361
361
  @secrets_cli.command(name="list", help="List secret sets and set keys")
362
362
  @synchronizer.create_blocking
363
363
  @requires_login
364
- async def list(
364
+ async def list_sets(
365
365
  name: str = typer.Argument(
366
366
  None, help="Name of the secret set to list secrets from e.g. 'my-secret-set'"
367
367
  ),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.7.0
3
+ Version: 0.7.2
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/
@@ -12,6 +12,7 @@ uv.lock
12
12
  .github/workflows/format.yml
13
13
  .github/workflows/publish-pypi.yml
14
14
  .github/workflows/publish-test.yml
15
+ .github/workflows/python-compatibility.yml
15
16
  .github/workflows/tests.yml
16
17
  scripts/fix-ruff.sh
17
18
  src/pipecatcloud/__init__.py
@@ -59,6 +60,7 @@ tests/test_api_client.py
59
60
  tests/test_auth.py
60
61
  tests/test_build_utils.py
61
62
  tests/test_config_file.py
63
+ tests/test_deploy_poll.py
62
64
  tests/test_deployment_status.py
63
65
  tests/test_docker_commands.py
64
66
  tests/test_krisp_viva.py
@@ -0,0 +1,43 @@
1
+ """
2
+ Unit tests for deploy status-poll failure handling (PCC-872).
3
+
4
+ The deploy poll loop must not crash when a status poll fails: `API.agent`
5
+ returns `(None, error)` on any non-2xx, so the loop has to classify the failure
6
+ before touching the (None) status payload. `_poll_failure_is_transient` is that
7
+ classifier — transient blips get retried, real problems abort.
8
+ """
9
+
10
+ from pipecatcloud.cli.commands.deploy import _poll_failure_is_transient
11
+
12
+
13
+ class TestTransientFailures:
14
+ """Failures that should be retried rather than failing the deploy."""
15
+
16
+ def test_no_error_payload_is_transient(self):
17
+ # Network-level failures (timeout, connection reset) and 200-without-body
18
+ # responses surface as (None, None) — no structured error at all.
19
+ assert _poll_failure_is_transient(None) is True
20
+
21
+ def test_502_bad_gateway_is_transient(self):
22
+ # The exact case from the ticket: API pod cycled by its liveness probe.
23
+ assert _poll_failure_is_transient({"error": "Bad Gateway", "code": "502"}) is True
24
+
25
+ def test_500_and_503_are_transient(self):
26
+ assert _poll_failure_is_transient({"code": "500"}) is True
27
+ assert _poll_failure_is_transient({"code": "503"}) is True
28
+
29
+
30
+ class TestFatalFailures:
31
+ """Failures that won't clear on their own and should abort the deploy."""
32
+
33
+ def test_4xx_is_fatal(self):
34
+ assert _poll_failure_is_transient({"error": "Unauthorized", "code": "401"}) is False
35
+ assert _poll_failure_is_transient({"error": "Not Found", "code": "404"}) is False
36
+
37
+ def test_non_numeric_application_code_is_fatal(self):
38
+ # Structured PCC errors carry codes like this rather than HTTP statuses.
39
+ assert _poll_failure_is_transient({"code": "AGENT_CONFIG_INVALID"}) is False
40
+
41
+ def test_missing_code_with_error_present_is_fatal(self):
42
+ # An error dict with no code we can classify: don't assume retryable.
43
+ assert _poll_failure_is_transient({"error": "something broke"}) is False
@@ -179,7 +179,7 @@ class TestCLIRegionValidation:
179
179
  """Secrets set should accept valid regions from API."""
180
180
  # Arrange
181
181
  from pipecatcloud._utils import regions
182
- from pipecatcloud.cli.commands.secrets import set as secrets_set
182
+ from pipecatcloud.cli.commands.secrets import create_set as secrets_set
183
183
 
184
184
  regions._regions_cache = None
185
185
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes