pipecatcloud 0.7.1__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.1 → pipecatcloud-0.7.2}/CHANGELOG.md +14 -0
  2. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/PKG-INFO +1 -1
  3. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/deploy.py +66 -5
  4. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/PKG-INFO +1 -1
  5. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
  6. pipecatcloud-0.7.2/tests/test_deploy_poll.py +43 -0
  7. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.claude/settings.json +0 -0
  8. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/format.yml +0 -0
  9. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/publish-pypi.yml +0 -0
  10. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/publish-test.yml +0 -0
  11. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/python-compatibility.yml +0 -0
  12. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/tests.yml +0 -0
  13. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.gitignore +0 -0
  14. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.pre-commit-config.yaml +0 -0
  15. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/CLAUDE.md +0 -0
  16. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/LICENSE +0 -0
  17. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/README.md +0 -0
  18. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/pipecat-cloud.png +0 -0
  19. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/pyproject.toml +0 -0
  20. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/pytest.ini +0 -0
  21. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/scripts/fix-ruff.sh +0 -0
  22. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/setup.cfg +0 -0
  23. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/__init__.py +0 -0
  24. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/__main__.py +0 -0
  25. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/__version__.py +0 -0
  26. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/__init__.py +0 -0
  27. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/async_utils.py +0 -0
  28. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  29. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/build_utils.py +0 -0
  30. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/console_utils.py +0 -0
  31. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  32. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/regions.py +0 -0
  33. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/agent.py +0 -0
  34. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/api.py +0 -0
  35. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/__init__.py +0 -0
  36. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/api.py +0 -0
  37. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  38. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/agent.py +0 -0
  39. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/auth.py +0 -0
  40. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/build.py +0 -0
  41. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/docker.py +0 -0
  42. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  43. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/regions.py +0 -0
  44. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  45. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/config.py +0 -0
  46. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/entry_point.py +0 -0
  47. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/config.py +0 -0
  48. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/constants.py +0 -0
  49. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/exception.py +0 -0
  50. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/py.typed +0 -0
  51. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/session.py +0 -0
  52. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  53. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  54. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  55. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/requires.txt +0 -0
  56. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  57. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/__init__.py +0 -0
  58. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/conftest.py +0 -0
  59. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_agent_sessions.py +0 -0
  60. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_agent_stop.py +0 -0
  61. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_api_client.py +0 -0
  62. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_auth.py +0 -0
  63. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_build_utils.py +0 -0
  64. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_config_file.py +0 -0
  65. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_deployment_status.py +0 -0
  66. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_docker_commands.py +0 -0
  67. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_krisp_viva.py +0 -0
  68. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_max_session_duration.py +0 -0
  69. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_pat_auth.py +0 -0
  70. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_regions.py +0 -0
  71. {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/uv.lock +0 -0
@@ -5,6 +5,20 @@ 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
+
8
22
  ## [0.7.1] - 2026-05-14
9
23
 
10
24
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.7.1
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/
@@ -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")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.7.1
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/
@@ -60,6 +60,7 @@ tests/test_api_client.py
60
60
  tests/test_auth.py
61
61
  tests/test_build_utils.py
62
62
  tests/test_config_file.py
63
+ tests/test_deploy_poll.py
63
64
  tests/test_deployment_status.py
64
65
  tests/test_docker_commands.py
65
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes