llama-deploy-core 0.3.0a6__py3-none-any.whl → 0.3.0a8__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.
- llama_deploy/core/config.py +1 -1
- llama_deploy/core/deployment_config.py +318 -24
- llama_deploy/core/git/git_util.py +8 -0
- llama_deploy/core/path_util.py +24 -0
- llama_deploy/core/schema/__init__.py +2 -1
- llama_deploy/core/schema/base.py +9 -0
- llama_deploy/core/schema/deployments.py +2 -2
- llama_deploy/core/schema/git_validation.py +0 -1
- llama_deploy/core/ui_build.py +9 -6
- {llama_deploy_core-0.3.0a6.dist-info → llama_deploy_core-0.3.0a8.dist-info}/METADATA +1 -1
- llama_deploy_core-0.3.0a8.dist-info/RECORD +14 -0
- llama_deploy_core-0.3.0a6.dist-info/RECORD +0 -13
- {llama_deploy_core-0.3.0a6.dist-info → llama_deploy_core-0.3.0a8.dist-info}/WHEEL +0 -0
llama_deploy/core/config.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
DEFAULT_DEPLOYMENT_FILE_PATH = "
|
|
1
|
+
DEFAULT_DEPLOYMENT_FILE_PATH = "."
|
|
@@ -1,11 +1,288 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import tomllib
|
|
1
5
|
from pathlib import Path
|
|
2
6
|
from typing import Any
|
|
3
7
|
|
|
4
8
|
import yaml
|
|
5
|
-
from
|
|
9
|
+
from llama_deploy.core.path_util import validate_path_traversal
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
|
|
11
|
+
|
|
12
|
+
DEFAULT_DEPLOYMENT_NAME = "default"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_deployment_config(source_root: Path, config_path: Path) -> "DeploymentConfig":
|
|
16
|
+
"""
|
|
17
|
+
Read the deployment config from the config directory.
|
|
18
|
+
|
|
19
|
+
- first checks for a llamadeploy.toml in the config_path
|
|
20
|
+
- then checks for a tool config in the pyproject.toml
|
|
21
|
+
- then check for a legacy yaml config (if config_path is a file, uses that, otherwise uses the config_path/llama_deploy.yaml)
|
|
22
|
+
- based on what was resolved here, discovers the package.json, if any ui, and resolves its values from the package.json
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
source_root: path to the root of the source code. References should not exit this directory.
|
|
26
|
+
config_path: path to a deployment config file, or directory containing a deployment config file.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
DeploymentConfig: the deployment config
|
|
30
|
+
"""
|
|
31
|
+
config_file: Path | None = None
|
|
32
|
+
if (source_root / config_path).is_file():
|
|
33
|
+
config_file = Path(config_path.name)
|
|
34
|
+
if str(config_file) in {"llama_deploy.toml", "pyproject.toml"}:
|
|
35
|
+
config_file = None
|
|
36
|
+
config_path = config_path.parent
|
|
37
|
+
local_toml_path = source_root / config_path / "llama_deploy.toml"
|
|
38
|
+
pyproject_path = source_root / config_path / "pyproject.toml"
|
|
39
|
+
toml_config: DeploymentConfig = DeploymentConfig()
|
|
40
|
+
# local TOML format
|
|
41
|
+
if local_toml_path.exists():
|
|
42
|
+
with open(local_toml_path, "rb") as toml_file:
|
|
43
|
+
toml_data = tomllib.load(toml_file)
|
|
44
|
+
if isinstance(toml_data, dict):
|
|
45
|
+
toml_config = DeploymentConfig.model_validate(toml_data)
|
|
46
|
+
# pyproject.toml format
|
|
47
|
+
elif pyproject_path.exists():
|
|
48
|
+
with open(pyproject_path, "rb") as pyproject_file:
|
|
49
|
+
pyproject = tomllib.load(pyproject_file)
|
|
50
|
+
tool = pyproject.get("tool", {})
|
|
51
|
+
project_name: str | None = None
|
|
52
|
+
project_metadata = pyproject.get("project", {})
|
|
53
|
+
if isinstance(project_metadata, dict):
|
|
54
|
+
name = project_metadata.get("name")
|
|
55
|
+
if isinstance(name, str):
|
|
56
|
+
project_name = name
|
|
57
|
+
if isinstance(tool, dict):
|
|
58
|
+
llama_deploy = tool.get("llamadeploy", {})
|
|
59
|
+
if isinstance(llama_deploy, dict):
|
|
60
|
+
if "name" not in llama_deploy:
|
|
61
|
+
llama_deploy["name"] = project_name
|
|
62
|
+
toml_config = DeploymentConfig.model_validate(llama_deploy)
|
|
63
|
+
# legacy yaml format, (and why not support yaml in the new format too, since this is doing everything all the ways)
|
|
64
|
+
if toml_config.has_no_workflows():
|
|
65
|
+
yaml_path = (
|
|
66
|
+
source_root / config_path / (config_file or Path("llama_deploy.yaml"))
|
|
67
|
+
)
|
|
68
|
+
if yaml_path.exists():
|
|
69
|
+
with open(yaml_path, "r", encoding="utf-8") as yaml_file:
|
|
70
|
+
yaml_loaded = yaml.safe_load(yaml_file) or {}
|
|
71
|
+
|
|
72
|
+
old_config: DeploymentConfig | None = None
|
|
73
|
+
new_config: DeploymentConfig | None = None
|
|
74
|
+
try:
|
|
75
|
+
old_config = DeprecatedDeploymentConfig.model_validate(
|
|
76
|
+
yaml_loaded
|
|
77
|
+
).to_deployment_config()
|
|
78
|
+
except ValidationError:
|
|
79
|
+
pass
|
|
80
|
+
try:
|
|
81
|
+
new_config = DeploymentConfig.model_validate(yaml_loaded)
|
|
82
|
+
except ValidationError:
|
|
83
|
+
pass
|
|
84
|
+
loaded: DeploymentConfig | None = new_config
|
|
85
|
+
if (
|
|
86
|
+
old_config is not None
|
|
87
|
+
and old_config.is_valid()
|
|
88
|
+
and (new_config is None or not new_config.is_valid())
|
|
89
|
+
):
|
|
90
|
+
loaded = old_config
|
|
91
|
+
if loaded is not None:
|
|
92
|
+
toml_config = toml_config.merge_config(loaded)
|
|
93
|
+
|
|
94
|
+
# package.json format
|
|
95
|
+
if toml_config.ui is not None:
|
|
96
|
+
package_json_path = (
|
|
97
|
+
source_root / config_path / toml_config.ui.directory / "package.json"
|
|
98
|
+
)
|
|
99
|
+
if package_json_path.exists():
|
|
100
|
+
with open(package_json_path, "r", encoding="utf-8") as package_json_file:
|
|
101
|
+
package_json = json.load(package_json_file)
|
|
102
|
+
if isinstance(package_json, dict):
|
|
103
|
+
# Standard packageManager fallback, e.g. "pnpm@9.0.0" -> "pnpm"
|
|
104
|
+
pkg_manager_value = package_json.get("packageManager")
|
|
105
|
+
pkg_manager_name: str | None = None
|
|
106
|
+
if isinstance(pkg_manager_value, str) and pkg_manager_value:
|
|
107
|
+
pkg_manager_name = pkg_manager_value.split("@", 1)[0] or None
|
|
108
|
+
|
|
109
|
+
llama_deploy = package_json.get("llamadeploy", {})
|
|
110
|
+
|
|
111
|
+
if isinstance(llama_deploy, dict):
|
|
112
|
+
# Prepare payload without leaking Path objects into Pydantic
|
|
113
|
+
ui_dir = toml_config.ui.directory if toml_config.ui else None
|
|
114
|
+
ui_payload: dict[str, object] = {**llama_deploy}
|
|
115
|
+
if "directory" not in ui_payload and ui_dir is not None:
|
|
116
|
+
ui_payload["directory"] = ui_dir
|
|
117
|
+
if (
|
|
118
|
+
"package_manager" not in ui_payload
|
|
119
|
+
and pkg_manager_name is not None
|
|
120
|
+
):
|
|
121
|
+
ui_payload["package_manager"] = pkg_manager_name
|
|
122
|
+
|
|
123
|
+
ui_config = UIConfig.model_validate(ui_payload)
|
|
124
|
+
if ui_config.build_output_dir is not None:
|
|
125
|
+
ui_config.build_output_dir = str(
|
|
126
|
+
Path(toml_config.ui.directory) / ui_config.build_output_dir
|
|
127
|
+
)
|
|
128
|
+
toml_config.ui = ui_config.merge_config(toml_config.ui)
|
|
129
|
+
|
|
130
|
+
if toml_config.ui is not None:
|
|
131
|
+
validate_path_traversal(
|
|
132
|
+
config_path / toml_config.ui.directory, source_root, "ui_source"
|
|
133
|
+
)
|
|
134
|
+
if toml_config.ui.build_output_dir:
|
|
135
|
+
validate_path_traversal(
|
|
136
|
+
config_path / toml_config.ui.build_output_dir,
|
|
137
|
+
source_root,
|
|
138
|
+
"ui_build_output_dir",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return toml_config
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def resolve_config_parent(root: Path, deployment_path: Path) -> Path:
|
|
145
|
+
path = root / deployment_path
|
|
146
|
+
if path.is_file():
|
|
147
|
+
return path.parent
|
|
148
|
+
else:
|
|
149
|
+
return path
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
DEFAULT_UI_PACKAGE_MANAGER = "npm"
|
|
153
|
+
DEFAULT_UI_BUILD_COMMAND = "build"
|
|
154
|
+
DEFAULT_UI_SERVE_COMMAND = "dev"
|
|
155
|
+
DEFAULT_UI_PROXY_PORT = 4502
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class DeploymentConfig(BaseModel):
|
|
159
|
+
name: str = Field(
|
|
160
|
+
default=DEFAULT_DEPLOYMENT_NAME,
|
|
161
|
+
description="The url safe path name of the deployment.",
|
|
162
|
+
)
|
|
163
|
+
app: str | None = Field(
|
|
164
|
+
None,
|
|
165
|
+
description="A full bundle of all workflows as an 'app'. \"path.to_import:app_name\"",
|
|
166
|
+
)
|
|
167
|
+
workflows: dict[str, str] = Field(
|
|
168
|
+
default_factory=dict,
|
|
169
|
+
description='Deprecated: A map of workflow names to their import paths. "nice_name": "path.to_import:workflow_name"',
|
|
170
|
+
)
|
|
171
|
+
env_files: list[str] = Field(
|
|
172
|
+
default_factory=list,
|
|
173
|
+
description="The environment files to load. Defaults to ['.env']",
|
|
174
|
+
)
|
|
175
|
+
env: dict[str, str] = Field(
|
|
176
|
+
default_factory=dict,
|
|
177
|
+
description="Arbitrary environment variables to set. Defaults to {}",
|
|
178
|
+
)
|
|
179
|
+
ui: UIConfig | None = Field(
|
|
180
|
+
None,
|
|
181
|
+
description="The UI configuration.",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def merge_config(self, config: "DeploymentConfig") -> "DeploymentConfig":
|
|
185
|
+
"""Merge the config with another config."""
|
|
6
186
|
|
|
187
|
+
return DeploymentConfig(
|
|
188
|
+
name=_pick_non_default(self.name, config.name, "default"),
|
|
189
|
+
app=self.app or config.app,
|
|
190
|
+
workflows={**self.workflows, **config.workflows},
|
|
191
|
+
env_files=list(set(self.env_files + config.env_files)),
|
|
192
|
+
env={**self.env, **config.env},
|
|
193
|
+
ui=self.ui.merge_config(config.ui)
|
|
194
|
+
if config.ui is not None and self.ui is not None
|
|
195
|
+
else self.ui or config.ui,
|
|
196
|
+
)
|
|
7
197
|
|
|
8
|
-
|
|
198
|
+
def has_no_workflows(self) -> bool:
|
|
199
|
+
"""Check if the config has no workflows."""
|
|
200
|
+
return len(self.workflows) == 0 and self.app is None
|
|
201
|
+
|
|
202
|
+
def has_both_app_and_workflows(self) -> bool:
|
|
203
|
+
"""Check if the config has both app and workflows."""
|
|
204
|
+
return self.app is not None and len(self.workflows) > 0
|
|
205
|
+
|
|
206
|
+
def is_valid(self) -> bool:
|
|
207
|
+
"""Check if the config is valid."""
|
|
208
|
+
try:
|
|
209
|
+
self.validate()
|
|
210
|
+
return True
|
|
211
|
+
except ValueError:
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def validate(self) -> None:
|
|
215
|
+
"""Validate the config."""
|
|
216
|
+
if self.has_no_workflows():
|
|
217
|
+
raise ValueError("Config must have at least one workflow.")
|
|
218
|
+
if self.has_both_app_and_workflows():
|
|
219
|
+
raise ValueError("Config cannot have both app and workflows configured.")
|
|
220
|
+
|
|
221
|
+
def build_output_path(self) -> Path | None:
|
|
222
|
+
"""get the build output path, or default to the ui directory/dist"""
|
|
223
|
+
if self.ui is None:
|
|
224
|
+
return None
|
|
225
|
+
return (
|
|
226
|
+
Path(self.ui.build_output_dir)
|
|
227
|
+
if self.ui.build_output_dir
|
|
228
|
+
else Path(self.ui.directory) / "dist"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _pick_non_default[T](a: T, b: T, default: T) -> T:
|
|
233
|
+
if a != default:
|
|
234
|
+
return a
|
|
235
|
+
return b or default
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class UIConfig(BaseModel):
|
|
239
|
+
directory: str = Field(
|
|
240
|
+
...,
|
|
241
|
+
description="The directory containing the UI, relative to the pyproject.toml directory",
|
|
242
|
+
)
|
|
243
|
+
build_output_dir: str | None = Field(
|
|
244
|
+
None,
|
|
245
|
+
description="The directory containing the built UI, relative to the pyproject.toml directory. Defaults to 'dist' relative to the ui_directory, if defined",
|
|
246
|
+
)
|
|
247
|
+
package_manager: str = Field(
|
|
248
|
+
DEFAULT_UI_PACKAGE_MANAGER,
|
|
249
|
+
description=f"The package manager to use to build the UI. Defaults to '{DEFAULT_UI_PACKAGE_MANAGER}'",
|
|
250
|
+
)
|
|
251
|
+
build_command: str = Field(
|
|
252
|
+
DEFAULT_UI_BUILD_COMMAND,
|
|
253
|
+
description=f"The npm script command to build the UI. Defaults to '{DEFAULT_UI_BUILD_COMMAND}' if not specified",
|
|
254
|
+
)
|
|
255
|
+
serve_command: str = Field(
|
|
256
|
+
DEFAULT_UI_SERVE_COMMAND,
|
|
257
|
+
description=f"The command to serve the UI. Defaults to '{DEFAULT_UI_SERVE_COMMAND}' if not specified",
|
|
258
|
+
)
|
|
259
|
+
proxy_port: int = Field(
|
|
260
|
+
DEFAULT_UI_PROXY_PORT,
|
|
261
|
+
description=f"The port to proxy the UI to. Defaults to '{DEFAULT_UI_PROXY_PORT}' if not specified",
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def merge_config(self, config: "UIConfig") -> "UIConfig":
|
|
265
|
+
"""Merge the config with the default config."""
|
|
266
|
+
|
|
267
|
+
return UIConfig(
|
|
268
|
+
directory=self.directory,
|
|
269
|
+
build_output_dir=self.build_output_dir or config.build_output_dir,
|
|
270
|
+
package_manager=_pick_non_default(
|
|
271
|
+
self.package_manager, config.package_manager, DEFAULT_UI_PACKAGE_MANAGER
|
|
272
|
+
),
|
|
273
|
+
build_command=_pick_non_default(
|
|
274
|
+
self.build_command, config.build_command, DEFAULT_UI_BUILD_COMMAND
|
|
275
|
+
),
|
|
276
|
+
serve_command=_pick_non_default(
|
|
277
|
+
self.serve_command, config.serve_command, DEFAULT_UI_SERVE_COMMAND
|
|
278
|
+
),
|
|
279
|
+
proxy_port=_pick_non_default(
|
|
280
|
+
self.proxy_port, config.proxy_port, DEFAULT_UI_PROXY_PORT
|
|
281
|
+
),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class ServiceSourceV0(BaseModel):
|
|
9
286
|
"""Configuration for where to load the workflow or other source. Path is relative to the config file its declared within."""
|
|
10
287
|
|
|
11
288
|
location: str
|
|
@@ -19,10 +296,10 @@ class ServiceSource(BaseModel):
|
|
|
19
296
|
return data
|
|
20
297
|
|
|
21
298
|
|
|
22
|
-
class
|
|
299
|
+
class DerecatedService(BaseModel):
|
|
23
300
|
"""Configuration for a single service."""
|
|
24
301
|
|
|
25
|
-
source:
|
|
302
|
+
source: ServiceSourceV0 | None = Field(None)
|
|
26
303
|
import_path: str | None = Field(None)
|
|
27
304
|
env: dict[str, str] | None = Field(None)
|
|
28
305
|
env_files: list[str] | None = Field(None)
|
|
@@ -54,22 +331,15 @@ class Service(BaseModel):
|
|
|
54
331
|
return Path(module_name).name, workflow_name
|
|
55
332
|
|
|
56
333
|
|
|
57
|
-
class
|
|
58
|
-
port: int = Field(
|
|
59
|
-
default=3000,
|
|
60
|
-
description="The TCP port to use for the nextjs server",
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class DeploymentConfig(BaseModel):
|
|
334
|
+
class DeprecatedDeploymentConfig(BaseModel):
|
|
65
335
|
"""Model definition mapping a deployment config file."""
|
|
66
336
|
|
|
67
337
|
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
68
338
|
|
|
69
339
|
name: str
|
|
70
340
|
default_service: str | None = Field(None)
|
|
71
|
-
services: dict[str,
|
|
72
|
-
ui:
|
|
341
|
+
services: dict[str, DerecatedService]
|
|
342
|
+
ui: DerecatedService | None = None
|
|
73
343
|
|
|
74
344
|
@model_validator(mode="before")
|
|
75
345
|
@classmethod
|
|
@@ -82,18 +352,42 @@ class DeploymentConfig(BaseModel):
|
|
|
82
352
|
return data
|
|
83
353
|
|
|
84
354
|
@classmethod
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@classmethod
|
|
91
|
-
def from_yaml(cls, path: Path, name: str | None = None) -> "DeploymentConfig":
|
|
355
|
+
def from_yaml(
|
|
356
|
+
cls,
|
|
357
|
+
path: Path,
|
|
358
|
+
) -> "DeprecatedDeploymentConfig":
|
|
92
359
|
"""Read config data from a yaml file."""
|
|
93
360
|
with open(path, "r", encoding="utf-8") as yaml_file:
|
|
94
361
|
config = yaml.safe_load(yaml_file) or {}
|
|
95
362
|
|
|
96
|
-
instance = cls(
|
|
97
|
-
if name:
|
|
98
|
-
instance.name = name
|
|
363
|
+
instance = cls.model_validate(config)
|
|
99
364
|
return instance
|
|
365
|
+
|
|
366
|
+
def to_deployment_config(self) -> DeploymentConfig:
|
|
367
|
+
"""Convert the deployment config to a DeploymentConfig."""
|
|
368
|
+
workflows = {}
|
|
369
|
+
env_files = []
|
|
370
|
+
env = {}
|
|
371
|
+
ui_directory: str | None = None
|
|
372
|
+
for service_name, service in self.services.items():
|
|
373
|
+
if service.import_path:
|
|
374
|
+
path, name = service.module_location()
|
|
375
|
+
workflows[service_name] = f"{path}:{name}"
|
|
376
|
+
if service.env_files:
|
|
377
|
+
env_files.extend(service.env_files)
|
|
378
|
+
if service.env:
|
|
379
|
+
env.update(service.env)
|
|
380
|
+
if self.default_service:
|
|
381
|
+
workflows["default"] = workflows[self.default_service]
|
|
382
|
+
env_files = list(set(env_files))
|
|
383
|
+
|
|
384
|
+
if self.ui:
|
|
385
|
+
ui_directory = self.ui.source.location
|
|
386
|
+
|
|
387
|
+
return DeploymentConfig(
|
|
388
|
+
name=self.name,
|
|
389
|
+
workflows=workflows,
|
|
390
|
+
env_files=env_files,
|
|
391
|
+
env=env,
|
|
392
|
+
ui=UIConfig(directory=ui_directory) if ui_directory else None,
|
|
393
|
+
)
|
|
@@ -261,3 +261,11 @@ def get_current_branch() -> str | None:
|
|
|
261
261
|
"""
|
|
262
262
|
result = _run_process(["git", "branch", "--show-current"])
|
|
263
263
|
return result.strip() if result.strip() else None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def get_git_root() -> Path:
|
|
267
|
+
"""
|
|
268
|
+
get the root of the current git repo
|
|
269
|
+
"""
|
|
270
|
+
result = _run_process(["git", "rev-parse", "--show-toplevel"])
|
|
271
|
+
return Path(result.strip())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_path_traversal(
|
|
5
|
+
path: Path, source_root: Path, path_type: str = "path"
|
|
6
|
+
) -> None:
|
|
7
|
+
"""Validates that a path is within the source root to prevent path traversal attacks.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
path: The path to validate
|
|
11
|
+
source_root: The root directory that paths should be relative to
|
|
12
|
+
path_type: Description of the path type for error messages
|
|
13
|
+
|
|
14
|
+
Raises:
|
|
15
|
+
DeploymentError: If the path is outside the source root
|
|
16
|
+
"""
|
|
17
|
+
resolved_path = (source_root / path).resolve()
|
|
18
|
+
resolved_source_root = source_root.resolve()
|
|
19
|
+
|
|
20
|
+
if not resolved_path.is_relative_to(resolved_source_root):
|
|
21
|
+
msg = (
|
|
22
|
+
f"{path_type} {path} is not a subdirectory of the source root {source_root}"
|
|
23
|
+
)
|
|
24
|
+
raise RuntimeError(msg)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .base import Base
|
|
1
|
+
from .base import Base, LogEvent
|
|
2
2
|
from .deployments import (
|
|
3
3
|
DeploymentCreate,
|
|
4
4
|
DeploymentResponse,
|
|
@@ -13,6 +13,7 @@ from .projects import ProjectsListResponse, ProjectSummary
|
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
15
|
"Base",
|
|
16
|
+
"LogEvent",
|
|
16
17
|
"DeploymentCreate",
|
|
17
18
|
"DeploymentResponse",
|
|
18
19
|
"DeploymentUpdate",
|
llama_deploy/core/schema/base.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
1
3
|
from pydantic import BaseModel, ConfigDict
|
|
2
4
|
|
|
3
5
|
base_config = ConfigDict(
|
|
@@ -18,3 +20,10 @@ base_config = ConfigDict(
|
|
|
18
20
|
|
|
19
21
|
class Base(BaseModel):
|
|
20
22
|
model_config = base_config
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LogEvent(Base):
|
|
26
|
+
pod: str
|
|
27
|
+
container: str
|
|
28
|
+
text: str
|
|
29
|
+
timestamp: datetime
|
|
@@ -30,6 +30,7 @@ class DeploymentResponse(Base):
|
|
|
30
30
|
secret_names: list[str] | None = None
|
|
31
31
|
apiserver_url: HttpUrl | None
|
|
32
32
|
status: LlamaDeploymentPhase
|
|
33
|
+
warning: str | None = None
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class DeploymentsListResponse(Base):
|
|
@@ -65,7 +66,7 @@ class LlamaDeploymentSpec(Base):
|
|
|
65
66
|
|
|
66
67
|
projectId: str
|
|
67
68
|
repoUrl: str
|
|
68
|
-
deploymentFilePath: str = "
|
|
69
|
+
deploymentFilePath: str = "."
|
|
69
70
|
gitRef: str | None = None
|
|
70
71
|
gitSha: str | None = None
|
|
71
72
|
name: str
|
|
@@ -164,7 +165,6 @@ def apply_deployment_update(
|
|
|
164
165
|
updated_spec.staticAssetsPath = (
|
|
165
166
|
str(update.static_assets_path) if update.static_assets_path else None
|
|
166
167
|
)
|
|
167
|
-
print(f"staticAssetsPath: {updated_spec.staticAssetsPath}")
|
|
168
168
|
|
|
169
169
|
# Track secret changes
|
|
170
170
|
secret_adds: dict[str, str] = {}
|
llama_deploy/core/ui_build.py
CHANGED
|
@@ -10,8 +10,7 @@ def resolve_ui_root(config_parent: Path, config: DeploymentConfig) -> Path | Non
|
|
|
10
10
|
"""Return the absolute path to the UI root if UI is configured; otherwise None."""
|
|
11
11
|
if config.ui is None:
|
|
12
12
|
return None
|
|
13
|
-
|
|
14
|
-
return (config_parent / rel).resolve()
|
|
13
|
+
return (config_parent / config.ui.directory).resolve()
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
def ui_build_output_path(config_parent: Path, config: DeploymentConfig) -> Path | None:
|
|
@@ -20,22 +19,26 @@ def ui_build_output_path(config_parent: Path, config: DeploymentConfig) -> Path
|
|
|
20
19
|
Right now, assumes its just `/dist` in the UI root.
|
|
21
20
|
|
|
22
21
|
Returns:
|
|
23
|
-
-
|
|
24
|
-
- False if a package.json exists but lacks a "build" script
|
|
22
|
+
- Path to the build output directory if a package.json exists and contains a "build" script
|
|
25
23
|
- None if there is no UI configured or no package.json exists
|
|
26
24
|
"""
|
|
27
25
|
ui_root = resolve_ui_root(config_parent, config)
|
|
28
26
|
if ui_root is None:
|
|
29
27
|
return None
|
|
28
|
+
if config.ui is None:
|
|
29
|
+
return None
|
|
30
30
|
package_json = ui_root / "package.json"
|
|
31
31
|
if not package_json.exists():
|
|
32
32
|
return None
|
|
33
33
|
try:
|
|
34
34
|
with open(package_json, "r", encoding="utf-8") as f:
|
|
35
35
|
pkg = json.load(f)
|
|
36
|
+
if not isinstance(pkg, dict):
|
|
37
|
+
return None
|
|
36
38
|
scripts = pkg.get("scripts", {}) or {}
|
|
37
|
-
if
|
|
38
|
-
return
|
|
39
|
+
if config.ui.build_command in scripts:
|
|
40
|
+
return config.build_output_path()
|
|
41
|
+
return None
|
|
39
42
|
except Exception:
|
|
40
43
|
# Do not raise for malformed package.json in validation contexts
|
|
41
44
|
return None
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
llama_deploy/core/__init__.py,sha256=112612bf2e928c2e0310d6556bb13fc28c00db70297b90a8527486cd2562e408,43
|
|
2
|
+
llama_deploy/core/config.py,sha256=69bb0ea8ac169eaa4e808cd60a098b616bddd3145d26c6c35e56db38496b0e6a,35
|
|
3
|
+
llama_deploy/core/deployment_config.py,sha256=ff10cc96f2c64abc4761eb83c5372fd22f3770159b45503818264723b578de4e,15092
|
|
4
|
+
llama_deploy/core/git/git_util.py,sha256=c581c1da13871b4e89eda58f56ddb074139454c06ae9b04c0b396fdb2b9a5176,9193
|
|
5
|
+
llama_deploy/core/path_util.py,sha256=14d50c0c337c8450ed46cafc88436027056b365a48370a69cdb76c88d7c26fd1,798
|
|
6
|
+
llama_deploy/core/schema/__init__.py,sha256=cc60a6fb54983d7ca13e2cc86d414a0d006a79c20e44344701f9fbe3b1d21577,739
|
|
7
|
+
llama_deploy/core/schema/base.py,sha256=c02e33e35e7e4540b3065a82267febeb6da169222210a1d1c2479f6a7f1c6a4b,802
|
|
8
|
+
llama_deploy/core/schema/deployments.py,sha256=fe4c540d39a3e369e3fa286df128398043f591ddc4a623ccb2d432596c53dca6,6108
|
|
9
|
+
llama_deploy/core/schema/git_validation.py,sha256=27b306aa6ecabe58cab6381d92551545f263fe7550c58b3087115410bc71fd21,1915
|
|
10
|
+
llama_deploy/core/schema/projects.py,sha256=c97eda38207d80354c2ee3a237cba9c3f6838148197cfa2d97b9a18d3da1a38b,294
|
|
11
|
+
llama_deploy/core/ui_build.py,sha256=290dafa951918e5593b9035570fa4c66791d7e5ea785bd372ad11e99e8283857,1514
|
|
12
|
+
llama_deploy_core-0.3.0a8.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
|
|
13
|
+
llama_deploy_core-0.3.0a8.dist-info/METADATA,sha256=f5fe24509ec6739b64202009cd19dc590052aa2a35cddf114ba985e430969993,402
|
|
14
|
+
llama_deploy_core-0.3.0a8.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
llama_deploy/core/__init__.py,sha256=112612bf2e928c2e0310d6556bb13fc28c00db70297b90a8527486cd2562e408,43
|
|
2
|
-
llama_deploy/core/config.py,sha256=41c5a1baa25a67f0bb58b701ef8f0b8b3281714d7116580b8f87674eb9e396a3,51
|
|
3
|
-
llama_deploy/core/deployment_config.py,sha256=2907106ca4cb504fda407bf2c7b1114a50ed12da00b3876e4340016f2ffbb78d,3143
|
|
4
|
-
llama_deploy/core/git/git_util.py,sha256=d3a57ce6c8bce3da26bd554df222988ea02fb1e20f3f5192dad69a2bbd1395a4,9007
|
|
5
|
-
llama_deploy/core/schema/__init__.py,sha256=3e838d5304bc1f11c42590ba78267405e957e11add783e280d00bf5766728072,713
|
|
6
|
-
llama_deploy/core/schema/base.py,sha256=2de6d23e58c36b6bb311ec0aea4b902661867056c1250c6b7ce3bad17141fe15,677
|
|
7
|
-
llama_deploy/core/schema/deployments.py,sha256=b23a458f71cebc4b13ab32e922ba6759dcb7f69eeca2b5e28cda10766a109251,6157
|
|
8
|
-
llama_deploy/core/schema/git_validation.py,sha256=ce58491992252fd0bba6dcf3fc776f57995eb407a531aeb0a050a4f0587f6e50,1943
|
|
9
|
-
llama_deploy/core/schema/projects.py,sha256=c97eda38207d80354c2ee3a237cba9c3f6838148197cfa2d97b9a18d3da1a38b,294
|
|
10
|
-
llama_deploy/core/ui_build.py,sha256=88226f6f2b9a91b86de7d4c49024ef254d946b6519057712bb51bcae8a8deaeb,1472
|
|
11
|
-
llama_deploy_core-0.3.0a6.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
|
|
12
|
-
llama_deploy_core-0.3.0a6.dist-info/METADATA,sha256=fb6f43d57436b11908fa44388c04c3326e3322b3329a4941522ac3032dfb251c,402
|
|
13
|
-
llama_deploy_core-0.3.0a6.dist-info/RECORD,,
|
|
File without changes
|