llama-deploy-core 0.3.0a4__py3-none-any.whl → 0.3.0a5__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,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
@@ -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
- async def validate_git_public_access(repository_url: str) -> bool:
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
- async def validate_git_credential_access(repository_url: str, basic_auth: str) -> bool:
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
@@ -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 RepositoryValidationResponse, RepositoryValidationRequest
12
- from .projects import ProjectSummary, ProjectsListResponse
11
+ from .git_validation import RepositoryValidationRequest, RepositoryValidationResponse
12
+ from .projects import ProjectsListResponse, ProjectSummary
13
13
 
14
14
  __all__ = [
15
15
  "Base",
@@ -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: Optional[str] = Field(
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: Optional[str] = Field(
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: Optional[str] = None
33
- pat: Optional[str] = None
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llama-deploy-core
3
- Version: 0.3.0a4
3
+ Version: 0.3.0a5
4
4
  Summary: Core models and schemas for LlamaDeploy
5
5
  License: MIT
6
6
  Requires-Dist: pydantic>=2.0.0
@@ -0,0 +1,13 @@
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.0a5.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
12
+ llama_deploy_core-0.3.0a5.dist-info/METADATA,sha256=7badf0c1c0175d07d64231391802bc466fd47e5ef691f7374c03bf675a362def,402
13
+ llama_deploy_core-0.3.0a5.dist-info/RECORD,,
@@ -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=be9069cd1a5006266b75264505a0e3052fc155e876aa0ba76ec30672e6192b45,8381
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.3.0a4.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
10
- llama_deploy_core-0.3.0a4.dist-info/METADATA,sha256=13aebeebb1e192d4af1887d890adc3f913b0ec4ff8e66ab60aebf1b9e0af1ae1,402
11
- llama_deploy_core-0.3.0a4.dist-info/RECORD,,