llama-deploy-core 0.3.0a4__tar.gz → 0.3.0a5__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.0a4 → llama_deploy_core-0.3.0a5}/PKG-INFO +1 -1
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/pyproject.toml +1 -1
- llama_deploy_core-0.3.0a5/src/llama_deploy/core/deployment_config.py +99 -0
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/git/git_util.py +31 -4
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/__init__.py +4 -4
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/deployments.py +11 -2
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/git_validation.py +10 -4
- llama_deploy_core-0.3.0a5/src/llama_deploy/core/ui_build.py +41 -0
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/README.md +0 -0
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/__init__.py +0 -0
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/config.py +0 -0
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/base.py +0 -0
- {llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/projects.py +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ServiceSource(BaseModel):
|
|
9
|
+
"""Configuration for where to load the workflow or other source. Path is relative to the config file its declared within."""
|
|
10
|
+
|
|
11
|
+
location: str
|
|
12
|
+
|
|
13
|
+
@model_validator(mode="before")
|
|
14
|
+
@classmethod
|
|
15
|
+
def validate_fields(cls, data: Any) -> Any:
|
|
16
|
+
if isinstance(data, dict):
|
|
17
|
+
if "name" in data:
|
|
18
|
+
data["location"] = data.pop("name")
|
|
19
|
+
return data
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Service(BaseModel):
|
|
23
|
+
"""Configuration for a single service."""
|
|
24
|
+
|
|
25
|
+
source: ServiceSource | None = Field(None)
|
|
26
|
+
import_path: str | None = Field(None)
|
|
27
|
+
env: dict[str, str] | None = Field(None)
|
|
28
|
+
env_files: list[str] | None = Field(None)
|
|
29
|
+
python_dependencies: list[str] | None = Field(None)
|
|
30
|
+
|
|
31
|
+
@model_validator(mode="before")
|
|
32
|
+
@classmethod
|
|
33
|
+
def validate_fields(cls, data: Any) -> Any:
|
|
34
|
+
if isinstance(data, dict):
|
|
35
|
+
# Handle YAML aliases
|
|
36
|
+
if "path" in data:
|
|
37
|
+
data["import_path"] = data.pop("path")
|
|
38
|
+
if "import-path" in data:
|
|
39
|
+
data["import_path"] = data.pop("import-path")
|
|
40
|
+
if "env-files" in data:
|
|
41
|
+
data["env_files"] = data.pop("env-files")
|
|
42
|
+
|
|
43
|
+
return data
|
|
44
|
+
|
|
45
|
+
def module_location(self) -> tuple[str, str]:
|
|
46
|
+
"""
|
|
47
|
+
Parses the import path, and target, discarding legacy file path portion, if any
|
|
48
|
+
|
|
49
|
+
"src/module.workflow:my_workflow" -> ("module.workflow", "my_workflow")
|
|
50
|
+
"""
|
|
51
|
+
if self.import_path is None:
|
|
52
|
+
raise ValueError("import_path is required to compute module_location")
|
|
53
|
+
module_name, workflow_name = self.import_path.split(":")
|
|
54
|
+
return Path(module_name).name, workflow_name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class UIService(Service):
|
|
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):
|
|
65
|
+
"""Model definition mapping a deployment config file."""
|
|
66
|
+
|
|
67
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
68
|
+
|
|
69
|
+
name: str
|
|
70
|
+
default_service: str | None = Field(None)
|
|
71
|
+
services: dict[str, Service]
|
|
72
|
+
ui: UIService | None = None
|
|
73
|
+
|
|
74
|
+
@model_validator(mode="before")
|
|
75
|
+
@classmethod
|
|
76
|
+
def validate_fields(cls, data: Any) -> Any:
|
|
77
|
+
# Handle YAML aliases
|
|
78
|
+
if isinstance(data, dict):
|
|
79
|
+
if "default-service" in data:
|
|
80
|
+
data["default_service"] = data.pop("default-service")
|
|
81
|
+
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_yaml_bytes(cls, src: bytes) -> "DeploymentConfig":
|
|
86
|
+
"""Read config data from bytes containing yaml code."""
|
|
87
|
+
config = yaml.safe_load(src) or {}
|
|
88
|
+
return cls(**config)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_yaml(cls, path: Path, name: str | None = None) -> "DeploymentConfig":
|
|
92
|
+
"""Read config data from a yaml file."""
|
|
93
|
+
with open(path, "r", encoding="utf-8") as yaml_file:
|
|
94
|
+
config = yaml.safe_load(yaml_file) or {}
|
|
95
|
+
|
|
96
|
+
instance = cls(**config)
|
|
97
|
+
if name:
|
|
98
|
+
instance.name = name
|
|
99
|
+
return instance
|
{llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/git/git_util.py
RENAMED
|
@@ -3,11 +3,11 @@ Git utilities for the purpose of exploring, cloning, and parsing llama-deploy re
|
|
|
3
3
|
Responsibilities are lower level git access, as well as some application specific config parsing.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from dataclasses import dataclass
|
|
7
6
|
import re
|
|
8
7
|
import subprocess
|
|
9
|
-
from pathlib import Path
|
|
10
8
|
import tempfile
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
import yaml
|
|
13
13
|
|
|
@@ -203,7 +203,7 @@ def validate_deployment_file(repo_dir: Path, deployment_file_path: str) -> bool:
|
|
|
203
203
|
return False
|
|
204
204
|
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
def validate_git_public_access(repository_url: str) -> bool:
|
|
207
207
|
"""Check if a git repository is publicly accessible using git ls-remote."""
|
|
208
208
|
|
|
209
209
|
try:
|
|
@@ -219,7 +219,7 @@ async def validate_git_public_access(repository_url: str) -> bool:
|
|
|
219
219
|
return False
|
|
220
220
|
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
def validate_git_credential_access(repository_url: str, basic_auth: str) -> bool:
|
|
223
223
|
"""Check if a credential provides access to a git repository."""
|
|
224
224
|
|
|
225
225
|
auth_url = inject_basic_auth(repository_url, basic_auth)
|
|
@@ -234,3 +234,30 @@ async def validate_git_credential_access(repository_url: str, basic_auth: str) -
|
|
|
234
234
|
return result.returncode == 0
|
|
235
235
|
except (subprocess.TimeoutExpired, Exception):
|
|
236
236
|
return False
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def is_git_repo() -> bool:
|
|
240
|
+
"""
|
|
241
|
+
checks if the cwd is a git repo
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
_run_process(["git", "status"])
|
|
245
|
+
return True
|
|
246
|
+
except GitAccessError:
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def list_remotes() -> list[str]:
|
|
251
|
+
"""
|
|
252
|
+
list the remote urls for the current git repo
|
|
253
|
+
"""
|
|
254
|
+
result = _run_process(["git", "remote", "-v"])
|
|
255
|
+
return [line.split()[1] for line in result.splitlines()]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_current_branch() -> str | None:
|
|
259
|
+
"""
|
|
260
|
+
get the current branch for the current git repo
|
|
261
|
+
"""
|
|
262
|
+
result = _run_process(["git", "branch", "--show-current"])
|
|
263
|
+
return result.strip() if result.strip() else None
|
{llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/__init__.py
RENAMED
|
@@ -2,14 +2,14 @@ from .base import Base
|
|
|
2
2
|
from .deployments import (
|
|
3
3
|
DeploymentCreate,
|
|
4
4
|
DeploymentResponse,
|
|
5
|
-
DeploymentUpdate,
|
|
6
5
|
DeploymentsListResponse,
|
|
6
|
+
DeploymentUpdate,
|
|
7
|
+
LlamaDeploymentPhase,
|
|
7
8
|
LlamaDeploymentSpec,
|
|
8
9
|
apply_deployment_update,
|
|
9
|
-
LlamaDeploymentPhase,
|
|
10
10
|
)
|
|
11
|
-
from .git_validation import
|
|
12
|
-
from .projects import
|
|
11
|
+
from .git_validation import RepositoryValidationRequest, RepositoryValidationResponse
|
|
12
|
+
from .projects import ProjectsListResponse, ProjectSummary
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
15
|
"Base",
|
{llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/deployments.py
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
1
|
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Literal
|
|
3
4
|
|
|
4
5
|
from pydantic import HttpUrl
|
|
5
6
|
|
|
6
7
|
from .base import Base
|
|
7
8
|
|
|
8
|
-
|
|
9
9
|
# K8s CRD phase values
|
|
10
10
|
LlamaDeploymentPhase = Literal[
|
|
11
11
|
"Syncing", # Initial reconciliation phase - controller is processing the deployment
|
|
@@ -70,6 +70,8 @@ class LlamaDeploymentSpec(Base):
|
|
|
70
70
|
gitSha: str | None = None
|
|
71
71
|
name: str
|
|
72
72
|
secretName: str | None = None
|
|
73
|
+
# when true, the deployment will prebuild the UI assets and serve them from a static file server
|
|
74
|
+
staticAssetsPath: str | None = None
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
class LlamaDeploymentStatus(Base):
|
|
@@ -108,6 +110,7 @@ class DeploymentUpdate(Base):
|
|
|
108
110
|
git_sha: str | None = None
|
|
109
111
|
personal_access_token: str | None = None
|
|
110
112
|
secrets: dict[str, str | None] | None = None
|
|
113
|
+
static_assets_path: Path | None = None
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
class DeploymentUpdateResult(Base):
|
|
@@ -157,6 +160,12 @@ def apply_deployment_update(
|
|
|
157
160
|
if update.git_sha is not None:
|
|
158
161
|
updated_spec.gitSha = None if update.git_sha == "" else update.git_sha
|
|
159
162
|
|
|
163
|
+
# always apply this, as it should be cleared out if none, and is only set by the server
|
|
164
|
+
updated_spec.staticAssetsPath = (
|
|
165
|
+
str(update.static_assets_path) if update.static_assets_path else None
|
|
166
|
+
)
|
|
167
|
+
print(f"staticAssetsPath: {updated_spec.staticAssetsPath}")
|
|
168
|
+
|
|
160
169
|
# Track secret changes
|
|
161
170
|
secret_adds: dict[str, str] = {}
|
|
162
171
|
secret_removes: list[str] = []
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
from typing import Optional
|
|
3
|
+
|
|
2
4
|
from pydantic import BaseModel, Field
|
|
3
5
|
|
|
4
6
|
|
|
@@ -17,11 +19,11 @@ class RepositoryValidationResponse(BaseModel):
|
|
|
17
19
|
default=False,
|
|
18
20
|
description="True if validation succeeded via GitHub App for a deployment that previously used a PAT",
|
|
19
21
|
)
|
|
20
|
-
github_app_name:
|
|
22
|
+
github_app_name: str | None = Field(
|
|
21
23
|
default=None,
|
|
22
24
|
description="Name of the GitHub App if repository is a private GitHub repo and server has GitHub App configured",
|
|
23
25
|
)
|
|
24
|
-
github_app_installation_url:
|
|
26
|
+
github_app_installation_url: str | None = Field(
|
|
25
27
|
default=None,
|
|
26
28
|
description="GitHub App installation URL if repository is a private GitHub repo and server has GitHub App configured",
|
|
27
29
|
)
|
|
@@ -29,8 +31,8 @@ class RepositoryValidationResponse(BaseModel):
|
|
|
29
31
|
|
|
30
32
|
class RepositoryValidationRequest(BaseModel):
|
|
31
33
|
repository_url: str
|
|
32
|
-
deployment_id:
|
|
33
|
-
pat:
|
|
34
|
+
deployment_id: str | None = None
|
|
35
|
+
pat: str | None = None
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
class GitApplicationValidationResponse(BaseModel):
|
|
@@ -44,3 +46,7 @@ class GitApplicationValidationResponse(BaseModel):
|
|
|
44
46
|
git_ref: str | None = None
|
|
45
47
|
git_sha: str | None = None
|
|
46
48
|
valid_deployment_file_path: str | None = None
|
|
49
|
+
ui_build_output_path: Path | None = Field(
|
|
50
|
+
default=None,
|
|
51
|
+
description="Path to the UI build output, if the deployment's UI has a package.json with a build script; None if no UI is configured",
|
|
52
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
rel = config.ui.source.location if config.ui.source else "."
|
|
14
|
+
return (config_parent / rel).resolve()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def ui_build_output_path(config_parent: Path, config: DeploymentConfig) -> Path | None:
|
|
18
|
+
"""
|
|
19
|
+
Determine if the UI has a build script defined in package.json, and where the output will be.
|
|
20
|
+
Right now, assumes its just `/dist` in the UI root.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
- True if a package.json exists and contains a "build" script
|
|
24
|
+
- False if a package.json exists but lacks a "build" script
|
|
25
|
+
- None if there is no UI configured or no package.json exists
|
|
26
|
+
"""
|
|
27
|
+
ui_root = resolve_ui_root(config_parent, config)
|
|
28
|
+
if ui_root 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
|
+
scripts = pkg.get("scripts", {}) or {}
|
|
37
|
+
if "build" in scripts:
|
|
38
|
+
return (ui_root / "dist").relative_to(config_parent)
|
|
39
|
+
except Exception:
|
|
40
|
+
# Do not raise for malformed package.json in validation contexts
|
|
41
|
+
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/base.py
RENAMED
|
File without changes
|
{llama_deploy_core-0.3.0a4 → llama_deploy_core-0.3.0a5}/src/llama_deploy/core/schema/projects.py
RENAMED
|
File without changes
|