llama-deploy-appserver 0.2.7a1__py3-none-any.whl → 0.3.0a1__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.
Files changed (29) hide show
  1. llama_deploy/appserver/__main__.py +0 -4
  2. llama_deploy/appserver/app.py +105 -25
  3. llama_deploy/appserver/bootstrap.py +76 -24
  4. llama_deploy/appserver/deployment.py +7 -421
  5. llama_deploy/appserver/deployment_config_parser.py +35 -59
  6. llama_deploy/appserver/routers/__init__.py +4 -3
  7. llama_deploy/appserver/routers/deployments.py +162 -385
  8. llama_deploy/appserver/routers/status.py +4 -31
  9. llama_deploy/appserver/routers/ui_proxy.py +213 -0
  10. llama_deploy/appserver/settings.py +57 -55
  11. llama_deploy/appserver/types.py +0 -3
  12. llama_deploy/appserver/workflow_loader.py +383 -0
  13. {llama_deploy_appserver-0.2.7a1.dist-info → llama_deploy_appserver-0.3.0a1.dist-info}/METADATA +3 -6
  14. llama_deploy_appserver-0.3.0a1.dist-info/RECORD +17 -0
  15. {llama_deploy_appserver-0.2.7a1.dist-info → llama_deploy_appserver-0.3.0a1.dist-info}/WHEEL +1 -1
  16. llama_deploy/appserver/client/__init__.py +0 -3
  17. llama_deploy/appserver/client/base.py +0 -30
  18. llama_deploy/appserver/client/client.py +0 -49
  19. llama_deploy/appserver/client/models/__init__.py +0 -4
  20. llama_deploy/appserver/client/models/apiserver.py +0 -356
  21. llama_deploy/appserver/client/models/model.py +0 -82
  22. llama_deploy/appserver/run_autodeploy.py +0 -141
  23. llama_deploy/appserver/server.py +0 -60
  24. llama_deploy/appserver/source_managers/__init__.py +0 -5
  25. llama_deploy/appserver/source_managers/base.py +0 -33
  26. llama_deploy/appserver/source_managers/git.py +0 -48
  27. llama_deploy/appserver/source_managers/local.py +0 -51
  28. llama_deploy/appserver/tracing.py +0 -237
  29. llama_deploy_appserver-0.2.7a1.dist-info/RECORD +0 -28
