llama-deploy-core 0.3.0a10__tar.gz → 0.3.0a11__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.
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/PKG-INFO +6 -1
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/pyproject.toml +10 -1
- llama_deploy_core-0.3.0a11/src/llama_deploy/core/client/manage_client.py +219 -0
- llama_deploy_core-0.3.0a11/src/llama_deploy/core/server/manage_api/__init__.py +10 -0
- llama_deploy_core-0.3.0a11/src/llama_deploy/core/server/manage_api/_abstract_deployments_service.py +149 -0
- llama_deploy_core-0.3.0a11/src/llama_deploy/core/server/manage_api/_create_deployments_router.py +167 -0
- llama_deploy_core-0.3.0a11/src/llama_deploy/core/server/manage_api/_exceptions.py +14 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/README.md +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/__init__.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/config.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/deployment_config.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/git/git_util.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/path_util.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/py.typed +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/__init__.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/base.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/deployments.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/git_validation.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/projects.py +0 -0
- {llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/ui_build.py +0 -0
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: llama-deploy-core
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.0a11
|
|
4
4
|
Summary: Core models and schemas for LlamaDeploy
|
|
5
5
|
License: MIT
|
|
6
|
+
Requires-Dist: fastapi>=0.115.0
|
|
6
7
|
Requires-Dist: pydantic>=2.0.0
|
|
7
8
|
Requires-Dist: pyyaml>=6.0.2
|
|
8
9
|
Requires-Dist: types-pyyaml>=6.0.12.20250822
|
|
10
|
+
Requires-Dist: httpx>=0.27.0 ; extra == 'client'
|
|
11
|
+
Requires-Dist: fastapi>=0.115.0 ; extra == 'server'
|
|
9
12
|
Requires-Python: >=3.12, <4
|
|
13
|
+
Provides-Extra: client
|
|
14
|
+
Provides-Extra: server
|
|
10
15
|
Description-Content-Type: text/markdown
|
|
11
16
|
|
|
12
17
|
> [!WARNING]
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "llama-deploy-core"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.0a11"
|
|
4
4
|
description = "Core models and schemas for LlamaDeploy"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
7
7
|
requires-python = ">=3.12, <4"
|
|
8
8
|
dependencies = [
|
|
9
|
+
"fastapi>=0.115.0",
|
|
9
10
|
"pydantic>=2.0.0",
|
|
10
11
|
"pyyaml>=6.0.2",
|
|
11
12
|
"types-pyyaml>=6.0.12.20250822",
|
|
12
13
|
]
|
|
13
14
|
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
server = [
|
|
17
|
+
"fastapi>=0.115.0",
|
|
18
|
+
]
|
|
19
|
+
client = [
|
|
20
|
+
"httpx>=0.27.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
14
23
|
[build-system]
|
|
15
24
|
requires = ["uv_build>=0.7.20,<0.8.0"]
|
|
16
25
|
build-backend = "uv_build"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from typing import Iterator, List
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from llama_deploy.core.schema.base import LogEvent
|
|
6
|
+
from llama_deploy.core.schema.deployments import (
|
|
7
|
+
DeploymentCreate,
|
|
8
|
+
DeploymentResponse,
|
|
9
|
+
DeploymentsListResponse,
|
|
10
|
+
DeploymentUpdate,
|
|
11
|
+
)
|
|
12
|
+
from llama_deploy.core.schema.git_validation import (
|
|
13
|
+
RepositoryValidationRequest,
|
|
14
|
+
RepositoryValidationResponse,
|
|
15
|
+
)
|
|
16
|
+
from llama_deploy.core.schema.projects import ProjectsListResponse, ProjectSummary
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClientError(Exception):
|
|
20
|
+
"""Base class for client errors."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, message: str) -> None:
|
|
23
|
+
super().__init__(message)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseClient:
|
|
27
|
+
def __init__(self, base_url: str) -> None:
|
|
28
|
+
self.base_url = base_url.rstrip("/")
|
|
29
|
+
self.client = httpx.Client(
|
|
30
|
+
base_url=self.base_url,
|
|
31
|
+
event_hooks={"response": [self._handle_response]},
|
|
32
|
+
)
|
|
33
|
+
self.hookless_client = httpx.Client(base_url=self.base_url)
|
|
34
|
+
|
|
35
|
+
def _handle_response(self, response: httpx.Response) -> None:
|
|
36
|
+
try:
|
|
37
|
+
response.raise_for_status()
|
|
38
|
+
except httpx.HTTPStatusError as e:
|
|
39
|
+
try:
|
|
40
|
+
response.read()
|
|
41
|
+
error_data = e.response.json()
|
|
42
|
+
if isinstance(error_data, dict) and "detail" in error_data:
|
|
43
|
+
error_message = error_data["detail"]
|
|
44
|
+
else:
|
|
45
|
+
error_message = str(error_data)
|
|
46
|
+
except (ValueError, KeyError):
|
|
47
|
+
error_message = e.response.text
|
|
48
|
+
raise ClientError(f"HTTP {e.response.status_code}: {error_message}") from e
|
|
49
|
+
except httpx.RequestError as e:
|
|
50
|
+
raise ClientError(f"Request failed: {e}") from e
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ControlPlaneClient(BaseClient):
|
|
54
|
+
"""Unscoped client for non-project endpoints."""
|
|
55
|
+
|
|
56
|
+
def health_check(self) -> dict:
|
|
57
|
+
response = self.client.get("/health")
|
|
58
|
+
return response.json()
|
|
59
|
+
|
|
60
|
+
def server_version(self) -> dict:
|
|
61
|
+
response = self.client.get("/version")
|
|
62
|
+
return response.json()
|
|
63
|
+
|
|
64
|
+
def list_projects(self) -> List[ProjectSummary]:
|
|
65
|
+
response = self.client.get("/api/v1beta1/deployments/list-projects")
|
|
66
|
+
projects_response = ProjectsListResponse.model_validate(response.json())
|
|
67
|
+
return [project for project in projects_response.projects]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ProjectClient(BaseClient):
|
|
71
|
+
"""Project-scoped client for deployment operations."""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
base_url: str,
|
|
76
|
+
project_id: str,
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__(base_url)
|
|
79
|
+
self.project_id = project_id
|
|
80
|
+
|
|
81
|
+
def list_deployments(self) -> List[DeploymentResponse]:
|
|
82
|
+
response = self.client.get(
|
|
83
|
+
"/api/v1beta1/deployments",
|
|
84
|
+
params={"project_id": self.project_id},
|
|
85
|
+
)
|
|
86
|
+
deployments_response = DeploymentsListResponse.model_validate(response.json())
|
|
87
|
+
return [deployment for deployment in deployments_response.deployments]
|
|
88
|
+
|
|
89
|
+
def get_deployment(
|
|
90
|
+
self, deployment_id: str, include_events: bool = False
|
|
91
|
+
) -> DeploymentResponse:
|
|
92
|
+
response = self.client.get(
|
|
93
|
+
f"/api/v1beta1/deployments/{deployment_id}",
|
|
94
|
+
params={"project_id": self.project_id, "include_events": include_events},
|
|
95
|
+
)
|
|
96
|
+
return DeploymentResponse.model_validate(response.json())
|
|
97
|
+
|
|
98
|
+
def create_deployment(
|
|
99
|
+
self, deployment_data: DeploymentCreate
|
|
100
|
+
) -> DeploymentResponse:
|
|
101
|
+
response = self.client.post(
|
|
102
|
+
"/api/v1beta1/deployments",
|
|
103
|
+
params={"project_id": self.project_id},
|
|
104
|
+
json=deployment_data.model_dump(exclude_none=True),
|
|
105
|
+
)
|
|
106
|
+
return DeploymentResponse.model_validate(response.json())
|
|
107
|
+
|
|
108
|
+
def delete_deployment(self, deployment_id: str) -> None:
|
|
109
|
+
self.client.delete(
|
|
110
|
+
f"/api/v1beta1/deployments/{deployment_id}",
|
|
111
|
+
params={"project_id": self.project_id},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def update_deployment(
|
|
115
|
+
self,
|
|
116
|
+
deployment_id: str,
|
|
117
|
+
update_data: DeploymentUpdate,
|
|
118
|
+
) -> DeploymentResponse:
|
|
119
|
+
response = self.client.patch(
|
|
120
|
+
f"/api/v1beta1/deployments/{deployment_id}",
|
|
121
|
+
params={"project_id": self.project_id},
|
|
122
|
+
json=update_data.model_dump(),
|
|
123
|
+
)
|
|
124
|
+
return DeploymentResponse.model_validate(response.json())
|
|
125
|
+
|
|
126
|
+
def validate_repository(
|
|
127
|
+
self,
|
|
128
|
+
repo_url: str,
|
|
129
|
+
deployment_id: str | None = None,
|
|
130
|
+
pat: str | None = None,
|
|
131
|
+
) -> RepositoryValidationResponse:
|
|
132
|
+
response = self.client.post(
|
|
133
|
+
"/api/v1beta1/deployments/validate-repository",
|
|
134
|
+
params={"project_id": self.project_id},
|
|
135
|
+
json=RepositoryValidationRequest(
|
|
136
|
+
repository_url=repo_url,
|
|
137
|
+
deployment_id=deployment_id,
|
|
138
|
+
pat=pat,
|
|
139
|
+
).model_dump(),
|
|
140
|
+
)
|
|
141
|
+
return RepositoryValidationResponse.model_validate(response.json())
|
|
142
|
+
|
|
143
|
+
def stream_deployment_logs(
|
|
144
|
+
self,
|
|
145
|
+
deployment_id: str,
|
|
146
|
+
*,
|
|
147
|
+
include_init_containers: bool = False,
|
|
148
|
+
since_seconds: int | None = None,
|
|
149
|
+
tail_lines: int | None = None,
|
|
150
|
+
) -> tuple["Closer", Iterator[LogEvent]]:
|
|
151
|
+
"""Stream logs as LogEvent items from the control plane using SSE.
|
|
152
|
+
|
|
153
|
+
This yields `LogEvent` models until the stream ends (e.g. rollout).
|
|
154
|
+
"""
|
|
155
|
+
# Use a separate client without response hooks so we don't consume the stream
|
|
156
|
+
|
|
157
|
+
params = {
|
|
158
|
+
"project_id": self.project_id,
|
|
159
|
+
"include_init_containers": include_init_containers,
|
|
160
|
+
}
|
|
161
|
+
if since_seconds is not None:
|
|
162
|
+
params["since_seconds"] = since_seconds
|
|
163
|
+
if tail_lines is not None:
|
|
164
|
+
params["tail_lines"] = tail_lines
|
|
165
|
+
|
|
166
|
+
url = f"/api/v1beta1/deployments/{deployment_id}/logs"
|
|
167
|
+
headers = {"Accept": "text/event-stream"}
|
|
168
|
+
|
|
169
|
+
stack = contextlib.ExitStack()
|
|
170
|
+
response = stack.enter_context(
|
|
171
|
+
self.hookless_client.stream(
|
|
172
|
+
"GET", url, params=params, headers=headers, timeout=None
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
try:
|
|
176
|
+
response.raise_for_status()
|
|
177
|
+
except Exception:
|
|
178
|
+
stack.close()
|
|
179
|
+
raise
|
|
180
|
+
|
|
181
|
+
return stack.close, _iterate_log_stream(response, stack.close)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _iterate_log_stream(
|
|
185
|
+
response: httpx.Response, closer: "Closer"
|
|
186
|
+
) -> Iterator[LogEvent]:
|
|
187
|
+
event_name: str | None = None
|
|
188
|
+
data_lines: list[str] = []
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
for line in response.iter_lines():
|
|
192
|
+
if line is None:
|
|
193
|
+
continue
|
|
194
|
+
line = line.decode() if isinstance(line, (bytes, bytearray)) else line
|
|
195
|
+
print("got line", line)
|
|
196
|
+
if line.startswith("event:"):
|
|
197
|
+
event_name = line[len("event:") :].strip()
|
|
198
|
+
elif line.startswith("data:"):
|
|
199
|
+
data_lines.append(line[len("data:") :].lstrip())
|
|
200
|
+
elif line.strip() == "":
|
|
201
|
+
if event_name == "log" and data_lines:
|
|
202
|
+
data_str = "\n".join(data_lines)
|
|
203
|
+
try:
|
|
204
|
+
yield LogEvent.model_validate_json(data_str)
|
|
205
|
+
print("yielded log event", data_str)
|
|
206
|
+
except Exception:
|
|
207
|
+
# If parsing fails, skip malformed event
|
|
208
|
+
pass
|
|
209
|
+
# reset for next event
|
|
210
|
+
event_name = None
|
|
211
|
+
data_lines = []
|
|
212
|
+
finally:
|
|
213
|
+
try:
|
|
214
|
+
closer()
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
type Closer = callable[tuple[()], None]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from ._abstract_deployments_service import AbstractDeploymentsService
|
|
2
|
+
from ._create_deployments_router import create_v1beta1_deployments_router
|
|
3
|
+
from ._exceptions import DeploymentNotFoundError, ReplicaSetNotFoundError
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"AbstractDeploymentsService",
|
|
7
|
+
"create_v1beta1_deployments_router",
|
|
8
|
+
"DeploymentNotFoundError",
|
|
9
|
+
"ReplicaSetNotFoundError",
|
|
10
|
+
]
|
llama_deploy_core-0.3.0a11/src/llama_deploy/core/server/manage_api/_abstract_deployments_service.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import AsyncGenerator, cast
|
|
3
|
+
|
|
4
|
+
from llama_deploy.core import schema
|
|
5
|
+
from llama_deploy.core.schema.base import LogEvent
|
|
6
|
+
from llama_deploy.core.schema.deployments import DeploymentResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AbstractDeploymentsService(ABC):
|
|
10
|
+
@abstractmethod
|
|
11
|
+
async def validate_repository(
|
|
12
|
+
self,
|
|
13
|
+
project_id: str,
|
|
14
|
+
request: schema.RepositoryValidationRequest,
|
|
15
|
+
) -> schema.RepositoryValidationResponse:
|
|
16
|
+
"""
|
|
17
|
+
Validate repository access and return unified response.
|
|
18
|
+
"""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def create_deployment(
|
|
23
|
+
self,
|
|
24
|
+
project_id: str,
|
|
25
|
+
deployment_data: schema.DeploymentCreate,
|
|
26
|
+
) -> DeploymentResponse:
|
|
27
|
+
"""
|
|
28
|
+
Create a new deployment
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
project_id: The ID of the project to create the deployment in
|
|
32
|
+
deployment_data: The data for the deployment
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The created deployment
|
|
36
|
+
Raises:
|
|
37
|
+
DeploymentNotFoundError: If the deployment ID is not found
|
|
38
|
+
"""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
async def get_deployments(
|
|
43
|
+
self,
|
|
44
|
+
project_id: str,
|
|
45
|
+
) -> schema.DeploymentsListResponse:
|
|
46
|
+
"""
|
|
47
|
+
Get a list of deployments for a project
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
project_id: The ID of the project to get the deployments for
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A list of deployments
|
|
54
|
+
"""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
async def get_deployment(
|
|
59
|
+
self,
|
|
60
|
+
project_id: str,
|
|
61
|
+
deployment_id: str,
|
|
62
|
+
include_events: bool = False,
|
|
63
|
+
) -> schema.DeploymentResponse | None:
|
|
64
|
+
"""
|
|
65
|
+
Get a deployment by ID
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
project_id: The ID of the project to get the deployment for
|
|
69
|
+
deployment_id: The ID of the deployment to get
|
|
70
|
+
include_events: Whether to include events in the response
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The deployment
|
|
74
|
+
Raises:
|
|
75
|
+
DeploymentNotFoundError: If the deployment ID is not found
|
|
76
|
+
"""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
async def delete_deployment(
|
|
81
|
+
self,
|
|
82
|
+
project_id: str,
|
|
83
|
+
deployment_id: str,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Delete a deployment
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
project_id: The ID of the project to delete the deployment from
|
|
90
|
+
deployment_id: The ID of the deployment to delete
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
None
|
|
94
|
+
Raises:
|
|
95
|
+
DeploymentNotFoundError: If the deployment ID is not found
|
|
96
|
+
"""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
async def update_deployment(
|
|
101
|
+
self,
|
|
102
|
+
project_id: str,
|
|
103
|
+
deployment_id: str,
|
|
104
|
+
update_data: schema.DeploymentUpdate,
|
|
105
|
+
) -> DeploymentResponse:
|
|
106
|
+
"""
|
|
107
|
+
Update a deployment
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
project_id: The ID of the project to update the deployment in
|
|
111
|
+
deployment_id: The ID of the deployment to update
|
|
112
|
+
update_data: The data to update the deployment with
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
The updated deployment
|
|
116
|
+
Raises:
|
|
117
|
+
DeploymentNotFoundError: If the deployment ID is not found
|
|
118
|
+
"""
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
async def stream_deployment_logs(
|
|
123
|
+
self,
|
|
124
|
+
project_id: str,
|
|
125
|
+
deployment_id: str,
|
|
126
|
+
include_init_containers: bool = False,
|
|
127
|
+
since_seconds: int | None = None,
|
|
128
|
+
tail_lines: int | None = None,
|
|
129
|
+
) -> AsyncGenerator[LogEvent, None]:
|
|
130
|
+
"""
|
|
131
|
+
Stream the logs for a deployment
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
project_id: The ID of the project to stream the logs for
|
|
135
|
+
deployment_id: The ID of the deployment to stream the logs for
|
|
136
|
+
include_init_containers: Whether to include init containers in the logs
|
|
137
|
+
since_seconds: The number of seconds to stream the logs for
|
|
138
|
+
tail_lines: The number of lines to stream the logs for
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A generator of log events
|
|
142
|
+
Raises:
|
|
143
|
+
DeploymentNotFoundError: If the deployment ID is not found
|
|
144
|
+
"""
|
|
145
|
+
# This method is abstract. The following unreachable code ensures type
|
|
146
|
+
# checkers treat it as an async generator, so call sites can `async for`.
|
|
147
|
+
raise NotImplementedError
|
|
148
|
+
if False:
|
|
149
|
+
yield cast(LogEvent, None)
|
llama_deploy_core-0.3.0a11/src/llama_deploy/core/server/manage_api/_create_deployments_router.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Awaitable, Callable
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
|
5
|
+
from fastapi.params import Query
|
|
6
|
+
from fastapi.responses import StreamingResponse
|
|
7
|
+
from llama_deploy.control_plane import k8s_client
|
|
8
|
+
from llama_deploy.core import schema
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
|
|
11
|
+
from ._abstract_deployments_service import AbstractDeploymentsService
|
|
12
|
+
from ._exceptions import DeploymentNotFoundError, ReplicaSetNotFoundError
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def get_project_id(project_id: Annotated[str, Query()]) -> str:
|
|
18
|
+
return project_id
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_v1beta1_deployments_router(
|
|
22
|
+
deployments_service: AbstractDeploymentsService,
|
|
23
|
+
get_project_id: Callable[[str], Awaitable[str]] = get_project_id,
|
|
24
|
+
) -> APIRouter:
|
|
25
|
+
router = APIRouter(prefix="/api/v1beta1/deployments", tags=["v1beta1-deployments"])
|
|
26
|
+
|
|
27
|
+
@router.get("/list-projects")
|
|
28
|
+
async def get_projects() -> schema.ProjectsListResponse:
|
|
29
|
+
"""Get all unique projects with their deployment counts"""
|
|
30
|
+
projects_data = await k8s_client.get_projects_with_deployment_count()
|
|
31
|
+
return schema.ProjectsListResponse(projects=projects_data)
|
|
32
|
+
|
|
33
|
+
@router.post("/validate-repository")
|
|
34
|
+
async def validate_repository(
|
|
35
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
36
|
+
request: schema.RepositoryValidationRequest,
|
|
37
|
+
) -> schema.RepositoryValidationResponse:
|
|
38
|
+
"""Validate repository access and return unified response."""
|
|
39
|
+
return await deployments_service.validate_repository(
|
|
40
|
+
project_id=project_id,
|
|
41
|
+
request=request,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@router.post("", response_model=schema.DeploymentResponse)
|
|
45
|
+
async def create_deployment(
|
|
46
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
47
|
+
deployment_data: schema.DeploymentCreate,
|
|
48
|
+
) -> Response:
|
|
49
|
+
deployment_response = await deployments_service.create_deployment(
|
|
50
|
+
project_id=project_id,
|
|
51
|
+
deployment_data=deployment_data,
|
|
52
|
+
)
|
|
53
|
+
# Return deployment response with warning header if there are git issues
|
|
54
|
+
|
|
55
|
+
response = Response(
|
|
56
|
+
content=deployment_response.model_dump_json(),
|
|
57
|
+
status_code=201,
|
|
58
|
+
media_type="application/json",
|
|
59
|
+
)
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
@router.get("")
|
|
63
|
+
async def get_deployments(
|
|
64
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
65
|
+
) -> schema.DeploymentsListResponse:
|
|
66
|
+
return await deployments_service.get_deployments(project_id=project_id)
|
|
67
|
+
|
|
68
|
+
@router.get("/{deployment_id}")
|
|
69
|
+
async def get_deployment(
|
|
70
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
71
|
+
deployment_id: str,
|
|
72
|
+
include_events: Annotated[bool, Query()] = False,
|
|
73
|
+
) -> schema.DeploymentResponse:
|
|
74
|
+
deployment = await deployments_service.get_deployment(
|
|
75
|
+
project_id=project_id,
|
|
76
|
+
deployment_id=deployment_id,
|
|
77
|
+
include_events=include_events,
|
|
78
|
+
)
|
|
79
|
+
if deployment is None:
|
|
80
|
+
raise HTTPException(
|
|
81
|
+
status_code=404,
|
|
82
|
+
detail=f"Deployment with id {deployment_id} not found",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return deployment
|
|
86
|
+
|
|
87
|
+
@router.delete("/{deployment_id}")
|
|
88
|
+
async def delete_deployment(
|
|
89
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
90
|
+
deployment_id: str,
|
|
91
|
+
) -> None:
|
|
92
|
+
await deployments_service.delete_deployment(
|
|
93
|
+
project_id=project_id, deployment_id=deployment_id
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@router.patch("/{deployment_id}", response_model=schema.DeploymentResponse)
|
|
97
|
+
async def update_deployment(
|
|
98
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
99
|
+
deployment_id: str,
|
|
100
|
+
update_data: schema.DeploymentUpdate,
|
|
101
|
+
) -> Response:
|
|
102
|
+
"""Update an existing deployment with patch-style changes
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
project_id: The project ID
|
|
106
|
+
deployment_id: The deployment ID to update
|
|
107
|
+
update_data: The patch-style update data
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
deployment_response = await deployments_service.update_deployment(
|
|
111
|
+
project_id=project_id,
|
|
112
|
+
deployment_id=deployment_id,
|
|
113
|
+
update_data=update_data,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
response = Response(
|
|
117
|
+
content=deployment_response.model_dump_json(),
|
|
118
|
+
status_code=200,
|
|
119
|
+
media_type="application/json",
|
|
120
|
+
)
|
|
121
|
+
return response
|
|
122
|
+
|
|
123
|
+
@router.get("/{deployment_id}/logs")
|
|
124
|
+
async def stream_deployment_logs(
|
|
125
|
+
request: Request,
|
|
126
|
+
project_id: Annotated[str, Depends(get_project_id)],
|
|
127
|
+
deployment_id: str,
|
|
128
|
+
include_init_containers: Annotated[bool, Query()] = False,
|
|
129
|
+
since_seconds: Annotated[int | None, Query()] = None,
|
|
130
|
+
tail_lines: Annotated[int | None, Query()] = None,
|
|
131
|
+
):
|
|
132
|
+
"""Stream logs for the latest ReplicaSet of a deployment.
|
|
133
|
+
|
|
134
|
+
The stream ends when the latest ReplicaSet changes (e.g., a new rollout occurs).
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
inner = deployments_service.stream_deployment_logs(
|
|
139
|
+
project_id=project_id,
|
|
140
|
+
deployment_id=deployment_id,
|
|
141
|
+
include_init_containers=include_init_containers,
|
|
142
|
+
since_seconds=since_seconds,
|
|
143
|
+
tail_lines=tail_lines,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
async def sse_lines():
|
|
147
|
+
async for data in inner:
|
|
148
|
+
yield "event: log\n"
|
|
149
|
+
yield f"data: {data.model_dump_json()}\n\n"
|
|
150
|
+
|
|
151
|
+
return StreamingResponse(
|
|
152
|
+
sse_lines(),
|
|
153
|
+
media_type="text/event-stream",
|
|
154
|
+
headers={
|
|
155
|
+
"Cache-Control": "no-cache",
|
|
156
|
+
"Connection": "keep-alive",
|
|
157
|
+
"X-Accel-Buffering": "no",
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except DeploymentNotFoundError as e:
|
|
162
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
163
|
+
except ReplicaSetNotFoundError as e:
|
|
164
|
+
# Deployment exists but hasn't created a ReplicaSet yet
|
|
165
|
+
raise HTTPException(status_code=409, detail=str(e))
|
|
166
|
+
|
|
167
|
+
return router
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/deployment_config.py
RENAMED
|
File without changes
|
{llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/git/git_util.py
RENAMED
|
File without changes
|
{llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/path_util.py
RENAMED
|
File without changes
|
|
File without changes
|
{llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/__init__.py
RENAMED
|
File without changes
|
{llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llama_deploy_core-0.3.0a10 → llama_deploy_core-0.3.0a11}/src/llama_deploy/core/schema/projects.py
RENAMED
|
File without changes
|
|
File without changes
|