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.
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/CHANGELOG.md +14 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/PKG-INFO +1 -1
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/deploy.py +66 -5
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/PKG-INFO +1 -1
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
- pipecatcloud-0.7.2/tests/test_deploy_poll.py +43 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.claude/settings.json +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/format.yml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/python-compatibility.yml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.gitignore +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/.pre-commit-config.yaml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/CLAUDE.md +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/LICENSE +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/README.md +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/pyproject.toml +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/pytest.ini +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/scripts/fix-ruff.sh +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/setup.cfg +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/__init__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/build_utils.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/console_utils.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/_utils/regions.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/agent.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/api.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/agent.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/build.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/docker.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/regions.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/commands/secrets.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/constants.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/__init__.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/conftest.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_agent_stop.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_api_client.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_auth.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_build_utils.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_config_file.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_deployment_status.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_docker_commands.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_krisp_viva.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_max_session_duration.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_pat_auth.py +0 -0
- {pipecatcloud-0.7.1 → pipecatcloud-0.7.2}/tests/test_regions.py +0 -0
- {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
|
|
@@ -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")
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|