-
-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathloader.py
More file actions
64 lines (48 loc) · 2.32 KB
/
Copy pathloader.py
File metadata and controls
64 lines (48 loc) · 2.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""Load + validate packaged builtin tool TOMLs and user-authored "My Tools" TOMLs."""
from __future__ import annotations
import tomllib
from importlib import resources
from pathlib import Path
from pydantic import ValidationError
from gflow_cli.errors import ConfigurationError
from gflow_cli.tools.spec import ToolSpec
_BUILTIN_PACKAGE = "gflow_cli.tools.builtin"
def _parse(name: str, text: str) -> dict[str, object]:
# Wrap BOTH failure modes as ConfigurationError so spec §4.2 holds
# ("Invalid TOML → ConfigurationError") — syntactic (TOMLDecodeError) and
# schema (ValidationError, in _validate). (council D2 nice-to-have)
try:
return tomllib.loads(text)
except tomllib.TOMLDecodeError as exc:
raise ConfigurationError(detail=f"malformed TOML in tool {name!r}: {exc}") from exc
def _validate(name: str, data: dict[str, object]) -> ToolSpec:
try:
return ToolSpec.model_validate(data)
except ValidationError as exc:
raise ConfigurationError(detail=f"invalid tool definition {name!r}: {exc}") from exc
def load_builtin_tools() -> dict[str, ToolSpec]:
tools: dict[str, ToolSpec] = {}
root = resources.files(_BUILTIN_PACKAGE)
# Sorted for deterministic load order (stable across platforms / packagers).
for entry in sorted(root.iterdir(), key=lambda e: e.name):
if entry.name.endswith(".toml"):
label = Path(entry.name).stem # strip .toml for error labels
spec = _validate(label, _parse(label, entry.read_text(encoding="utf-8")))
tools[spec.name] = spec
return tools
def _load_dir(directory: Path) -> dict[str, ToolSpec]:
tools: dict[str, ToolSpec] = {}
for path in sorted(directory.glob("*.toml")):
spec = _validate(path.stem, _parse(path.stem, path.read_text(encoding="utf-8")))
tools[spec.name] = spec
return tools
def load_user_tools(config_dir: Path) -> dict[str, ToolSpec]:
"""Scan a user config dir for "My Tools" tool TOMLs.
Wired into the registry (``registry._registry`` layers these over the
packaged builtins; a same-named user tool overrides a builtin). Returns
``{}`` when the dir is absent. A malformed TOML fails loud
(``ConfigurationError``), like a malformed builtin.
"""
if not config_dir.exists():
return {}
return _load_dir(config_dir)