llama-deploy-core 0.2.7a1__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,165 @@
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 import LogEvent
6
+ from llama_deploy.core.schema.deployments import DeploymentResponse
7
+
8
+
9
+ class AbstractPublicDeploymentsService(ABC):
10
+ @abstractmethod
11
+ async def get_version(self) -> schema.VersionResponse:
12
+ """
13
+ Get the version of the server
14
+ """
15
+ ...
16
+
17
+
18
+ class AbstractDeploymentsService(ABC):
19
+ @abstractmethod
20
+ async def get_projects(self) -> schema.ProjectsListResponse:
21
+ """
22
+ Get a list of projects
23
+ """
24
+ ...
25
+
26
+ @abstractmethod
27
+ async def validate_repository(
28
+ self,
29
+ project_id: str,
30
+ request: schema.RepositoryValidationRequest,
31
+ ) -> schema.RepositoryValidationResponse:
32
+ """
33
+ Validate repository access and return unified response.
34
+ """
35
+ ...
36
+
37
+ @abstractmethod
38
+ async def create_deployment(
39
+ self,
40
+ project_id: str,
41
+ deployment_data: schema.DeploymentCreate,
42
+ ) -> DeploymentResponse:
43
+ """
44
+ Create a new deployment
45
+
46
+ Args:
47
+ project_id: The ID of the project to create the deployment in
48
+ deployment_data: The data for the deployment
49
+
50
+ Returns:
51
+ The created deployment
52
+ Raises:
53
+ DeploymentNotFoundError: If the deployment ID is not found
54
+ """
55
+ ...
56
+
57
+ @abstractmethod
58
+ async def get_deployments(
59
+ self,
60
+ project_id: str,
61
+ ) -> schema.DeploymentsListResponse:
62
+ """
63
+ Get a list of deployments for a project
64
+
65
+ Args:
66
+ project_id: The ID of the project to get the deployments for
67
+
68
+ Returns:
69
+ A list of deployments
70
+ """
71
+ ...
72
+
73
+ @abstractmethod
74
+ async def get_deployment(
75
+ self,
76
+ project_id: str,
77
+ deployment_id: str,
78
+ include_events: bool = False,
79
+ ) -> schema.DeploymentResponse | None:
80
+ """
81
+ Get a deployment by ID
82
+
83
+ Args:
84
+ project_id: The ID of the project to get the deployment for
85
+ deployment_id: The ID of the deployment to get
86
+ include_events: Whether to include events in the response
87
+
88
+ Returns:
89
+ The deployment
90
+ Raises:
91
+ DeploymentNotFoundError: If the deployment ID is not found
92
+ """
93
+ ...
94
+
95
+ @abstractmethod
96
+ async def delete_deployment(
97
+ self,
98
+ project_id: str,
99
+ deployment_id: str,
100
+ ) -> None:
101
+ """
102
+ Delete a deployment
103
+
104
+ Args:
105
+ project_id: The ID of the project to delete the deployment from
106
+ deployment_id: The ID of the deployment to delete
107
+
108
+ Returns:
109
+ None
110
+ Raises:
111
+ DeploymentNotFoundError: If the deployment ID is not found
112
+ """
113
+ ...
114
+
115
+ @abstractmethod
116
+ async def update_deployment(
117
+ self,
118
+ project_id: str,
119
+ deployment_id: str,
120
+ update_data: schema.DeploymentUpdate,
121
+ ) -> DeploymentResponse:
122
+ """
123
+ Update a deployment
124
+
125
+ Args:
126
+ project_id: The ID of the project to update the deployment in
127
+ deployment_id: The ID of the deployment to update
128
+ update_data: The data to update the deployment with
129
+
130
+ Returns:
131
+ The updated deployment
132
+ Raises:
133
+ DeploymentNotFoundError: If the deployment ID is not found
134
+ """
135
+ ...
136
+
137
+ @abstractmethod
138
+ async def stream_deployment_logs(
139
+ self,
140
+ project_id: str,
141
+ deployment_id: str,
142
+ include_init_containers: bool = False,
143
+ since_seconds: int | None = None,
144
+ tail_lines: int | None = None,
145
+ ) -> AsyncGenerator[LogEvent, None]:
146
+ """
147
+ Stream the logs for a deployment
148
+
149
+ Args:
150
+ project_id: The ID of the project to stream the logs for
151
+ deployment_id: The ID of the deployment to stream the logs for
152
+ include_init_containers: Whether to include init containers in the logs
153
+ since_seconds: The number of seconds to stream the logs for
154
+ tail_lines: The number of lines to stream the logs for
155
+
156
+ Returns:
157
+ A generator of log events
158
+ Raises:
159
+ DeploymentNotFoundError: If the deployment ID is not found
160
+ """
161
+ # This method is abstract. The following unreachable code ensures type
162
+ # checkers treat it as an async generator, so call sites can `async for`.
163
+ raise NotImplementedError
164
+ if False:
165
+ yield cast(LogEvent, None)
@@ -0,0 +1,188 @@
1
+ import logging
2
+ from typing import Awaitable, Callable
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException, Request, Response, params
5
+ from fastapi.params import Query
6
+ from fastapi.responses import StreamingResponse
7
+ from llama_deploy.core import schema
8
+ from typing_extensions import Annotated
9
+
10
+ from ._abstract_deployments_service import (
11
+ AbstractDeploymentsService,
12
+ AbstractPublicDeploymentsService,
13
+ )
14
+ from ._exceptions import DeploymentNotFoundError, ReplicaSetNotFoundError
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ async def get_project_id(project_id: Annotated[str, Query()]) -> str:
20
+ return project_id
21
+
22
+
23
+ def create_v1beta1_deployments_router(
24
+ deployments_service: AbstractDeploymentsService,
25
+ public_service: AbstractPublicDeploymentsService,
26
+ get_project_id: Callable[..., Awaitable[str]] = get_project_id,
27
+ dependencies: list[params.Depends] | None = None,
28
+ public_dependencies: list[params.Depends] | None = None,
29
+ include_in_schema: bool = True,
30
+ ) -> APIRouter:
31
+ base_router = APIRouter(prefix="/api/v1beta1", include_in_schema=include_in_schema)
32
+ public_router = APIRouter(
33
+ tags=["v1beta1-deployments-public"],
34
+ dependencies=public_dependencies,
35
+ include_in_schema=include_in_schema,
36
+ )
37
+ router = APIRouter(
38
+ tags=["v1beta1-deployments"],
39
+ dependencies=dependencies,
40
+ include_in_schema=include_in_schema,
41
+ )
42
+
43
+ @public_router.get("/version")
44
+ async def get_version() -> schema.VersionResponse:
45
+ return await public_service.get_version()
46
+
47
+ @router.get("/list-projects")
48
+ async def get_projects() -> schema.ProjectsListResponse:
49
+ """Get all unique projects with their deployment counts"""
50
+ return await deployments_service.get_projects()
51
+
52
+ @router.post("/validate-repository")
53
+ async def validate_repository(
54
+ project_id: Annotated[str, Depends(get_project_id)],
55
+ request: schema.RepositoryValidationRequest,
56
+ ) -> schema.RepositoryValidationResponse:
57
+ """Validate repository access and return unified response."""
58
+ return await deployments_service.validate_repository(
59
+ project_id=project_id,
60
+ request=request,
61
+ )
62
+
63
+ @router.post("", response_model=schema.DeploymentResponse)
64
+ async def create_deployment(
65
+ project_id: Annotated[str, Depends(get_project_id)],
66
+ deployment_data: schema.DeploymentCreate,
67
+ ) -> Response:
68
+ deployment_response = await deployments_service.create_deployment(
69
+ project_id=project_id,
70
+ deployment_data=deployment_data,
71
+ )
72
+ # Return deployment response with warning header if there are git issues
73
+
74
+ response = Response(
75
+ content=deployment_response.model_dump_json(),
76
+ status_code=201,
77
+ media_type="application/json",
78
+ )
79
+ return response
80
+
81
+ @router.get("")
82
+ async def get_deployments(
83
+ project_id: Annotated[str, Depends(get_project_id)],
84
+ ) -> schema.DeploymentsListResponse:
85
+ return await deployments_service.get_deployments(project_id=project_id)
86
+
87
+ @router.get("/{deployment_id}")
88
+ async def get_deployment(
89
+ project_id: Annotated[str, Depends(get_project_id)],
90
+ deployment_id: str,
91
+ include_events: Annotated[bool, Query()] = False,
92
+ ) -> schema.DeploymentResponse:
93
+ deployment = await deployments_service.get_deployment(
94
+ project_id=project_id,
95
+ deployment_id=deployment_id,
96
+ include_events=include_events,
97
+ )
98
+ if deployment is None:
99
+ raise HTTPException(
100
+ status_code=404,
101
+ detail=f"Deployment with id {deployment_id} not found",
102
+ )
103
+
104
+ return deployment
105
+
106
+ @router.delete("/{deployment_id}")
107
+ async def delete_deployment(
108
+ project_id: Annotated[str, Depends(get_project_id)],
109
+ deployment_id: str,
110
+ ) -> None:
111
+ await deployments_service.delete_deployment(
112
+ project_id=project_id, deployment_id=deployment_id
113
+ )
114
+
115
+ @router.patch("/{deployment_id}", response_model=schema.DeploymentResponse)
116
+ async def update_deployment(
117
+ project_id: Annotated[str, Depends(get_project_id)],
118
+ deployment_id: str,
119
+ update_data: schema.DeploymentUpdate,
120
+ ) -> Response:
121
+ """Update an existing deployment with patch-style changes
122
+
123
+ Args:
124
+ project_id: The project ID
125
+ deployment_id: The deployment ID to update
126
+ update_data: The patch-style update data
127
+ """
128
+
129
+ deployment_response = await deployments_service.update_deployment(
130
+ project_id=project_id,
131
+ deployment_id=deployment_id,
132
+ update_data=update_data,
133
+ )
134
+
135
+ response = Response(
136
+ content=deployment_response.model_dump_json(),
137
+ status_code=200,
138
+ media_type="application/json",
139
+ )
140
+ return response
141
+
142
+ @router.get("/{deployment_id}/logs")
143
+ async def stream_deployment_logs(
144
+ request: Request,
145
+ project_id: Annotated[str, Depends(get_project_id)],
146
+ deployment_id: str,
147
+ include_init_containers: Annotated[bool, Query()] = False,
148
+ since_seconds: Annotated[int | None, Query()] = None,
149
+ tail_lines: Annotated[int | None, Query()] = None,
150
+ ):
151
+ """Stream logs for the latest ReplicaSet of a deployment.
152
+
153
+ The stream ends when the latest ReplicaSet changes (e.g., a new rollout occurs).
154
+ """
155
+
156
+ try:
157
+ inner = deployments_service.stream_deployment_logs(
158
+ project_id=project_id,
159
+ deployment_id=deployment_id,
160
+ include_init_containers=include_init_containers,
161
+ since_seconds=since_seconds,
162
+ tail_lines=tail_lines,
163
+ )
164
+
165
+ async def sse_lines():
166
+ async for data in inner:
167
+ yield "event: log\n"
168
+ yield f"data: {data.model_dump_json()}\n\n"
169
+
170
+ return StreamingResponse(
171
+ sse_lines(),
172
+ media_type="text/event-stream",
173
+ headers={
174
+ "Cache-Control": "no-cache",
175
+ "Connection": "keep-alive",
176
+ "X-Accel-Buffering": "no",
177
+ },
178
+ )
179
+
180
+ except DeploymentNotFoundError as e:
181
+ raise HTTPException(status_code=404, detail=str(e))
182
+ except ReplicaSetNotFoundError as e:
183
+ # Deployment exists but hasn't created a ReplicaSet yet
184
+ raise HTTPException(status_code=409, detail=str(e))
185
+
186
+ base_router.include_router(public_router, prefix="/deployments-public")
187
+ base_router.include_router(router, prefix="/deployments")
188
+ return base_router
@@ -0,0 +1,14 @@
1
+ class DeploymentNotFoundError(Exception):
2
+ """
3
+ Raised when a deployment is not found
4
+ """
5
+
6
+ pass
7
+
8
+
9
+ class ReplicaSetNotFoundError(Exception):
10
+ """
11
+ May be raised if a deployment does not set have a replica set
12
+ """
13
+
14
+ pass
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from .deployment_config import DeploymentConfig
7
+
8
+
9
+ def resolve_ui_root(config_parent: Path, config: DeploymentConfig) -> Path | None:
10
+ """Return the absolute path to the UI root if UI is configured; otherwise None."""
11
+ if config.ui is None:
12
+ return None
13
+ return (config_parent / config.ui.directory).resolve()
14
+
15
+
16
+ def ui_build_output_path(config_parent: Path, config: DeploymentConfig) -> Path | None:
17
+ """
18
+ Determine if the UI has a build script defined in package.json, and where the output will be.
19
+ Right now, assumes its just `/dist` in the UI root.
20
+
21
+ Returns:
22
+ - Path to the build output directory if a package.json exists and contains a "build" script
23
+ - None if there is no UI configured or no package.json exists
24
+ """
25
+ ui_root = resolve_ui_root(config_parent, config)
26
+ if ui_root is None:
27
+ return None
28
+ if config.ui is None:
29
+ return None
30
+ package_json = ui_root / "package.json"
31
+ if not package_json.exists():
32
+ return None
33
+ try:
34
+ with open(package_json, "r", encoding="utf-8") as f:
35
+ pkg = json.load(f)
36
+ if not isinstance(pkg, dict):
37
+ return None
38
+ scripts = pkg.get("scripts", {}) or {}
39
+ if config.ui.build_command in scripts:
40
+ return config.build_output_path()
41
+ return None
42
+ except Exception:
43
+ # Do not raise for malformed package.json in validation contexts
44
+ return None
@@ -1,11 +1,18 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llama-deploy-core
3
- Version: 0.2.7a1
3
+ Version: 0.3.0
4
4
  Summary: Core models and schemas for LlamaDeploy
