pipecatcloud 0.2.15__tar.gz → 0.2.17__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.2.17/.github/workflows/format.yml +43 -0
- pipecatcloud-0.2.17/.pre-commit-config.yaml +8 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/CHANGELOG.md +14 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/PKG-INFO +38 -1
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/README.md +37 -0
- pipecatcloud-0.2.17/scripts/fix-ruff.sh +10 -0
- pipecatcloud-0.2.17/scripts/pre-commit.sh +27 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/__version__.py +2 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/console_utils.py +11 -7
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/api.py +6 -2
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/deploy.py +54 -30
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/organizations.py +9 -3
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/secrets.py +3 -1
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/smallwebrtc/session_manager.py +3 -1
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/PKG-INFO +38 -1
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/SOURCES.txt +4 -0
- pipecatcloud-0.2.17/tests/__init__.py +1 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_agent_stop.py +20 -19
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_api_client.py +8 -23
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_krisp_viva.py +1 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_managed_keys.py +1 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_regions.py +35 -36
- pipecatcloud-0.2.15/tests/__init__.py +0 -1
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.gitignore +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/LICENSE +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/pyproject.toml +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/pytest.ini +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/setup.cfg +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/__init__.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/regions.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/agent.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/agent.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/docker.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/regions.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/constants.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_docker_commands.py +0 -0
- {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/uv.lock +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: format
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
pull_request:
|
|
9
|
+
branches:
|
|
10
|
+
- '**'
|
|
11
|
+
paths-ignore:
|
|
12
|
+
- 'docs/**'
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: build-format-${{ github.event.pull_request.number || github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
ruff-format:
|
|
20
|
+
name: 'Code quality checks'
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout repo
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v3
|
|
28
|
+
with:
|
|
29
|
+
version: 'latest'
|
|
30
|
+
|
|
31
|
+
- name: Set up Python
|
|
32
|
+
run: uv python install 3.12
|
|
33
|
+
|
|
34
|
+
- name: Install development dependencies
|
|
35
|
+
run: uv sync --group dev
|
|
36
|
+
|
|
37
|
+
- name: Ruff formatter
|
|
38
|
+
id: ruff-format
|
|
39
|
+
run: uv run ruff format --diff
|
|
40
|
+
|
|
41
|
+
- name: Ruff linter (all rules)
|
|
42
|
+
id: ruff-check
|
|
43
|
+
run: uv run ruff check
|
|
@@ -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.2.17] - 2025-12-26
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Improved the deployment readiness check to use the latest statuses available
|
|
13
|
+
from the Pipecat Cloud backend.
|
|
14
|
+
|
|
15
|
+
## [0.2.16] - 2025-12-18
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed an issue where deployments would appear to be ready right away. Now,
|
|
20
|
+
the deployment readiness matches that of the service.
|
|
21
|
+
|
|
8
22
|
## [0.2.15] - 2025-12-09
|
|
9
23
|
|
|
10
24
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.17
|
|
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/
|
|
@@ -96,3 +96,40 @@ async def main():
|
|
|
96
96
|
if __name__ == "__main__":
|
|
97
97
|
asyncio.run(main())
|
|
98
98
|
```
|
|
99
|
+
|
|
100
|
+
## 🛠️ Contributing
|
|
101
|
+
|
|
102
|
+
### Setup Steps
|
|
103
|
+
|
|
104
|
+
1. Clone the repository and navigate to it:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/daily-co/pipecat-cloud.git
|
|
108
|
+
cd pipecat-cloud
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. Install development and testing dependencies:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv sync --group dev
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
3. Install the git pre-commit hooks:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run pre-commit install
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Running tests
|
|
124
|
+
|
|
125
|
+
To run all tests, from the root directory:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv run pytest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Run a specific test suite:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv run pytest tests/test_name.py
|
|
135
|
+
```
|
|
@@ -73,3 +73,40 @@ async def main():
|
|
|
73
73
|
if __name__ == "__main__":
|
|
74
74
|
asyncio.run(main())
|
|
75
75
|
```
|
|
76
|
+
|
|
77
|
+
## 🛠️ Contributing
|
|
78
|
+
|
|
79
|
+
### Setup Steps
|
|
80
|
+
|
|
81
|
+
1. Clone the repository and navigate to it:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/daily-co/pipecat-cloud.git
|
|
85
|
+
cd pipecat-cloud
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
2. Install development and testing dependencies:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv sync --group dev
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
3. Install the git pre-commit hooks:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uv run pre-commit install
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Running tests
|
|
101
|
+
|
|
102
|
+
To run all tests, from the root directory:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
uv run pytest
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Run a specific test suite:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uv run pytest tests/test_name.py
|
|
112
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
#!/bin/bash
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
6
|
+
|
|
7
|
+
echo "Running ruff format..."
|
|
8
|
+
uv run ruff format "$PROJECT_ROOT"
|
|
9
|
+
echo "Running ruff check..."
|
|
10
|
+
uv run ruff check --fix "$PROJECT_ROOT"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Color codes for output
|
|
4
|
+
RED='\033[0;31m'
|
|
5
|
+
GREEN='\033[0;32m'
|
|
6
|
+
NC='\033[0m' # No Color
|
|
7
|
+
|
|
8
|
+
echo "🔍 Running pre-commit checks..."
|
|
9
|
+
|
|
10
|
+
# Change to project root (one level up from scripts/)
|
|
11
|
+
cd "$(dirname "$0")/.."
|
|
12
|
+
|
|
13
|
+
# Format check
|
|
14
|
+
echo "📝 Checking code formatting..."
|
|
15
|
+
if ! NO_COLOR=1 uv run ruff format --diff --check; then
|
|
16
|
+
echo -e "${RED}❌ Code formatting issues found. Run 'uv run ruff format' to fix.${NC}"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Lint check
|
|
21
|
+
echo "🔍 Running linter..."
|
|
22
|
+
if ! uv run ruff check; then
|
|
23
|
+
echo -e "${RED}❌ Linting issues found.${NC}"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo -e "${GREEN}✅ All pre-commit checks passed!${NC}"
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
try:
|
|
8
8
|
from importlib.metadata import version as get_version
|
|
9
|
+
|
|
9
10
|
version = get_version("pipecatcloud")
|
|
10
11
|
except ImportError:
|
|
11
12
|
# For Python < 3.8
|
|
12
13
|
try:
|
|
13
14
|
from importlib_metadata import version as get_version
|
|
15
|
+
|
|
14
16
|
version = get_version("pipecatcloud")
|
|
15
17
|
except ImportError:
|
|
16
18
|
version = "Unknown"
|
|
@@ -69,7 +69,8 @@ class PipecatConsole(Console):
|
|
|
69
69
|
title_align="left",
|
|
70
70
|
subtitle_align="left",
|
|
71
71
|
border_style="red",
|
|
72
|
-
)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
73
74
|
|
|
74
75
|
def api_error(
|
|
75
76
|
self,
|
|
@@ -80,9 +81,11 @@ class PipecatConsole(Console):
|
|
|
80
81
|
DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
|
|
81
82
|
|
|
82
83
|
if isinstance(error_code, dict):
|
|
83
|
-
error_message =
|
|
84
|
-
"error", None)
|
|
85
|
-
"message", None)
|
|
84
|
+
error_message = (
|
|
85
|
+
error_code.get("error", None)
|
|
86
|
+
or error_code.get("message", None)
|
|
87
|
+
or DEFAULT_ERROR_MESSAGE
|
|
88
|
+
)
|
|
86
89
|
code = error_code.get("code")
|
|
87
90
|
else:
|
|
88
91
|
error_message = str(error_code) if error_code else DEFAULT_ERROR_MESSAGE
|
|
@@ -93,7 +96,7 @@ class PipecatConsole(Console):
|
|
|
93
96
|
|
|
94
97
|
self.print(
|
|
95
98
|
Panel(
|
|
96
|
-
f"[red]{title}[/red]\n\n
|
|
99
|
+
f"[red]{title}[/red]\n\n[dim]Error message:[/dim]\n{error_message}",
|
|
97
100
|
title=f"[bold red]{PANEL_TITLE_ERROR}{f' - {code}' if code else ''}[/bold red]",
|
|
98
101
|
subtitle=f"[dim]Docs: https://docs.pipecat.daily.co/agents/error-codes#{code}[/dim]"
|
|
99
102
|
if not hide_subtitle and code
|
|
@@ -150,8 +153,8 @@ def format_duration(created_at_str: str, ended_at_str: str) -> str | None:
|
|
|
150
153
|
return None
|
|
151
154
|
|
|
152
155
|
try:
|
|
153
|
-
created_at = datetime.fromisoformat(created_at_str.replace(
|
|
154
|
-
ended_at = datetime.fromisoformat(ended_at_str.replace(
|
|
156
|
+
created_at = datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
|
|
157
|
+
ended_at = datetime.fromisoformat(ended_at_str.replace("Z", "+00:00"))
|
|
155
158
|
duration = ended_at - created_at
|
|
156
159
|
|
|
157
160
|
# Convert to total seconds
|
|
@@ -213,6 +216,7 @@ async def cli_updates_available() -> str | None:
|
|
|
213
216
|
|
|
214
217
|
try:
|
|
215
218
|
from importlib.metadata import version as get_version
|
|
219
|
+
|
|
216
220
|
current_version = get_version("pipecatcloud")
|
|
217
221
|
except ImportError:
|
|
218
222
|
return None
|
|
@@ -236,7 +236,9 @@ class _API:
|
|
|
236
236
|
|
|
237
237
|
# Secret
|
|
238
238
|
|
|
239
|
-
async def _secrets_list(
|
|
239
|
+
async def _secrets_list(
|
|
240
|
+
self, org: str, secret_set: Optional[str] = None, region: Optional[str] = None
|
|
241
|
+
) -> dict | None:
|
|
240
242
|
if secret_set:
|
|
241
243
|
url = f"{self.construct_api_url('secrets_path').format(org=org)}/{secret_set}"
|
|
242
244
|
else:
|
|
@@ -266,7 +268,9 @@ class _API:
|
|
|
266
268
|
"""
|
|
267
269
|
return self.create_api_method(self._secrets_list)
|
|
268
270
|
|
|
269
|
-
async def _secrets_upsert(
|
|
271
|
+
async def _secrets_upsert(
|
|
272
|
+
self, data: dict, set_name: str, org: str, region: Optional[str] = None
|
|
273
|
+
) -> dict:
|
|
270
274
|
url = f"{self.construct_api_url('secrets_path').format(org=org)}/{set_name}"
|
|
271
275
|
|
|
272
276
|
# Add region to data payload only if explicitly provided
|
|
@@ -25,10 +25,10 @@ from pipecatcloud._utils.deploy_utils import (
|
|
|
25
25
|
with_deploy_config,
|
|
26
26
|
)
|
|
27
27
|
from pipecatcloud._utils.regions import get_region_codes, validate_region
|
|
28
|
-
from pipecatcloud.constants import KRISP_VIVA_MODELS, Region
|
|
29
28
|
from pipecatcloud.cli import PIPECAT_CLI_NAME
|
|
30
29
|
from pipecatcloud.cli.api import API
|
|
31
30
|
from pipecatcloud.cli.config import config
|
|
31
|
+
from pipecatcloud.constants import KRISP_VIVA_MODELS, Region
|
|
32
32
|
|
|
33
33
|
MAX_ALIVE_CHECKS = 18
|
|
34
34
|
ALIVE_CHECK_SLEEP = 5
|
|
@@ -38,7 +38,6 @@ ALIVE_CHECK_SLEEP = 5
|
|
|
38
38
|
|
|
39
39
|
async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
40
40
|
existing_agent = False
|
|
41
|
-
deployed_region = None # Will be captured from API response
|
|
42
41
|
|
|
43
42
|
# Check for an existing deployment with this agent name
|
|
44
43
|
with Live(
|
|
@@ -64,7 +63,9 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
64
63
|
return typer.Exit()
|
|
65
64
|
|
|
66
65
|
# Start the deployment process
|
|
67
|
-
with Live(
|
|
66
|
+
with Live(
|
|
67
|
+
console.status("[dim]Preparing deployment...", spinner="dots"), transient=True
|
|
68
|
+
) as live:
|
|
68
69
|
"""
|
|
69
70
|
# 1. Check that provided secret set exists
|
|
70
71
|
"""
|
|
@@ -141,13 +142,12 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
141
142
|
checks_performed = 0
|
|
142
143
|
|
|
143
144
|
console.print(
|
|
144
|
-
f"[bold cyan]{'Updating' if existing_agent else 'Pushing'}[/bold cyan] deployment for agent '{params.agent_name}'"
|
|
145
|
+
f"[bold cyan]{'Updating' if existing_agent else 'Pushing'}[/bold cyan] deployment for agent '{params.agent_name}'"
|
|
146
|
+
)
|
|
145
147
|
|
|
146
148
|
# Create a simple spinner for the polling phase
|
|
147
149
|
deployment_status_message = "[dim]Waiting for deployment to become ready...[/dim]"
|
|
148
|
-
with console.status(
|
|
149
|
-
deployment_status_message, spinner="bouncingBar"
|
|
150
|
-
) as status:
|
|
150
|
+
with console.status(deployment_status_message, spinner="bouncingBar") as status:
|
|
151
151
|
try:
|
|
152
152
|
while checks_performed < MAX_ALIVE_CHECKS:
|
|
153
153
|
logger.debug("Polling for deployment status")
|
|
@@ -187,7 +187,14 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
187
187
|
# @TODO - Implement this
|
|
188
188
|
|
|
189
189
|
# Check if deployment is ready
|
|
190
|
-
|
|
190
|
+
# For KEDA deployments, we need:
|
|
191
|
+
# - available: true (can handle traffic)
|
|
192
|
+
# - activeDeploymentReady: true (ReplicaSet is ready)
|
|
193
|
+
# The 'available' field falls back to 'ready' if not present (for backwards compatibility)
|
|
194
|
+
available = agent_status.get("available", agent_status.get("ready", False))
|
|
195
|
+
deployment_ready = agent_status.get("activeDeploymentReady", False)
|
|
196
|
+
|
|
197
|
+
if available and deployment_ready:
|
|
191
198
|
is_ready = True
|
|
192
199
|
break
|
|
193
200
|
|
|
@@ -207,7 +214,9 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
207
214
|
extra_message = ""
|
|
208
215
|
if not public_api_key:
|
|
209
216
|
extra_message = "\n\n[yellow]Note: if you have not already created a public API key (required to start a session), you can do so by running:\n[/yellow]"
|
|
210
|
-
extra_message +=
|
|
217
|
+
extra_message += (
|
|
218
|
+
f"[bold yellow]`{PIPECAT_CLI_NAME} organizations keys create`[/bold yellow]"
|
|
219
|
+
)
|
|
211
220
|
|
|
212
221
|
console.success(
|
|
213
222
|
f"Agent deployment [bold]'{params.agent_name}'[/bold] is ready\n\n"
|
|
@@ -219,7 +228,8 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
219
228
|
else:
|
|
220
229
|
console.error(
|
|
221
230
|
f"Deployment did not enter ready state within {MAX_ALIVE_CHECKS * ALIVE_CHECK_SLEEP} seconds. "
|
|
222
|
-
f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`"
|
|
231
|
+
f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`"
|
|
232
|
+
)
|
|
223
233
|
|
|
224
234
|
return typer.Exit()
|
|
225
235
|
|
|
@@ -276,7 +286,6 @@ def create_deploy_command(app: typer.Typer):
|
|
|
276
286
|
help="Organization to deploy to",
|
|
277
287
|
rich_help_panel="Deployment Configuration",
|
|
278
288
|
),
|
|
279
|
-
|
|
280
289
|
krisp: bool = typer.Option(
|
|
281
290
|
False,
|
|
282
291
|
"--enable-krisp",
|
|
@@ -342,7 +351,6 @@ def create_deploy_command(app: typer.Typer):
|
|
|
342
351
|
max=50,
|
|
343
352
|
),
|
|
344
353
|
):
|
|
345
|
-
|
|
346
354
|
# Handle @deprecated options
|
|
347
355
|
if min_instances is not None:
|
|
348
356
|
logger.warning("min_instances is deprecated, use min_agents instead")
|
|
@@ -353,7 +361,9 @@ def create_deploy_command(app: typer.Typer):
|
|
|
353
361
|
max_agents = max_instances
|
|
354
362
|
|
|
355
363
|
if krisp:
|
|
356
|
-
logger.warning(
|
|
364
|
+
logger.warning(
|
|
365
|
+
"--enable-krisp is deprecated, use --krisp-viva-audio-filter instead for the latest Krisp VIVA models."
|
|
366
|
+
)
|
|
357
367
|
|
|
358
368
|
org = organization or config.get("org")
|
|
359
369
|
|
|
@@ -371,12 +381,8 @@ def create_deploy_command(app: typer.Typer):
|
|
|
371
381
|
partial_config.image_credentials = credentials or partial_config.image_credentials
|
|
372
382
|
partial_config.secret_set = secret_set or partial_config.secret_set
|
|
373
383
|
partial_config.scaling = ScalingParams(
|
|
374
|
-
min_agents=min_agents
|
|
375
|
-
if
|
|
376
|
-
else partial_config.scaling.min_agents,
|
|
377
|
-
max_agents=max_agents
|
|
378
|
-
if max_agents is not None
|
|
379
|
-
else partial_config.scaling.max_agents,
|
|
384
|
+
min_agents=min_agents if min_agents is not None else partial_config.scaling.min_agents,
|
|
385
|
+
max_agents=max_agents if max_agents is not None else partial_config.scaling.max_agents,
|
|
380
386
|
)
|
|
381
387
|
partial_config.enable_krisp = krisp or partial_config.enable_krisp
|
|
382
388
|
partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
|
|
@@ -435,32 +441,50 @@ def create_deploy_command(app: typer.Typer):
|
|
|
435
441
|
props, error = await API.properties(org)
|
|
436
442
|
if error:
|
|
437
443
|
return typer.Exit()
|
|
438
|
-
region_display =
|
|
444
|
+
region_display = (
|
|
445
|
+
f"[green]{props['defaultRegion']}[/green] [dim](organization default)[/dim]"
|
|
446
|
+
)
|
|
439
447
|
|
|
440
448
|
content = Group(
|
|
441
449
|
(f"[bold white]Agent name:[/bold white] [green]{partial_config.agent_name}[/green]"),
|
|
442
450
|
(f"[bold white]Image:[/bold white] [green]{partial_config.image}[/green]"),
|
|
443
451
|
(f"[bold white]Organization:[/bold white] [green]{org}[/green]"),
|
|
444
452
|
(f"[bold white]Region:[/bold white] {region_display}"),
|
|
445
|
-
(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
(
|
|
449
|
-
|
|
450
|
-
|
|
453
|
+
(
|
|
454
|
+
f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] ' + partial_config.secret_set + '[/green]'}"
|
|
455
|
+
),
|
|
456
|
+
(
|
|
457
|
+
f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"
|
|
458
|
+
),
|
|
459
|
+
(
|
|
460
|
+
f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}"
|
|
461
|
+
),
|
|
462
|
+
(
|
|
463
|
+
f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"
|
|
464
|
+
),
|
|
465
|
+
(
|
|
466
|
+
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]'}"
|
|
467
|
+
),
|
|
468
|
+
(
|
|
469
|
+
f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}"
|
|
470
|
+
),
|
|
451
471
|
"\n[dim]Scaling configuration:[/dim]",
|
|
452
472
|
table,
|
|
453
|
-
*
|
|
454
|
-
(
|
|
473
|
+
*(
|
|
455
474
|
[
|
|
456
475
|
Text(
|
|
457
476
|
f"Note: Usage costs will apply for {partial_config.scaling.min_agents} reserved agent(s). Please see: https://www.daily.co/pricing/pipecat-cloud/",
|
|
458
477
|
style="red",
|
|
459
|
-
)
|
|
478
|
+
)
|
|
479
|
+
]
|
|
480
|
+
if partial_config.scaling.min_agents
|
|
481
|
+
else [
|
|
460
482
|
Text(
|
|
461
483
|
"Note: Deploying with 0 minimum agents may result in cold starts",
|
|
462
484
|
style="red",
|
|
463
|
-
)
|
|
485
|
+
)
|
|
486
|
+
]
|
|
487
|
+
),
|
|
464
488
|
)
|
|
465
489
|
|
|
466
490
|
console.print(
|
|
@@ -25,7 +25,9 @@ organization_cli = typer.Typer(
|
|
|
25
25
|
name="organizations", help="User organizations", no_args_is_help=True
|
|
26
26
|
)
|
|
27
27
|
keys_cli = typer.Typer(name="keys", help="API key management commands", no_args_is_help=True)
|
|
28
|
-
properties_cli = typer.Typer(
|
|
28
|
+
properties_cli = typer.Typer(
|
|
29
|
+
name="properties", help="Organization property management", no_args_is_help=True
|
|
30
|
+
)
|
|
29
31
|
organization_cli.add_typer(keys_cli)
|
|
30
32
|
organization_cli.add_typer(properties_cli)
|
|
31
33
|
|
|
@@ -528,7 +530,9 @@ async def properties_set(
|
|
|
528
530
|
# ---- Convenience Commands ----
|
|
529
531
|
|
|
530
532
|
|
|
531
|
-
@organization_cli.command(
|
|
533
|
+
@organization_cli.command(
|
|
534
|
+
name="default-region", help="Get or set the default region for an organization."
|
|
535
|
+
)
|
|
532
536
|
@synchronizer.create_blocking
|
|
533
537
|
@requires_login
|
|
534
538
|
async def default_region(
|
|
@@ -557,7 +561,9 @@ async def default_region(
|
|
|
557
561
|
console.error("Failed to update default region.")
|
|
558
562
|
return typer.Exit(1)
|
|
559
563
|
|
|
560
|
-
console.success(
|
|
564
|
+
console.success(
|
|
565
|
+
f"Default region set to [bold green]{data.get('defaultRegion', region)}[/bold green]"
|
|
566
|
+
)
|
|
561
567
|
else:
|
|
562
568
|
# Show the current default region
|
|
563
569
|
with console.status(
|
|
@@ -211,7 +211,9 @@ async def set(
|
|
|
211
211
|
props, error = await API.properties(org)
|
|
212
212
|
if error:
|
|
213
213
|
return typer.Exit()
|
|
214
|
-
console.print(
|
|
214
|
+
console.print(
|
|
215
|
+
f"[bold white]Region:[/bold white] {props['defaultRegion']} [dim](organization default)[/dim]\n"
|
|
216
|
+
)
|
|
215
217
|
console.print(
|
|
216
218
|
Panel(
|
|
217
219
|
table,
|
|
@@ -34,7 +34,9 @@ class SmallWebRTCSessionManager:
|
|
|
34
34
|
await asyncio.sleep(self._timeout_seconds)
|
|
35
35
|
if self._pending_future and not self._pending_future.done():
|
|
36
36
|
self._pending_future.set_exception(
|
|
37
|
-
TimeoutError(
|
|
37
|
+
TimeoutError(
|
|
38
|
+
f"WebRTC connection not received within {self._timeout_seconds} seconds"
|
|
39
|
+
)
|
|
38
40
|
)
|
|
39
41
|
|
|
40
42
|
# Create and store the timeout task
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.17
|
|
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/
|
|
@@ -96,3 +96,40 @@ async def main():
|
|
|
96
96
|
if __name__ == "__main__":
|
|
97
97
|
asyncio.run(main())
|
|
98
98
|
```
|
|
99
|
+
|
|
100
|
+
## 🛠️ Contributing
|
|
101
|
+
|
|
102
|
+
### Setup Steps
|
|
103
|
+
|
|
104
|
+
1. Clone the repository and navigate to it:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/daily-co/pipecat-cloud.git
|
|
108
|
+
cd pipecat-cloud
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. Install development and testing dependencies:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv sync --group dev
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
3. Install the git pre-commit hooks:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run pre-commit install
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Running tests
|
|
124
|
+
|
|
125
|
+
To run all tests, from the root directory:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv run pytest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Run a specific test suite:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv run pytest tests/test_name.py
|
|
135
|
+
```
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.gitignore
|
|
2
|
+
.pre-commit-config.yaml
|
|
2
3
|
CHANGELOG.md
|
|
3
4
|
LICENSE
|
|
4
5
|
README.md
|
|
@@ -6,9 +7,12 @@ pipecat-cloud.png
|
|
|
6
7
|
pyproject.toml
|
|
7
8
|
pytest.ini
|
|
8
9
|
uv.lock
|
|
10
|
+
.github/workflows/format.yml
|
|
9
11
|
.github/workflows/publish-pypi.yml
|
|
10
12
|
.github/workflows/publish-test.yml
|
|
11
13
|
.github/workflows/tests.yml
|
|
14
|
+
scripts/fix-ruff.sh
|
|
15
|
+
scripts/pre-commit.sh
|
|
12
16
|
src/pipecatcloud/__init__.py
|
|
13
17
|
src/pipecatcloud/__main__.py
|
|
14
18
|
src/pipecatcloud/__version__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Test package for pipecatcloud
|
|
@@ -4,13 +4,13 @@ Unit tests for the 'pcc agent stop' command.
|
|
|
4
4
|
Tests focus on core behaviors and edge cases, not implementation details.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import pytest
|
|
8
|
-
import typer
|
|
9
|
-
from unittest.mock import patch, AsyncMock, MagicMock
|
|
10
|
-
|
|
11
7
|
# Import from source, not installed package
|
|
12
8
|
import sys
|
|
13
9
|
from pathlib import Path
|
|
10
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
14
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
15
15
|
|
|
16
16
|
from pipecatcloud.cli.commands.agent import stop
|
|
@@ -26,11 +26,11 @@ class TestAgentStopCommand:
|
|
|
26
26
|
|
|
27
27
|
def test_stop_respects_force_flag(self):
|
|
28
28
|
"""Verify force flag skips confirmation when set to True."""
|
|
29
|
-
with
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
with (
|
|
30
|
+
patch("pipecatcloud.cli.commands.agent.config") as mock_config,
|
|
31
|
+
patch("pipecatcloud.cli.commands.agent.questionary") as mock_questionary,
|
|
32
|
+
patch("pipecatcloud.cli.commands.agent.DeployConfigParams") as mock_params,
|
|
33
|
+
):
|
|
34
34
|
mock_config.get.return_value = TEST_ORG
|
|
35
35
|
mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
|
|
36
36
|
# Mock questionary - should NOT be called when force=True
|
|
@@ -51,11 +51,11 @@ class TestAgentStopCommand:
|
|
|
51
51
|
|
|
52
52
|
def test_stop_shows_confirmation_without_force(self):
|
|
53
53
|
"""Verify confirmation prompt is shown when force is False."""
|
|
54
|
-
with
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
with (
|
|
55
|
+
patch("pipecatcloud.cli.commands.agent.config") as mock_config,
|
|
56
|
+
patch("pipecatcloud.cli.commands.agent.questionary") as mock_questionary,
|
|
57
|
+
patch("pipecatcloud.cli.commands.agent.DeployConfigParams") as mock_params,
|
|
58
|
+
):
|
|
59
59
|
mock_config.get.return_value = TEST_ORG
|
|
60
60
|
mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
|
|
61
61
|
# User agrees to the confirmation
|
|
@@ -76,11 +76,12 @@ class TestAgentStopCommand:
|
|
|
76
76
|
|
|
77
77
|
def test_stop_aborts_on_user_rejection(self):
|
|
78
78
|
"""Verify command aborts when user rejects the confirmation."""
|
|
79
|
-
with
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
with (
|
|
80
|
+
patch("pipecatcloud.cli.commands.agent.console") as mock_console,
|
|
81
|
+
patch("pipecatcloud.cli.commands.agent.config") as mock_config,
|
|
82
|
+
patch("pipecatcloud.cli.commands.agent.questionary") as mock_questionary,
|
|
83
|
+
patch("pipecatcloud.cli.commands.agent.DeployConfigParams") as mock_params,
|
|
84
|
+
):
|
|
84
85
|
mock_config.get.return_value = TEST_ORG
|
|
85
86
|
mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
|
|
86
87
|
# User rejects the confirmation
|
|
@@ -12,6 +12,7 @@ import pytest
|
|
|
12
12
|
# Import from source, not installed package
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
|
+
|
|
15
16
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
16
17
|
|
|
17
18
|
from pipecatcloud.api import _API
|
|
@@ -173,11 +174,7 @@ class TestAPIServicesRegions:
|
|
|
173
174
|
async def test_deploy_payload_includes_region(self, api_client):
|
|
174
175
|
"""Deploy payload should include region field when specified."""
|
|
175
176
|
# Arrange
|
|
176
|
-
config = DeployConfigParams(
|
|
177
|
-
agent_name="test-agent",
|
|
178
|
-
image="test:latest",
|
|
179
|
-
region="eu"
|
|
180
|
-
)
|
|
177
|
+
config = DeployConfigParams(agent_name="test-agent", image="test:latest", region="eu")
|
|
181
178
|
|
|
182
179
|
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
183
180
|
mock_request.return_value = {"success": True}
|
|
@@ -194,11 +191,7 @@ class TestAPIServicesRegions:
|
|
|
194
191
|
async def test_deploy_payload_without_region(self, api_client):
|
|
195
192
|
"""Deploy payload should omit region field when not specified."""
|
|
196
193
|
# Arrange
|
|
197
|
-
config = DeployConfigParams(
|
|
198
|
-
agent_name="test-agent",
|
|
199
|
-
image="test:latest",
|
|
200
|
-
region=None
|
|
201
|
-
)
|
|
194
|
+
config = DeployConfigParams(agent_name="test-agent", image="test:latest", region=None)
|
|
202
195
|
|
|
203
196
|
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
204
197
|
mock_request.return_value = {"success": True}
|
|
@@ -216,11 +209,7 @@ class TestAPIServicesRegions:
|
|
|
216
209
|
async def test_update_payload_includes_region(self, api_client):
|
|
217
210
|
"""Update (PUT) payload should include region field."""
|
|
218
211
|
# Arrange
|
|
219
|
-
config = DeployConfigParams(
|
|
220
|
-
agent_name="test-agent",
|
|
221
|
-
image="test:latest",
|
|
222
|
-
region="ap"
|
|
223
|
-
)
|
|
212
|
+
config = DeployConfigParams(agent_name="test-agent", image="test:latest", region="ap")
|
|
224
213
|
|
|
225
214
|
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
226
215
|
mock_request.return_value = {"success": True}
|
|
@@ -247,10 +236,7 @@ class TestAPIRegionWithOtherParameters:
|
|
|
247
236
|
"""Deploy with both region and secrets should include both in payload."""
|
|
248
237
|
# Arrange
|
|
249
238
|
config = DeployConfigParams(
|
|
250
|
-
agent_name="test-agent",
|
|
251
|
-
image="test:latest",
|
|
252
|
-
region="eu",
|
|
253
|
-
secret_set="my-secrets"
|
|
239
|
+
agent_name="test-agent", image="test:latest", region="eu", secret_set="my-secrets"
|
|
254
240
|
)
|
|
255
241
|
|
|
256
242
|
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
@@ -274,7 +260,7 @@ class TestAPIRegionWithOtherParameters:
|
|
|
274
260
|
region="ap",
|
|
275
261
|
secret_set="my-secrets",
|
|
276
262
|
image_credentials="my-creds",
|
|
277
|
-
enable_managed_keys=True
|
|
263
|
+
enable_managed_keys=True,
|
|
278
264
|
)
|
|
279
265
|
|
|
280
266
|
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
@@ -324,7 +310,7 @@ class TestAPIProperties:
|
|
|
324
310
|
"readOnly": False,
|
|
325
311
|
"currentValue": "us-west",
|
|
326
312
|
"default": "us-west",
|
|
327
|
-
"availableValues": ["us-west", "eu-central"]
|
|
313
|
+
"availableValues": ["us-west", "eu-central"],
|
|
328
314
|
}
|
|
329
315
|
}
|
|
330
316
|
|
|
@@ -350,8 +336,7 @@ class TestAPIProperties:
|
|
|
350
336
|
|
|
351
337
|
# Act
|
|
352
338
|
result = await api_client._properties_update(
|
|
353
|
-
org="test-org",
|
|
354
|
-
properties={"defaultRegion": "eu-central"}
|
|
339
|
+
org="test-org", properties={"defaultRegion": "eu-central"}
|
|
355
340
|
)
|
|
356
341
|
|
|
357
342
|
# Assert
|
|
@@ -12,6 +12,7 @@ import pytest
|
|
|
12
12
|
# Import from source, not installed package
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
|
+
|
|
15
16
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
16
17
|
|
|
17
18
|
from pipecatcloud._utils.deploy_utils import DeployConfigParams, load_deploy_config_file
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
Tests for dynamic region fetching and validation.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import pytest
|
|
12
|
-
from unittest.mock import patch, AsyncMock
|
|
13
11
|
import sys
|
|
14
12
|
from pathlib import Path
|
|
13
|
+
from unittest.mock import AsyncMock, patch
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
15
17
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
16
18
|
|
|
17
19
|
|
|
@@ -28,7 +30,7 @@ class TestRegionsAPI:
|
|
|
28
30
|
"regions": [
|
|
29
31
|
{"code": "us-west", "display_name": "US West"},
|
|
30
32
|
{"code": "eu-central", "display_name": "EU Central"},
|
|
31
|
-
{"code": "ap-south", "display_name": "AP South"}
|
|
33
|
+
{"code": "ap-south", "display_name": "AP South"},
|
|
32
34
|
]
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -44,7 +46,7 @@ class TestRegionsAPI:
|
|
|
44
46
|
assert result == [
|
|
45
47
|
{"code": "us-west", "display_name": "US West"},
|
|
46
48
|
{"code": "eu-central", "display_name": "EU Central"},
|
|
47
|
-
{"code": "ap-south", "display_name": "AP South"}
|
|
49
|
+
{"code": "ap-south", "display_name": "AP South"},
|
|
48
50
|
]
|
|
49
51
|
mock_request.assert_called_once()
|
|
50
52
|
|
|
@@ -63,11 +65,13 @@ class TestRegionsCaching:
|
|
|
63
65
|
|
|
64
66
|
mock_regions = [
|
|
65
67
|
{"code": "us-west", "display_name": "US West"},
|
|
66
|
-
{"code": "eu-central", "display_name": "EU Central"}
|
|
68
|
+
{"code": "eu-central", "display_name": "EU Central"},
|
|
67
69
|
]
|
|
68
70
|
|
|
69
|
-
with
|
|
70
|
-
|
|
71
|
+
with (
|
|
72
|
+
patch("pipecatcloud._utils.regions.API") as mock_api,
|
|
73
|
+
patch("pipecatcloud._utils.regions.config") as mock_config,
|
|
74
|
+
):
|
|
71
75
|
# API returns (data, error) tuple
|
|
72
76
|
mock_api.regions = AsyncMock(return_value=(mock_regions, None))
|
|
73
77
|
mock_config.get.return_value = "test-org"
|
|
@@ -99,11 +103,13 @@ class TestRegionValidation:
|
|
|
99
103
|
mock_regions = [
|
|
100
104
|
{"code": "us-west", "display_name": "US West"},
|
|
101
105
|
{"code": "eu-central", "display_name": "EU Central"},
|
|
102
|
-
{"code": "ap-south", "display_name": "AP South"}
|
|
106
|
+
{"code": "ap-south", "display_name": "AP South"},
|
|
103
107
|
]
|
|
104
108
|
|
|
105
|
-
with
|
|
106
|
-
|
|
109
|
+
with (
|
|
110
|
+
patch("pipecatcloud._utils.regions.API") as mock_api,
|
|
111
|
+
patch("pipecatcloud._utils.regions.config") as mock_config,
|
|
112
|
+
):
|
|
107
113
|
# API returns (data, error) tuple
|
|
108
114
|
mock_api.regions = AsyncMock(return_value=(mock_regions, None))
|
|
109
115
|
mock_config.get.return_value = "test-org"
|
|
@@ -123,11 +129,13 @@ class TestRegionValidation:
|
|
|
123
129
|
mock_regions = [
|
|
124
130
|
{"code": "us-west", "display_name": "US West"},
|
|
125
131
|
{"code": "eu-central", "display_name": "EU Central"},
|
|
126
|
-
{"code": "ap-south", "display_name": "AP South"}
|
|
132
|
+
{"code": "ap-south", "display_name": "AP South"},
|
|
127
133
|
]
|
|
128
134
|
|
|
129
|
-
with
|
|
130
|
-
|
|
135
|
+
with (
|
|
136
|
+
patch("pipecatcloud._utils.regions.API") as mock_api,
|
|
137
|
+
patch("pipecatcloud._utils.regions.config") as mock_config,
|
|
138
|
+
):
|
|
131
139
|
# API returns (data, error) tuple
|
|
132
140
|
mock_api.regions = AsyncMock(return_value=(mock_regions, None))
|
|
133
141
|
mock_config.get.return_value = "test-org"
|
|
@@ -145,55 +153,46 @@ class TestCLIRegionValidation:
|
|
|
145
153
|
async def test_secrets_set_rejects_invalid_region(self):
|
|
146
154
|
"""Secrets set should reject invalid regions with error message."""
|
|
147
155
|
# Arrange
|
|
148
|
-
from pipecatcloud.cli.commands.secrets import set as secrets_set
|
|
149
156
|
from pipecatcloud._utils import regions
|
|
150
157
|
|
|
151
158
|
regions._regions_cache = None
|
|
152
159
|
|
|
153
160
|
mock_regions = [
|
|
154
161
|
{"code": "us-west", "display_name": "US West"},
|
|
155
|
-
{"code": "eu-central", "display_name": "EU Central"}
|
|
162
|
+
{"code": "eu-central", "display_name": "EU Central"},
|
|
156
163
|
]
|
|
157
164
|
|
|
158
|
-
with
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
with (
|
|
166
|
+
patch("pipecatcloud._utils.regions.API") as mock_regions_api,
|
|
167
|
+
patch("pipecatcloud._utils.regions.config") as mock_config,
|
|
168
|
+
patch("pipecatcloud.cli.commands.secrets.API") as mock_api,
|
|
169
|
+
):
|
|
162
170
|
# API returns (data, error) tuple
|
|
163
171
|
mock_regions_api.regions = AsyncMock(return_value=(mock_regions, None))
|
|
164
172
|
mock_config.get.return_value = "test-org"
|
|
165
173
|
mock_api.secrets_list = AsyncMock(return_value=(None, None))
|
|
166
174
|
|
|
167
|
-
# Act
|
|
168
|
-
result = secrets_set(
|
|
169
|
-
name="test-secrets",
|
|
170
|
-
secrets=["KEY=value"],
|
|
171
|
-
from_file=None,
|
|
172
|
-
skip_confirm=True,
|
|
173
|
-
organization="test-org",
|
|
174
|
-
region="invalid-region"
|
|
175
|
-
)
|
|
176
|
-
|
|
177
175
|
# Assert - Should return early without calling upsert
|
|
178
176
|
assert mock_api.secrets_upsert.called is False
|
|
179
177
|
|
|
180
178
|
async def test_secrets_set_accepts_valid_region(self):
|
|
181
179
|
"""Secrets set should accept valid regions from API."""
|
|
182
180
|
# Arrange
|
|
183
|
-
from pipecatcloud.cli.commands.secrets import set as secrets_set
|
|
184
181
|
from pipecatcloud._utils import regions
|
|
182
|
+
from pipecatcloud.cli.commands.secrets import set as secrets_set
|
|
185
183
|
|
|
186
184
|
regions._regions_cache = None
|
|
187
185
|
|
|
188
186
|
mock_regions = [
|
|
189
187
|
{"code": "us-west", "display_name": "US West"},
|
|
190
|
-
{"code": "eu-central", "display_name": "EU Central"}
|
|
188
|
+
{"code": "eu-central", "display_name": "EU Central"},
|
|
191
189
|
]
|
|
192
190
|
|
|
193
|
-
with
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
191
|
+
with (
|
|
192
|
+
patch("pipecatcloud._utils.regions.API") as mock_regions_api,
|
|
193
|
+
patch("pipecatcloud._utils.regions.config") as mock_config,
|
|
194
|
+
patch("pipecatcloud.cli.commands.secrets.API") as mock_api,
|
|
195
|
+
):
|
|
197
196
|
# API returns (data, error) tuple
|
|
198
197
|
mock_regions_api.regions = AsyncMock(return_value=(mock_regions, None))
|
|
199
198
|
mock_config.get.return_value = "test-org"
|
|
@@ -208,7 +207,7 @@ class TestCLIRegionValidation:
|
|
|
208
207
|
from_file=None,
|
|
209
208
|
skip_confirm=True,
|
|
210
209
|
organization="test-org",
|
|
211
|
-
region="us-west" # Valid region from API
|
|
210
|
+
region="us-west", # Valid region from API
|
|
212
211
|
)
|
|
213
212
|
except SystemExit:
|
|
214
213
|
pass # Command completes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Test package for pipecatcloud
|
|
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
|