refactor: Update component specifications to use 'program' and 'behavior' attributes, enhancing clarity and consistency across the application
This commit is contained in:
@@ -53,11 +53,11 @@ class CastleConfig:
|
||||
|
||||
@property
|
||||
def tools(self) -> dict[str, ProgramSpec]:
|
||||
"""Return programs that are tools (have install.path or tool spec)."""
|
||||
"""Return programs that are tools (behavior == 'tool')."""
|
||||
return {
|
||||
k: v
|
||||
for k, v in self.programs.items()
|
||||
if (v.install and v.install.path) or v.tool
|
||||
if v.behavior == "tool"
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -180,9 +180,6 @@ def _clean_for_yaml(data: object, preserve_keys: set[str] | None = None) -> obje
|
||||
_STRUCTURAL_KEYS = {
|
||||
"manage",
|
||||
"systemd",
|
||||
"install",
|
||||
"path",
|
||||
"tool",
|
||||
"expose",
|
||||
"proxy",
|
||||
"caddy",
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import Annotated, Literal, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
EnvMap = dict[str, str]
|
||||
|
||||
@@ -38,7 +38,7 @@ class RunCommand(RunBase):
|
||||
|
||||
class RunPython(RunBase):
|
||||
runner: Literal["python"]
|
||||
tool: str
|
||||
program: str
|
||||
args: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
@@ -102,33 +102,6 @@ class ManageSpec(BaseModel):
|
||||
systemd: SystemdSpec | None = None
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Install (PATH shims)
|
||||
# ---------------------
|
||||
|
||||
|
||||
class PathInstallSpec(BaseModel):
|
||||
enable: bool = True
|
||||
alias: str | None = None
|
||||
shim: bool = True
|
||||
|
||||
|
||||
class InstallSpec(BaseModel):
|
||||
path: PathInstallSpec | None = None
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Tool spec
|
||||
# ---------------------
|
||||
|
||||
|
||||
class ToolSpec(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
version: str = "1.0.0"
|
||||
system_dependencies: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
# ---------------------
|
||||
# HTTP exposure + proxy
|
||||
# ---------------------
|
||||
@@ -206,12 +179,13 @@ class ProgramSpec(BaseModel):
|
||||
|
||||
id: str = ""
|
||||
description: str | None = None
|
||||
behavior: str | None = None
|
||||
|
||||
source: str | None = None
|
||||
stack: str | None = None
|
||||
|
||||
install: InstallSpec | None = None
|
||||
tool: ToolSpec | None = None
|
||||
system_dependencies: list[str] = Field(default_factory=list)
|
||||
version: str | None = None
|
||||
build: BuildSpec | None = None
|
||||
|
||||
provides: list[Capability] = Field(default_factory=list)
|
||||
|
||||
@@ -15,12 +15,10 @@ def castle_root(tmp_path: Path) -> Generator[Path, None, None]:
|
||||
castle_yaml = tmp_path / "castle.yaml"
|
||||
config = {
|
||||
"gateway": {"port": 18000},
|
||||
"components": {
|
||||
"programs": {
|
||||
"test-tool": {
|
||||
"description": "Test tool",
|
||||
"install": {
|
||||
"path": {"alias": "test-tool"},
|
||||
},
|
||||
"behavior": "tool",
|
||||
},
|
||||
},
|
||||
"services": {
|
||||
@@ -29,7 +27,7 @@ def castle_root(tmp_path: Path) -> Generator[Path, None, None]:
|
||||
"description": "Test service",
|
||||
"run": {
|
||||
"runner": "python",
|
||||
"tool": "test-svc",
|
||||
"program": "test-svc",
|
||||
},
|
||||
"defaults": {
|
||||
"env": {"TEST_SVC_DATA_DIR": str(tmp_path / "data" / "test-svc")},
|
||||
|
||||
@@ -51,7 +51,7 @@ class TestLoadConfig:
|
||||
config = load_config(castle_root)
|
||||
svc = config.services["test-svc"]
|
||||
assert svc.run.runner == "python"
|
||||
assert svc.run.tool == "test-svc"
|
||||
assert svc.run.program == "test-svc"
|
||||
|
||||
def test_service_component_ref(self, castle_root: Path) -> None:
|
||||
"""Service references a component."""
|
||||
|
||||
@@ -10,17 +10,14 @@ from castle_core.manifest import (
|
||||
ExposeSpec,
|
||||
HttpExposeSpec,
|
||||
HttpInternal,
|
||||
InstallSpec,
|
||||
JobSpec,
|
||||
ManageSpec,
|
||||
PathInstallSpec,
|
||||
ProxySpec,
|
||||
RunCommand,
|
||||
RunPython,
|
||||
RunRemote,
|
||||
ServiceSpec,
|
||||
SystemdSpec,
|
||||
ToolSpec,
|
||||
)
|
||||
|
||||
|
||||
@@ -32,26 +29,27 @@ class TestProgramSpec:
|
||||
c = ProgramSpec(id="bare")
|
||||
assert c.description is None
|
||||
assert c.source is None
|
||||
assert c.install is None
|
||||
assert c.tool is None
|
||||
assert c.behavior is None
|
||||
assert c.build is None
|
||||
|
||||
def test_tool_component(self) -> None:
|
||||
"""Component with tool and install specs."""
|
||||
"""Component with tool behavior and system_dependencies."""
|
||||
c = ProgramSpec(
|
||||
id="my-tool",
|
||||
description="A tool",
|
||||
source="my-tool/",
|
||||
tool=ToolSpec(),
|
||||
install=InstallSpec(path=PathInstallSpec(alias="my-tool")),
|
||||
behavior="tool",
|
||||
system_dependencies=["pandoc"],
|
||||
)
|
||||
assert c.source == "my-tool/"
|
||||
assert c.install.path.alias == "my-tool"
|
||||
assert c.behavior == "tool"
|
||||
assert c.system_dependencies == ["pandoc"]
|
||||
|
||||
def test_frontend_component(self) -> None:
|
||||
"""Component with build spec."""
|
||||
c = ProgramSpec(
|
||||
id="my-app",
|
||||
behavior="frontend",
|
||||
build=BuildSpec(commands=[["pnpm", "build"]], outputs=["dist/"]),
|
||||
)
|
||||
assert c.build.outputs == ["dist/"]
|
||||
@@ -74,7 +72,7 @@ class TestServiceSpec:
|
||||
"""Service with run and expose."""
|
||||
s = ServiceSpec(
|
||||
id="svc",
|
||||
run=RunPython(runner="python", tool="svc"),
|
||||
run=RunPython(runner="python", program="svc"),
|
||||
expose=ExposeSpec(http=HttpExposeSpec(internal=HttpInternal(port=8000))),
|
||||
)
|
||||
assert s.run.runner == "python"
|
||||
@@ -85,7 +83,7 @@ class TestServiceSpec:
|
||||
s = ServiceSpec(
|
||||
id="svc",
|
||||
component="my-component",
|
||||
run=RunPython(runner="python", tool="svc"),
|
||||
run=RunPython(runner="python", program="svc"),
|
||||
)
|
||||
assert s.component == "my-component"
|
||||
|
||||
@@ -93,7 +91,7 @@ class TestServiceSpec:
|
||||
"""Service with proxy spec."""
|
||||
s = ServiceSpec(
|
||||
id="svc",
|
||||
run=RunPython(runner="python", tool="svc"),
|
||||
run=RunPython(runner="python", program="svc"),
|
||||
proxy=ProxySpec(caddy=CaddySpec(path_prefix="/svc")),
|
||||
)
|
||||
assert s.proxy.caddy.path_prefix == "/svc"
|
||||
@@ -174,15 +172,14 @@ class TestModelSerialization:
|
||||
c = ProgramSpec(id="test", description="Test")
|
||||
data = c.model_dump(exclude_none=True, exclude={"id"})
|
||||
assert "description" in data
|
||||
assert "install" not in data
|
||||
assert "tool" not in data
|
||||
assert "build" not in data
|
||||
|
||||
def test_dump_service(self) -> None:
|
||||
"""Full service serializes correctly."""
|
||||
s = ServiceSpec(
|
||||
id="svc",
|
||||
description="A service",
|
||||
run=RunPython(runner="python", tool="svc"),
|
||||
run=RunPython(runner="python", program="svc"),
|
||||
expose=ExposeSpec(
|
||||
http=HttpExposeSpec(
|
||||
internal=HttpInternal(port=9001), health_path="/health"
|
||||
|
||||
Reference in New Issue
Block a user