@@ -1,356 +0,0 @@
1
- """Client functionalities to operate on the API Server.
2
-
3
- This module allows the client to use all the functionalities
4
- from the LlamaDeploy API Server. For this to work, the API
5
- Server must be up and its URL (by default `http://localhost:4501`)
6
- reachable by the host executing the client code.
7
- """
8
-
9
- import asyncio
10
- import json
11
- from typing import Any, AsyncGenerator, TextIO
12
-
13
- import httpx
14
- from llama_deploy.appserver.types import (
15
- EventDefinition,
16
- SessionDefinition,
17
- Status,
18
- StatusEnum,
19
- TaskDefinition,
20
- TaskResult,
21
- )
22
- from pydantic import Field
23
- from workflows.context import JsonSerializer
24
- from workflows.events import Event
25
-
26
- from .model import Collection, Model
27
-
28
-
29
- class SessionCollection(Collection):
30
- """A model representing a collection of session for a given deployment."""
31
-
32
- deployment_id: str = Field(
33
- description="The ID of the deployment containing the sessions."
34
- )
35
-
36
- async def delete(self, session_id: str) -> None:
37
- """Deletes the session with the provided `session_id`.
38
-
39
- Args:
40
- session_id: The id of the session that will be removed
41
-
42
- Raises:
43
- HTTPException: If the session couldn't be found with the id provided.
44
- """
45
- delete_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions/delete"
46
-
47
- await self.client.request(
48
- "POST",
49
- delete_url,
50
- params={"session_id": session_id},
51
- verify=not self.client.disable_ssl,
52
- timeout=self.client.timeout,
53
- )
54
-
55
- async def create(self) -> SessionDefinition:
56
- """Create a new session."""
57
- create_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions/create"
58
-
59
- r = await self.client.request(
60
- "POST",
61
- create_url,
62
- verify=not self.client.disable_ssl,
63
- timeout=self.client.timeout,
64
- )
65
-
66
- return SessionDefinition(**r.json())
67
-
68
- async def list(self) -> list[SessionDefinition]:
69
- """Returns a collection of all the sessions in the given deployment."""
70
- sessions_url = (
71
- f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions"
72
- )
73
- r = await self.client.request(
74
- "GET",
75
- sessions_url,
76
- verify=not self.client.disable_ssl,
77
- timeout=self.client.timeout,
78
- )
79
-
80
- return r.json()
81
-
82
- async def get(self, id: str) -> SessionDefinition:
83
- """Gets a deployment by id."""
84
- get_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions/{id}"
85
- await self.client.request(
86
- "GET",
87
- get_url,
88
- verify=not self.client.disable_ssl,
89
- timeout=self.client.timeout,
90
- )
91
- model_class = self._prepare(SessionDefinition)
92
- return model_class(client=self.client, id=id)
93
-
94
-
95
- class Task(Model):
96
- """A model representing a task belonging to a given session in the given deployment."""
97
-
98
- deployment_id: str = Field(
99
- description="The ID of the deployment this task belongs to."
100
- )
101
- session_id: str = Field(description="The ID of the session this task belongs to.")
102
-
103
- async def results(self) -> TaskResult | None:
104
- """Returns the result of a given task."""
105
- results_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/{self.id}/results"
106
-
107
- r = await self.client.request(
108
- "GET",
109
- results_url,
110
- verify=not self.client.disable_ssl,
111
- params={"session_id": self.session_id},
112
- timeout=self.client.timeout,
113
- )
114
- if r.json():
115
- return TaskResult.model_validate(r.json())
116
- return None
117
-
118
- async def send_event(self, ev: Event, service_name: str) -> EventDefinition:
119
- """Sends a human response event."""
120
- url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/{self.id}/events"
121
-
122
- serializer = JsonSerializer()
123
- event_def = EventDefinition(
124
- event_obj_str=serializer.serialize(ev), service_id=service_name
125
- )
126
-
127
- r = await self.client.request(
128
- "POST",
129
- url,
130
- verify=not self.client.disable_ssl,
131
- params={"session_id": self.session_id},
132
- json=event_def.model_dump(),
133
- timeout=self.client.timeout,
134
- )
135
- return EventDefinition.model_validate(r.json())
136
-
137
- async def events(self) -> AsyncGenerator[dict[str, Any], None]: # pragma: no cover
138
- """Returns a generator object to consume the events streamed from a service."""
139
- events_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/{self.id}/events"
140
-
141
- while True:
142
- try:
143
- async with httpx.AsyncClient(
144
- verify=not self.client.disable_ssl
145
- ) as client:
146
- async with client.stream(
147
- "GET", events_url, params={"session_id": self.session_id}
148
- ) as response:
149
- response.raise_for_status()
150
- async for line in response.aiter_lines():
151
- json_line = json.loads(line)
152
- yield json_line
153
- break # Exit the function if successful
154
- except httpx.HTTPStatusError as e:
155
- if e.response.status_code != 404:
156
- raise # Re-raise if it's not a 404 error
157
- await asyncio.sleep(self.client.poll_interval)
158
-
159
-
160
- class TaskCollection(Collection):
161
- """A model representing a collection of tasks for a given deployment."""
162
-
163
- deployment_id: str = Field(
164
- description="The ID of the deployment these tasks belong to."
165
- )
166
-
167
- async def run(self, task: TaskDefinition) -> Any:
168
- """Runs a task and returns the results once it's done.
169
-
170
- Args:
171
- task: The definition of the task we want to run.
172
- """
173
- run_url = (
174
- f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/run"
175
- )
176
- if task.session_id:
177
- run_url += f"?session_id={task.session_id}"
178
-
179
- r = await self.client.request(
180
- "POST",
181
- run_url,
182
- verify=not self.client.disable_ssl,
183
- json=task.model_dump(),
184
- timeout=self.client.timeout,
185
- )
186
-
187
- return r.json()
188
-
189
- async def create(self, task: TaskDefinition) -> Task:
190
- """Runs a task returns it immediately, without waiting for the results."""
191
- create_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/create"
192
-
193
- r = await self.client.request(
194
- "POST",
195
- create_url,
196
- verify=not self.client.disable_ssl,
197
- json=task.model_dump(),
198
- timeout=self.client.timeout,
199
- )
200
- response_fields = r.json()
201
-
202
- model_class = self._prepare(Task)
203
- return model_class(
204
- client=self.client,
205
- deployment_id=self.deployment_id,
206
- id=response_fields["task_id"],
207
- session_id=response_fields["session_id"],
208
- )
209
-
210
- async def list(self) -> list[Task]:
211
- """Returns the list of tasks from this collection."""
212
- tasks_url = (
213
- f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks"
214
- )
215
- r = await self.client.request(
216
- "GET",
217
- tasks_url,
218
- verify=not self.client.disable_ssl,
219
- timeout=self.client.timeout,
220
- )
221
- task_model_class = self._prepare(Task)
222
- items = {
223
- "id": task_model_class(
224
- client=self.client,
225
- id=task_def.task_id,
226
- session_id=task_def.session_id,
227
- deployment_id=self.deployment_id,
228
- )
229
- for task_def in r.json()
230
- }
231
- model_class = self._prepare(TaskCollection)
232
- return model_class(
233
- client=self.client, deployment_id=self.deployment_id, items=items
234
- )
235
-
236
-
237
- class Deployment(Model):
238
- """A model representing a deployment."""
239
-
240
- @property
241
- def tasks(self) -> TaskCollection:
242
- """Returns a collection of tasks from all the sessions in the given deployment."""
243
-
244
- model_class = self._prepare(TaskCollection)
245
- return model_class(client=self.client, deployment_id=self.id, items={})
246
-
247
- @property
248
- def sessions(self) -> SessionCollection:
249
- """Returns a collection of all the sessions in the given deployment."""
250
-
251
- coll_model_class = self._prepare(SessionCollection)
252
- return coll_model_class(client=self.client, deployment_id=self.id, items={})
253
-
254
-
255
- class DeploymentCollection(Collection):
256
- """A model representing a collection of deployments currently active."""
257
-
258
- async def create(
259
- self, config: TextIO, base_path: str, reload: bool = False, local: bool = False
260
- ) -> Deployment:
261
- """Creates a new deployment from a deployment file.
262
-
263
- If `reload` is true, an existing deployment will be reloaded, otherwise
264
- an error will be raised.
265
-
266
- If `local` is true, the sync managers won't attempt at syncing data.
267
- This is mostly for supporting local development.
268
-
269
- Example:
270
- ```
271
- with open("deployment.yml") as f:
272
- await client.apiserver.deployments.create(f)
273
- ```
274
- """
275
- create_url = f"{self.client.api_server_url}/deployments/create"
276
-
277
- files = {"config_file": config.read()}
278
- r = await self.client.request(
279
- "POST",
280
- create_url,
281
- files=files,
282
- params={"reload": reload, "local": local, "base_path": base_path},
283
- verify=not self.client.disable_ssl,
284
- timeout=self.client.timeout,
285
- )
286
-
287
- model_class = self._prepare(Deployment)
288
- return model_class(client=self.client, id=r.json().get("name"))
289
-
290
- async def get(self, id: str) -> Deployment:
291
- """Gets a deployment by id."""
292
- get_url = f"{self.client.api_server_url}/deployments/{id}"
293
- # Current version of apiserver doesn't returns anything useful in this endpoint, let's just ignore it
294
- await self.client.request(
295
- "GET",
296
- get_url,
297
- verify=not self.client.disable_ssl,
298
- timeout=self.client.timeout,
299
- )
300
- model_class = self._prepare(Deployment)
301
- return model_class(client=self.client, id=id)
302
-
303
- async def list(self) -> list[Deployment]:
304
- """Return a list of Deployment instances for this collection."""
305
- deployments_url = f"{self.client.api_server_url}/deployments/"
306
- r = await self.client.request("GET", deployments_url)
307
- model_class = self._prepare(Deployment)
308
- deployments = [model_class(client=self.client, id=name) for name in r.json()]
309
- return deployments
310
-
311
-
312
- class ApiServer(Model):
313
- """A model representing the API Server instance."""
314
-
315
- async def status(self) -> Status:
316
- """Returns the status of the API Server."""
317
- status_url = f"{self.client.api_server_url}/status/"
318
-
319
- try:
320
- r = await self.client.request(
321
- "GET",
322
- status_url,
323
- verify=not self.client.disable_ssl,
324
- timeout=self.client.timeout,
325
- )
326
- except httpx.ConnectError:
327
- return Status(
328
- status=StatusEnum.DOWN,
329
- status_message="API Server is down",
330
- )
331
-
332
- if r.status_code >= 400:
333
- body = r.json()
334
- return Status(status=StatusEnum.UNHEALTHY, status_message=r.text)
335
-
336
- description = "LlamaDeploy is up and running."
337
- body = r.json()
338
- deployments = body.get("deployments") or []
339
- if deployments:
340
- description += "\nActive deployments:"
341
- for d in deployments:
342
- description += f"\n- {d}"
343
- else:
344
- description += "\nCurrently there are no active deployments"
345
-
346
- return Status(
347
- status=StatusEnum.HEALTHY,
348
- status_message=description,
349
- deployments=deployments,
350
- )
351
-
352
- @property
353
- def deployments(self) -> DeploymentCollection:
354
- """Returns a collection of deployments currently active in the API Server."""
355
- model_class = self._prepare(DeploymentCollection)
356
- return model_class(client=self.client, items={})
@@ -1,82 +0,0 @@
1
- import asyncio
2
- import inspect
3
- from typing import Any, AsyncGenerator, Callable, Generic, TypeVar
4
-
5
- from asgiref.sync import async_to_sync
6
- from llama_deploy.appserver.client.base import _BaseClient
7
- from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
8
- from typing_extensions import ParamSpec
9
-
10
-
11
- class _Base(BaseModel):
12
- """The base model provides fields and functionalities common to derived models and collections."""
13
-
14
- client: _BaseClient = Field(exclude=True)
15
- _instance_is_sync: bool = PrivateAttr(default=False)
16
-
17
- model_config = ConfigDict(arbitrary_types_allowed=True)
18
-
19
- def _prepare(self, _class: type) -> type:
20
- if self._instance_is_sync:
21
- return make_sync(_class)
22
- return _class
23
-
24
-
25
- T = TypeVar("T", bound=_Base)
26
-
27
-
28
- class Model(_Base):
29
- id: str
30
-
31
-
32
- class Collection(_Base, Generic[T]):
33
- """A generic container of items of the same model type."""
34
-
35
- items: dict[str, T]
36
-
37
- def get(self, id: str) -> T:
38
- """Returns an item from the collection."""
39
- return self.items[id]
40
-
41
- async def list(self) -> list[T]:
42
- """Returns a list of all the items in the collection."""
43
- return [self.get(id) for id in self.items.keys()]
44
-
45
-
46
- # Generic type for what's returned by the async generator
47
- _G = TypeVar("_G")
48
- # Generic parameter for the wrapped generator method
49
- _P = ParamSpec("_P")
50
- # Generic parameter for the wrapped generator method return value
51
- _R = TypeVar("_R")
52
-
53
-
54
- async def _async_gen_to_list(async_gen: AsyncGenerator[_G, None]) -> list[_G]:
55
- return [item async for item in async_gen]
56
-
57
-
58
- def make_sync(_class: type[T]) -> Any:
59
- """Wraps the methods of the given model class so that they can be called without `await`."""
60
-
61
- class ModelWrapper(_class): # type: ignore
62
- _instance_is_sync: bool = True
63
-
64
- def generator_wrapper(
65
- func: Callable[_P, AsyncGenerator[_G, None]], # ty: ignore[invalid-type-form] - https://github.com/astral-sh/ty/issues/157
66
- /,
67
- *args: Any,
68
- **kwargs: Any,
69
- ) -> Callable[_P, list[_G]]: # ty: ignore[invalid-type-form] - https://github.com/astral-sh/ty/issues/157
70
- def new_func(*fargs: Any, **fkwargs: Any) -> list[_G]:
71
- return asyncio.run(_async_gen_to_list(func(*fargs, **fkwargs)))
72
-
73
- return new_func
74
-
75
- for name, method in _class.__dict__.items():
76
- # Only wrap async public methods
77
- if inspect.isasyncgenfunction(method):
78
- setattr(ModelWrapper, name, generator_wrapper(method))
79
- elif asyncio.iscoroutinefunction(method) and not name.startswith("_"):
80
- setattr(ModelWrapper, name, async_to_sync(method))
81
-
82
- return ModelWrapper
@@ -1,141 +0,0 @@
1
- import os
2
- import shutil
3
- import subprocess
4
- from pathlib import Path
5
-
6
- import uvicorn
7
- import yaml
8
- from prometheus_client import start_http_server
9
-
10
- from llama_deploy.appserver.settings import settings
11
-
12
- CLONED_REPO_FOLDER = Path("cloned_repo")
13
- RC_PATH = Path("/data")
14
-
15
-
16
- def run_process(args: list[str], cwd: str | None = None) -> None:
17
- kwargs = {
18
- "args": args,
19
- "capture_output": True,
20
- "text": True,
21
- "check": False,
22
- }
23
- if cwd:
24
- kwargs["cwd"] = cwd
25
- process = subprocess.run(**kwargs) # type: ignore
26
- if process.returncode != 0:
27
- stderr = process.stderr or ""
28
- raise Exception(stderr)
29
-
30
-
31
- def setup_repo(
32
- work_dir: Path, source: str, token: str | None = None, force: bool = False
33
- ) -> None:
34
- repo_url, ref_name = _parse_source(source, token)
35
- dest_dir = work_dir / CLONED_REPO_FOLDER
36
-
37
- # Remove existing repo if force=True
38
- if dest_dir.exists() and force:
39
- shutil.rmtree(dest_dir)
40
-
41
- if not dest_dir.exists():
42
- # need to do a full clone to resolve any kind of ref without exploding in
43
- # complexity (tag, branch, commit, short commit)
44
- clone_args = ["git", "clone", repo_url, str(dest_dir.absolute())]
45
- run_process(clone_args, cwd=str(work_dir.absolute()))
46
- else:
47
- run_process(["git", "fetch", "origin"], cwd=str(dest_dir.absolute()))
48
-
49
- # Checkout the ref (let git resolve it)
50
- if ref_name:
51
- run_process(["git", "checkout", ref_name], cwd=str(dest_dir.absolute()))
52
- # If no ref specified, stay on whatever the clone gave us (default branch)
53
-
54
-
55
- def _is_valid_uri(uri: str) -> bool:
56
- """Check if string looks like a valid URI"""
57
- return "://" in uri and "/" in uri.split("://", 1)[1]
58
-
59
-
60
- def _parse_source(source: str, pat: str | None = None) -> tuple[str, str | None]:
61
- """Accept Github urls like https://github.com/run-llama/llama_deploy.git@main
62
- or https://user:token@github.com/run-llama/llama_deploy.git@v1.0.0
63
- Returns the final URL (with auth if needed) and ref name (branch, tag, or commit SHA)"""
64
-
65
- # Try splitting on last @ to see if we have a ref specifier
66
- url = source
67
- ref_name = None
68
-
69
- if "@" in source:
70
- potential_url, potential_ref = source.rsplit("@", 1)
71
- if _is_valid_uri(potential_url):
72
- url = potential_url
73
- ref_name = potential_ref
74
-
75
- # Inject PAT auth if provided and URL doesn't already have auth
76
- if pat and "://" in url and "@" not in url:
77
- url = url.replace("https://", f"https://{pat}@")
78
-
79
- return url, ref_name
80
-
81
-
82
- def copy_sources(work_dir: Path, deployment_file_path: Path) -> None:
83
- app_folder = deployment_file_path.parent
84
- for item in app_folder.iterdir():
85
- if item.is_dir():
86
- # For directories, use copytree with dirs_exist_ok=True
87
- shutil.copytree(
88
- item, f"{work_dir.absolute()}/{item.name}", dirs_exist_ok=True
89
- )
90
- else:
91
- # For files, use copy2 to preserve metadata
92
- shutil.copy2(item, str(work_dir))
93
-
94
-
95
- if __name__ == "__main__":
96
- if settings.prometheus_enabled:
97
- start_http_server(settings.prometheus_port)
98
-
99
- repo_url = os.environ.get("REPO_URL", "")
100
- if not repo_url.startswith("https://") and not repo_url.startswith("http://"):
101
- raise ValueError("Git remote must HTTP(S)")
102
- repo_token = os.environ.get("GITHUB_PAT")
103
- work_dir = Path(os.environ.get("WORK_DIR", RC_PATH))
104
- work_dir.mkdir(exist_ok=True, parents=True)
105
-
106
- setup_repo(work_dir, repo_url, repo_token)
107
-
108
- if not settings.deployment_file_path:
109
- # first fall back to none LLAMA_DEPLOY_APISERVER_ prefixed env var (settings requires the prefix)
110
- settings.deployment_file_path = os.environ.get(
111
- "DEPLOYMENT_FILE_PATH", "deployment.yml"
112
- )
113
- deployment_file_path = settings.deployment_file_path
114
- deployment_file_abspath = work_dir / CLONED_REPO_FOLDER / deployment_file_path
115
- if not deployment_file_abspath.exists():
116
- raise ValueError(f"File {deployment_file_abspath} does not exist")
117
-
118
- deployment_override_name = os.environ.get("DEPLOYMENT_NAME")
119
- if deployment_override_name:
120
- with open(deployment_file_abspath) as f:
121
- # Replace deployment name with the overridden value
122
- data = yaml.safe_load(f)
123
-
124
- # Avoid failing here if the deployment config file has a wrong format,
125
- # let's do nothing if there's no field `name`
126
- if "name" in data:
127
- data["name"] = deployment_override_name
128
- with open(deployment_file_abspath, "w") as f:
129
- yaml.safe_dump(data, f)
130
-
131
- copy_sources(work_dir, deployment_file_abspath)
132
- shutil.rmtree(work_dir / CLONED_REPO_FOLDER)
133
-
134
- # update rc_path directly, as it has already been loaded, so setting the environment variable
135
- # doesn't work
136
- settings.rc_path = work_dir
137
- uvicorn.run(
138
- "llama_deploy.appserver.app:app",
139
- host=settings.host,
140
- port=settings.port,
141
- )
@@ -1,60 +0,0 @@
1
- import asyncio
2
- import logging
3
- from contextlib import asynccontextmanager
4
- from typing import Any, AsyncGenerator
5
-
6
- from fastapi import FastAPI
7
-
8
- from .deployment import Manager
9
- from .deployment_config_parser import DeploymentConfig
10
- from .settings import settings
11
- from .stats import apiserver_state
12
-
13
- logger = logging.getLogger("uvicorn.info")
14
- manager = Manager()
15
-
16
-
17
- @asynccontextmanager
18
- async def lifespan(app: FastAPI) -> AsyncGenerator[None, Any]:
19
- apiserver_state.state("starting")
20
-
21
- manager.set_deployments_path(settings.deployments_path)
22
- t = asyncio.create_task(manager.serve())
23
- await asyncio.sleep(0)
24
-
25
- logger.info(f"deployments folder: {settings.deployments_path}")
26
- logger.info(f"rc folder: {settings.rc_path}")
27
-
28
- if settings.rc_path.exists():
29
- if settings.deployment_file_path:
30
- logger.info(
31
- f"Browsing the rc folder {settings.rc_path} for deployment file {settings.deployment_file_path}"
32
- )
33
- else:
34
- logger.info(
35
- f"Browsing the rc folder {settings.rc_path} for deployments to start"
36
- )
37
-
38
- # if a deployment_file_path is provided, use it, otherwise glob all .yml/.yaml files
39
- # q match both .yml and .yaml files with the glob
40
- files = (
41
- [settings.rc_path / settings.deployment_file_path]
42
- if settings.deployment_file_path
43
- else [
44
- x for x in settings.rc_path.iterdir() if x.suffix in (".yml", ".yaml")
45
- ]
46
- )
47
- for yaml_file in files:
48
- try:
49
- logger.info(f"Deploying startup configuration from {yaml_file}")
50
- config = DeploymentConfig.from_yaml(yaml_file)
51
- await manager.deploy(config, base_path=str(settings.rc_path))
52
- except Exception as e:
53
- logger.error(f"Failed to deploy {yaml_file}: {str(e)}")
54
-
55
- apiserver_state.state("running")
56
- yield
57
-
58
- t.cancel()
59
-
60
- apiserver_state.state("stopped")
@@ -1,5 +0,0 @@
1
- from .base import SourceManager
2
- from .git import GitSourceManager
3
- from .local import LocalSourceManager
4
-
5
- __all__ = ["GitSourceManager", "LocalSourceManager", "SourceManager"]
@@ -1,33 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from pathlib import Path
3
-
4
- from llama_deploy.appserver.deployment_config_parser import DeploymentConfig, SyncPolicy
5
-
6
-
7
- class SourceManager(ABC):
8
- """Protocol to be implemented by classes responsible for managing Deployment sources."""
9
-
10
- def __init__(self, config: DeploymentConfig, base_path: Path | None = None) -> None:
11
- self._config = config
12
- self._base_path = base_path
13
-
14
- @abstractmethod
15
- def sync(
16
- self,
17
- source: str,
18
- destination: str | None = None,
19
- sync_policy: SyncPolicy = SyncPolicy.REPLACE,
20
- ) -> None: # pragma: no cover
21
- """Fetches resources from `source` so they can be used in a deployment.
22
-
23
- Optionally uses `destination` to store data when this makes sense for the
24
- specific source type.
25
- """
26
-
27
- def relative_path(self, source: str) -> str:
28
- """Unfortunately, there's a difference in behavior of how the source managers sync.
29
- The local source manager syncs the source into the <destination_path>/<source>, whereas
30
- the git source manager just syncs the source into the <destination_path>. This is a temporary shim, since
31
- changing this behavior is a breaking change to deployment.yaml configurations. Local source manager
32
- overrides it. In a future major version, this behavior will be made consistent"""
33
- return ""