llama-deploy-appserver 0.2.7a1__tar.gz → 0.3.0a1__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_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/PKG-INFO +3 -6
- {llama_deploy_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/pyproject.toml +10 -6
- {llama_deploy_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/src/llama_deploy/appserver/__main__.py +0 -4
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/app.py +129 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/bootstrap.py +95 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/deployment.py +81 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/deployment_config_parser.py +109 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/routers/__init__.py +5 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/routers/deployments.py +210 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/routers/status.py +13 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/routers/ui_proxy.py +213 -0
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/settings.py +85 -0
- {llama_deploy_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/src/llama_deploy/appserver/types.py +0 -3
- llama_deploy_appserver-0.3.0a1/src/llama_deploy/appserver/workflow_loader.py +383 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/app.py +0 -49
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/bootstrap.py +0 -43
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/__init__.py +0 -3
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/base.py +0 -30
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/client.py +0 -49
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/models/__init__.py +0 -4
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/models/apiserver.py +0 -356
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/models/model.py +0 -82
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/deployment.py +0 -495
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/deployment_config_parser.py +0 -133
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/routers/__init__.py +0 -4
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/routers/deployments.py +0 -433
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/routers/status.py +0 -40
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/run_autodeploy.py +0 -141
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/server.py +0 -60
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/settings.py +0 -83
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/__init__.py +0 -5
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/base.py +0 -33
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/git.py +0 -48
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/local.py +0 -51
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/tracing.py +0 -237
- {llama_deploy_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/README.md +0 -0
- {llama_deploy_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/src/llama_deploy/appserver/__init__.py +0 -0
- {llama_deploy_appserver-0.2.7a1 → llama_deploy_appserver-0.3.0a1}/src/llama_deploy/appserver/stats.py +0 -0
@@ -1,21 +1,18 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: llama-deploy-appserver
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0a1
|
4
4
|
Summary: Application server components for LlamaDeploy
|
5
5
|
Author: Massimiliano Pippi
|
6
6
|
Author-email: Massimiliano Pippi <mpippi@gmail.com>
|
7
7
|
License: MIT
|
8
|
-
Requires-Dist: asgiref>=3.9.1
|
9
8
|
Requires-Dist: llama-index-workflows>=1.1.0
|
10
9
|
Requires-Dist: pydantic-settings>=2.10.1
|
11
10
|
Requires-Dist: uvicorn>=0.24.0
|
12
|
-
Requires-Dist: prometheus-client>=0.20.0
|
13
|
-
Requires-Dist: python-multipart>=0.0.18,<0.0.19
|
14
11
|
Requires-Dist: fastapi>=0.100.0
|
15
12
|
Requires-Dist: websockets>=12.0
|
16
|
-
Requires-Dist:
|
17
|
-
Requires-Dist: llama-deploy-core>=0.2.7a1,<0.3.0
|
13
|
+
Requires-Dist: llama-deploy-core>=0.3.0a1,<0.4.0
|
18
14
|
Requires-Dist: httpx>=0.28.1
|
15
|
+
Requires-Dist: prometheus-fastapi-instrumentator>=7.1.0
|
19
16
|
Requires-Python: >=3.12, <4
|
20
17
|
Description-Content-Type: text/markdown
|
21
18
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "llama-deploy-appserver"
|
3
|
-
version = "0.
|
3
|
+
version = "0.3.0a1"
|
4
4
|
description = "Application server components for LlamaDeploy"
|
5
5
|
readme = "README.md"
|
6
6
|
license = { text = "MIT" }
|
@@ -9,17 +9,14 @@ authors = [
|
|
9
9
|
]
|
10
10
|
requires-python = ">=3.12, <4"
|
11
11
|
dependencies = [
|
12
|
-
"asgiref>=3.9.1",
|
13
12
|
"llama-index-workflows>=1.1.0",
|
14
13
|
"pydantic-settings>=2.10.1",
|
15
14
|
"uvicorn>=0.24.0",
|
16
|
-
"prometheus-client>=0.20.0",
|
17
|
-
"python-multipart>=0.0.18,<0.0.19",
|
18
15
|
"fastapi>=0.100.0",
|
19
16
|
"websockets>=12.0",
|
20
|
-
"
|
21
|
-
"llama-deploy-core>=0.2.7a1,<0.3.0",
|
17
|
+
"llama-deploy-core>=0.3.0a1,<0.4.0",
|
22
18
|
"httpx>=0.28.1",
|
19
|
+
"prometheus-fastapi-instrumentator>=7.1.0",
|
23
20
|
]
|
24
21
|
|
25
22
|
[build-system]
|
@@ -32,3 +29,10 @@ module-name = "llama_deploy.appserver"
|
|
32
29
|
|
33
30
|
[tool.uv.sources]
|
34
31
|
llama-deploy-core = { workspace = true }
|
32
|
+
|
33
|
+
[dependency-groups]
|
34
|
+
dev = [
|
35
|
+
"pytest>=8.4.1",
|
36
|
+
"pytest-asyncio>=0.25.3",
|
37
|
+
"respx>=0.22.0",
|
38
|
+
]
|
@@ -1,12 +1,8 @@
|
|
1
1
|
import uvicorn
|
2
|
-
from prometheus_client import start_http_server
|
3
2
|
|
4
3
|
from .settings import settings
|
5
4
|
|
6
5
|
if __name__ == "__main__":
|
7
|
-
if settings.prometheus_enabled:
|
8
|
-
start_http_server(settings.prometheus_port)
|
9
|
-
|
10
6
|
uvicorn.run(
|
11
7
|
"llama_deploy.appserver.app:app",
|
12
8
|
host=settings.host,
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
from pathlib import Path
|
4
|
+
import threading
|
5
|
+
import time
|
6
|
+
from llama_deploy.appserver.deployment_config_parser import (
|
7
|
+
get_deployment_config,
|
8
|
+
)
|
9
|
+
from llama_deploy.appserver.settings import configure_settings, settings
|
10
|
+
|
11
|
+
from fastapi import FastAPI
|
12
|
+
from fastapi.middleware.cors import CORSMiddleware
|
13
|
+
from llama_deploy.appserver.workflow_loader import (
|
14
|
+
build_ui,
|
15
|
+
do_install,
|
16
|
+
start_dev_ui_process,
|
17
|
+
)
|
18
|
+
import uvicorn
|
19
|
+
|
20
|
+
from .routers import health_router
|
21
|
+
from prometheus_fastapi_instrumentator import Instrumentator
|
22
|
+
from contextlib import asynccontextmanager
|
23
|
+
from typing import Any, AsyncGenerator
|
24
|
+
|
25
|
+
from llama_deploy.appserver.routers.deployments import (
|
26
|
+
create_base_router,
|
27
|
+
create_deployments_router,
|
28
|
+
)
|
29
|
+
from llama_deploy.appserver.routers.ui_proxy import (
|
30
|
+
create_ui_proxy_router,
|
31
|
+
mount_static_files,
|
32
|
+
)
|
33
|
+
from llama_deploy.appserver.workflow_loader import (
|
34
|
+
load_workflows,
|
35
|
+
)
|
36
|
+
|
37
|
+
from .deployment import Deployment
|
38
|
+
from .stats import apiserver_state
|
39
|
+
import webbrowser
|
40
|
+
|
41
|
+
logger = logging.getLogger("uvicorn.info")
|
42
|
+
|
43
|
+
|
44
|
+
@asynccontextmanager
|
45
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, Any]:
|
46
|
+
apiserver_state.state("starting")
|
47
|
+
|
48
|
+
config = get_deployment_config()
|
49
|
+
|
50
|
+
workflows = load_workflows(config)
|
51
|
+
deployment = Deployment(workflows)
|
52
|
+
base_router = create_base_router(config.name)
|
53
|
+
deploy_router = create_deployments_router(config.name, deployment)
|
54
|
+
app.include_router(base_router)
|
55
|
+
app.include_router(deploy_router)
|
56
|
+
# proxy UI in dev mode
|
57
|
+
if config.ui is not None:
|
58
|
+
if settings.proxy_ui:
|
59
|
+
ui_router = create_ui_proxy_router(config.name, config.ui.port)
|
60
|
+
app.include_router(ui_router)
|
61
|
+
else:
|
62
|
+
# otherwise serve the pre-built if available
|
63
|
+
mount_static_files(app, config, settings)
|
64
|
+
|
65
|
+
apiserver_state.state("running")
|
66
|
+
yield
|
67
|
+
|
68
|
+
apiserver_state.state("stopped")
|
69
|
+
|
70
|
+
|
71
|
+
app = FastAPI(lifespan=lifespan)
|
72
|
+
Instrumentator().instrument(app).expose(app)
|
73
|
+
|
74
|
+
# Configure CORS middleware if the environment variable is set
|
75
|
+
if not os.environ.get("DISABLE_CORS", False):
|
76
|
+
app.add_middleware(
|
77
|
+
CORSMiddleware,
|
78
|
+
allow_origins=["*"], # Allows all origins
|
79
|
+
allow_credentials=True,
|
80
|
+
allow_methods=["GET", "POST"],
|
81
|
+
allow_headers=["Content-Type", "Authorization"],
|
82
|
+
)
|
83
|
+
|
84
|
+
app.include_router(health_router)
|
85
|
+
|
86
|
+
|
87
|
+
def start_server(
|
88
|
+
proxy_ui: bool = False,
|
89
|
+
reload: bool = False,
|
90
|
+
cwd: Path | None = None,
|
91
|
+
deployment_file: Path | None = None,
|
92
|
+
install: bool = False,
|
93
|
+
build: bool = False,
|
94
|
+
open_browser: bool = False,
|
95
|
+
) -> None:
|
96
|
+
# Configure via environment so uvicorn reload workers inherit the values
|
97
|
+
configure_settings(
|
98
|
+
proxy_ui=proxy_ui, app_root=cwd, deployment_file_path=deployment_file
|
99
|
+
)
|
100
|
+
if install:
|
101
|
+
do_install()
|
102
|
+
if build:
|
103
|
+
build_ui(settings.config_parent, get_deployment_config())
|
104
|
+
|
105
|
+
ui_process = None
|
106
|
+
if proxy_ui:
|
107
|
+
ui_process = start_dev_ui_process(
|
108
|
+
settings.config_parent, settings.port, get_deployment_config()
|
109
|
+
)
|
110
|
+
try:
|
111
|
+
if open_browser:
|
112
|
+
|
113
|
+
def open_with_delay():
|
114
|
+
time.sleep(1)
|
115
|
+
webbrowser.open(f"http://{settings.host}:{settings.port}")
|
116
|
+
|
117
|
+
threading.Thread(
|
118
|
+
target=open_with_delay,
|
119
|
+
).start()
|
120
|
+
|
121
|
+
uvicorn.run(
|
122
|
+
"llama_deploy.appserver.app:app",
|
123
|
+
host=settings.host,
|
124
|
+
port=settings.port,
|
125
|
+
reload=reload,
|
126
|
+
)
|
127
|
+
finally:
|
128
|
+
if ui_process is not None:
|
129
|
+
ui_process.terminate()
|
@@ -0,0 +1,95 @@
|
|
1
|
+
"""
|
2
|
+
Bootstraps an application from a remote github repository given environment variables.
|
3
|
+
|
4
|
+
This just sets up the files from the repository. It's more of a build process, does not start an application.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
from llama_deploy.appserver.settings import settings
|
10
|
+
from llama_deploy.appserver.deployment_config_parser import get_deployment_config
|
11
|
+
from llama_deploy.appserver.workflow_loader import build_ui, do_install
|
12
|
+
from llama_deploy.core.git.git_util import (
|
13
|
+
clone_repo,
|
14
|
+
)
|
15
|
+
from llama_deploy.appserver.app import start_server
|
16
|
+
from llama_deploy.appserver.settings import BootstrapSettings, configure_settings
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
|
20
|
+
|
21
|
+
def bootstrap_app_from_repo(
|
22
|
+
clone: bool = False,
|
23
|
+
build: bool = False,
|
24
|
+
serve: bool = False,
|
25
|
+
target_dir: str = "/opt/app/",
|
26
|
+
):
|
27
|
+
bootstrap_settings = BootstrapSettings()
|
28
|
+
# Needs the github url+auth, and the deployment file path
|
29
|
+
# clones the repo to a standard directory
|
30
|
+
# (eventually) runs the UI build process and moves that to a standard directory for a file server
|
31
|
+
if clone:
|
32
|
+
repo_url = bootstrap_settings.repo_url
|
33
|
+
if repo_url is None:
|
34
|
+
raise ValueError("repo_url is required to bootstrap")
|
35
|
+
clone_repo(
|
36
|
+
repository_url=repo_url,
|
37
|
+
git_ref=bootstrap_settings.git_sha or bootstrap_settings.git_ref,
|
38
|
+
basic_auth=bootstrap_settings.auth_token,
|
39
|
+
dest_dir=target_dir,
|
40
|
+
)
|
41
|
+
# Ensure target_dir exists locally when running tests outside a container
|
42
|
+
os.makedirs(target_dir, exist_ok=True)
|
43
|
+
os.chdir(target_dir)
|
44
|
+
configure_settings(
|
45
|
+
app_root=Path(target_dir),
|
46
|
+
deployment_file_path=Path(bootstrap_settings.deployment_file_path),
|
47
|
+
)
|
48
|
+
|
49
|
+
built = True
|
50
|
+
if build:
|
51
|
+
do_install()
|
52
|
+
built = build_ui(settings.config_parent, get_deployment_config())
|
53
|
+
|
54
|
+
if serve:
|
55
|
+
start_server(
|
56
|
+
proxy_ui=not built,
|
57
|
+
)
|
58
|
+
pass
|
59
|
+
|
60
|
+
|
61
|
+
if __name__ == "__main__":
|
62
|
+
parser = argparse.ArgumentParser()
|
63
|
+
parser.add_argument(
|
64
|
+
"--clone",
|
65
|
+
action=argparse.BooleanOptionalAction,
|
66
|
+
default=False,
|
67
|
+
help="Clone the repository before bootstrapping (use --no-clone to disable)",
|
68
|
+
)
|
69
|
+
parser.add_argument(
|
70
|
+
"--build",
|
71
|
+
action=argparse.BooleanOptionalAction,
|
72
|
+
default=False,
|
73
|
+
help="Build the UI/assets (use --no-build to disable)",
|
74
|
+
)
|
75
|
+
parser.add_argument(
|
76
|
+
"--serve",
|
77
|
+
action=argparse.BooleanOptionalAction,
|
78
|
+
default=False,
|
79
|
+
help="Start the API server after bootstrap (use --no-serve to disable)",
|
80
|
+
)
|
81
|
+
args = parser.parse_args()
|
82
|
+
try:
|
83
|
+
bootstrap_app_from_repo(
|
84
|
+
clone=args.clone,
|
85
|
+
build=args.build,
|
86
|
+
serve=args.serve,
|
87
|
+
)
|
88
|
+
except Exception as e:
|
89
|
+
import logging
|
90
|
+
|
91
|
+
logging.exception("Error during bootstrap. Pausing for debugging.")
|
92
|
+
import time
|
93
|
+
|
94
|
+
time.sleep(1000000)
|
95
|
+
raise e
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import asyncio
|
2
|
+
import json
|
3
|
+
import logging
|
4
|
+
from typing import Any, Tuple
|
5
|
+
|
6
|
+
from llama_deploy.appserver.types import generate_id
|
7
|
+
from llama_deploy.appserver.workflow_loader import DEFAULT_SERVICE_ID
|
8
|
+
from workflows import Context, Workflow
|
9
|
+
from workflows.handler import WorkflowHandler
|
10
|
+
|
11
|
+
|
12
|
+
logger = logging.getLogger()
|
13
|
+
|
14
|
+
|
15
|
+
class DeploymentError(Exception): ...
|
16
|
+
|
17
|
+
|
18
|
+
class Deployment:
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
workflows: dict[str, Workflow],
|
22
|
+
) -> None:
|
23
|
+
"""Creates a Deployment instance.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
config: The configuration object defining this deployment
|
27
|
+
root_path: The path on the filesystem used to store deployment data
|
28
|
+
local: Whether the deployment is local. If true, sources won't be synced
|
29
|
+
"""
|
30
|
+
|
31
|
+
self._default_service: str | None = workflows.get(DEFAULT_SERVICE_ID)
|
32
|
+
self._service_tasks: list[asyncio.Task] = []
|
33
|
+
# Ready to load services
|
34
|
+
self._workflow_services: dict[str, Workflow] = workflows
|
35
|
+
self._contexts: dict[str, Context] = {}
|
36
|
+
self._handlers: dict[str, WorkflowHandler] = {}
|
37
|
+
self._handler_inputs: dict[str, str] = {}
|
38
|
+
|
39
|
+
@property
|
40
|
+
def default_service(self) -> Workflow | None:
|
41
|
+
return self._default_service
|
42
|
+
|
43
|
+
@property
|
44
|
+
def name(self) -> str:
|
45
|
+
"""Returns the name of this deployment."""
|
46
|
+
return self._name
|
47
|
+
|
48
|
+
@property
|
49
|
+
def service_names(self) -> list[str]:
|
50
|
+
"""Returns the list of service names in this deployment."""
|
51
|
+
return list(self._workflow_services.keys())
|
52
|
+
|
53
|
+
async def run_workflow(
|
54
|
+
self, service_id: str, session_id: str | None = None, **run_kwargs: dict
|
55
|
+
) -> Any:
|
56
|
+
workflow = self._workflow_services[service_id]
|
57
|
+
if session_id:
|
58
|
+
context = self._contexts[session_id]
|
59
|
+
return await workflow.run(context=context, **run_kwargs)
|
60
|
+
|
61
|
+
if run_kwargs:
|
62
|
+
return await workflow.run(**run_kwargs)
|
63
|
+
|
64
|
+
return await workflow.run()
|
65
|
+
|
66
|
+
def run_workflow_no_wait(
|
67
|
+
self, service_id: str, session_id: str | None = None, **run_kwargs: dict
|
68
|
+
) -> Tuple[str, str]:
|
69
|
+
workflow = self._workflow_services[service_id]
|
70
|
+
if session_id:
|
71
|
+
context = self._contexts[session_id]
|
72
|
+
handler = workflow.run(context=context, **run_kwargs)
|
73
|
+
else:
|
74
|
+
handler = workflow.run(**run_kwargs)
|
75
|
+
session_id = generate_id()
|
76
|
+
self._contexts[session_id] = handler.ctx or Context(workflow)
|
77
|
+
|
78
|
+
handler_id = generate_id()
|
79
|
+
self._handlers[handler_id] = handler
|
80
|
+
self._handler_inputs[handler_id] = json.dumps(run_kwargs)
|
81
|
+
return handler_id, session_id
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import functools
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
|
6
|
+
from llama_deploy.appserver.settings import settings, BootstrapSettings
|
7
|
+
import yaml
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
9
|
+
|
10
|
+
|
11
|
+
class ServiceSource(BaseModel):
|
12
|
+
"""Configuration for where to load the workflow or other source. Path is relative to the config file its declared within."""
|
13
|
+
|
14
|
+
location: str
|
15
|
+
|
16
|
+
@model_validator(mode="before")
|
17
|
+
@classmethod
|
18
|
+
def validate_fields(cls, data: Any) -> Any:
|
19
|
+
if isinstance(data, dict):
|
20
|
+
if "name" in data:
|
21
|
+
data["location"] = data.pop("name")
|
22
|
+
return data
|
23
|
+
|
24
|
+
|
25
|
+
class Service(BaseModel):
|
26
|
+
"""Configuration for a single service."""
|
27
|
+
|
28
|
+
source: ServiceSource | None = Field(None)
|
29
|
+
import_path: str | None = Field(None)
|
30
|
+
env: dict[str, str] | None = Field(None)
|
31
|
+
env_files: list[str] | None = Field(None)
|
32
|
+
python_dependencies: list[str] | None = Field(None)
|
33
|
+
|
34
|
+
@model_validator(mode="before")
|
35
|
+
@classmethod
|
36
|
+
def validate_fields(cls, data: Any) -> Any:
|
37
|
+
if isinstance(data, dict):
|
38
|
+
# Handle YAML aliases
|
39
|
+
if "path" in data:
|
40
|
+
data["import_path"] = data.pop("path")
|
41
|
+
if "import-path" in data:
|
42
|
+
data["import_path"] = data.pop("import-path")
|
43
|
+
if "env-files" in data:
|
44
|
+
data["env_files"] = data.pop("env-files")
|
45
|
+
|
46
|
+
return data
|
47
|
+
|
48
|
+
def module_location(self) -> tuple[str, str]:
|
49
|
+
"""
|
50
|
+
Parses the import path, and target, discarding legacy file path portion, if any
|
51
|
+
|
52
|
+
"src/module.workflow:my_workflow" -> ("module.workflow", "my_workflow")
|
53
|
+
"""
|
54
|
+
module_name, workflow_name = self.import_path.split(":")
|
55
|
+
return Path(module_name).name, workflow_name
|
56
|
+
|
57
|
+
|
58
|
+
class UIService(Service):
|
59
|
+
port: int = Field(
|
60
|
+
default=3000,
|
61
|
+
description="The TCP port to use for the nextjs server",
|
62
|
+
)
|
63
|
+
|
64
|
+
|
65
|
+
class DeploymentConfig(BaseModel):
|
66
|
+
"""Model definition mapping a deployment config file."""
|
67
|
+
|
68
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
69
|
+
|
70
|
+
name: str
|
71
|
+
default_service: str | None = Field(None)
|
72
|
+
services: dict[str, Service]
|
73
|
+
ui: UIService | None = None
|
74
|
+
|
75
|
+
@model_validator(mode="before")
|
76
|
+
@classmethod
|
77
|
+
def validate_fields(cls, data: Any) -> Any:
|
78
|
+
# Handle YAML aliases
|
79
|
+
if isinstance(data, dict):
|
80
|
+
if "default-service" in data:
|
81
|
+
data["default_service"] = data.pop("default-service")
|
82
|
+
|
83
|
+
return data
|
84
|
+
|
85
|
+
@classmethod
|
86
|
+
def from_yaml_bytes(cls, src: bytes) -> "DeploymentConfig":
|
87
|
+
"""Read config data from bytes containing yaml code."""
|
88
|
+
config = yaml.safe_load(src) or {}
|
89
|
+
return cls(**config)
|
90
|
+
|
91
|
+
@classmethod
|
92
|
+
def from_yaml(cls, path: Path, name: str | None = None) -> "DeploymentConfig":
|
93
|
+
"""Read config data from a yaml file."""
|
94
|
+
with open(path, "r") as yaml_file:
|
95
|
+
config = yaml.safe_load(yaml_file) or {}
|
96
|
+
|
97
|
+
instance = cls(**config)
|
98
|
+
if name:
|
99
|
+
instance.name = name
|
100
|
+
return instance
|
101
|
+
|
102
|
+
|
103
|
+
@functools.lru_cache
|
104
|
+
def get_deployment_config() -> DeploymentConfig:
|
105
|
+
base_settings = BootstrapSettings()
|
106
|
+
base = settings.app_root.resolve()
|
107
|
+
yaml_file = base / settings.deployment_file_path
|
108
|
+
name = base_settings.deployment_name
|
109
|
+
return DeploymentConfig.from_yaml(yaml_file, name)
|