5
5
  License: MIT
6
+ Requires-Dist: fastapi>=0.115.0
7
+ Requires-Dist: overrides>=7.7.0
6
8
  Requires-Dist: pydantic>=2.0.0
7
9
  Requires-Dist: pyyaml>=6.0.2
8
- Requires-Python: >=3.12, <4
10
+ Requires-Dist: types-pyyaml>=6.0.12.20250822
11
+ Requires-Dist: httpx>=0.24.0,<1.0.0 ; extra == 'client'
12
+ Requires-Dist: fastapi>=0.115.0 ; extra == 'server'
13
+ Requires-Python: >=3.11, <4
14
+ Provides-Extra: client
15
+ Provides-Extra: server
9
16
  Description-Content-Type: text/markdown
10
17
 
11
18
  > [!WARNING]
@@ -0,0 +1,22 @@
1
+ llama_deploy/core/__init__.py,sha256=112612bf2e928c2e0310d6556bb13fc28c00db70297b90a8527486cd2562e408,43
2
+ llama_deploy/core/client/manage_client.py,sha256=98715e323336fc9bc2730ecf585d6f9b862690fd6712998434cf9444d7bd47bc,7974
3
+ llama_deploy/core/config.py,sha256=69bb0ea8ac169eaa4e808cd60a098b616bddd3145d26c6c35e56db38496b0e6a,35
4
+ llama_deploy/core/deployment_config.py,sha256=bde431070758421f578f2e27f006152147e8cd752ee1054f1bf7c37ca95b0b38,15853
5
+ llama_deploy/core/git/git_util.py,sha256=e62a5479c619a5973de203ebcc56b9729b5060c48fcb9cfc2e442756716c2abf,10960
6
+ llama_deploy/core/iter_utils.py,sha256=68ac9dbf09f58315ffcfec6bd01ed50b88a954b00458da9bc28a0535b79ba29e,7042
7
+ llama_deploy/core/path_util.py,sha256=14d50c0c337c8450ed46cafc88436027056b365a48370a69cdb76c88d7c26fd1,798
8
+ llama_deploy/core/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
9
+ llama_deploy/core/schema/__init__.py,sha256=d1459ee50c690779a682130eff72f61dc1a687d2d4b26d8a5d3620a72d92d831,802
10
+ llama_deploy/core/schema/base.py,sha256=2de6d23e58c36b6bb311ec0aea4b902661867056c1250c6b7ce3bad17141fe15,677
11
+ llama_deploy/core/schema/deployments.py,sha256=d9254d9a478d7aeaf3d28ec6205215ea892dcfb245966d726d69d0418e03b03d,6486
12
+ llama_deploy/core/schema/git_validation.py,sha256=27b306aa6ecabe58cab6381d92551545f263fe7550c58b3087115410bc71fd21,1915
13
+ llama_deploy/core/schema/projects.py,sha256=726f91e90ff8699c90861d9740819c44c3f00d945ab09df71bd6d35fdc218a45,726
14
+ llama_deploy/core/schema/public.py,sha256=022129c8fc09192f5e503b0500ccf54d106f5712b9cf8ce84b3b1c37e186f930,147
15
+ llama_deploy/core/server/manage_api/__init__.py,sha256=e477ccab59cfd084edbad46f209972a282e623eb314d0847a754a46a16361db5,457
16
+ llama_deploy/core/server/manage_api/_abstract_deployments_service.py,sha256=85ceab2a343c3642db7f77d4a665d5710a14bca920bbfdc25c5f1168cce30b22,4638
17
+ llama_deploy/core/server/manage_api/_create_deployments_router.py,sha256=9bc8468169445e1cc7f2a479e1c7da42b4bdd7482fa3b440e03ee49cd09a75df,6801
18
+ llama_deploy/core/server/manage_api/_exceptions.py,sha256=ee71cd9c2354a665e6905cd9cc752d2d65f71f0b936d33fec3c1c5229c38accf,246
19
+ llama_deploy/core/ui_build.py,sha256=290dafa951918e5593b9035570fa4c66791d7e5ea785bd372ad11e99e8283857,1514
20
+ llama_deploy_core-0.3.0.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
21
+ llama_deploy_core-0.3.0.dist-info/METADATA,sha256=6030d472c3108b0db6d84f2464f71facf329aad2ab32b3f4989280c4b2345623,663
22
+ llama_deploy_core-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.7.21
2
+ Generator: uv 0.7.20
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,11 +0,0 @@
1
- llama_deploy/core/__init__.py,sha256=112612bf2e928c2e0310d6556bb13fc28c00db70297b90a8527486cd2562e408,43
2
- llama_deploy/core/config.py,sha256=41c5a1baa25a67f0bb58b701ef8f0b8b3281714d7116580b8f87674eb9e396a3,51
3
- llama_deploy/core/git/git_util.py,sha256=da62903621e80e3a9ea80009d6b711533dfa2403bb2fe3b8c4ddf3cde9a019e3,8084
4
- llama_deploy/core/schema/__init__.py,sha256=c6821c749182cab73e5b21cce9e0f80d1e0f403f4abc3d050b84d613f640be60,713
5
- llama_deploy/core/schema/base.py,sha256=2de6d23e58c36b6bb311ec0aea4b902661867056c1250c6b7ce3bad17141fe15,677
6
- llama_deploy/core/schema/deployments.py,sha256=a269bcdc98b0a5ce09838dbc6dd68541720a0edb956ec8df5c123c3e1b253e80,5670
7
- llama_deploy/core/schema/git_validation.py,sha256=5faee3f5fc071f10813e7d3755f43fa11aae76a87467583ac7392bf1e8844f1c,1711
8
- llama_deploy/core/schema/projects.py,sha256=c97eda38207d80354c2ee3a237cba9c3f6838148197cfa2d97b9a18d3da1a38b,294
9
- llama_deploy_core-0.2.7a1.dist-info/WHEEL,sha256=cc8ae5806c5874a696cde0fcf78fdf73db4982e7c824f3ceab35e2b65182fa1a,79
10
- llama_deploy_core-0.2.7a1.dist-info/METADATA,sha256=bdcc94edd19ccb608b8526a454dd03cf70e623709b83fe9c5b525ddcbd8489bc,402
11
- llama_deploy_core-0.2.7a1.dist-info/RECORD,,