feat(activity): multi-service config, make help default, install-cli
Bare make lists targets. make install-cli installs the activity tool via uv. Named activity-core backends live in ~/.config/activity/services.json with list/add/use/default/which; -s/--service selects one call without changing the default; --activity-url is a one-shot override.
This commit is contained in:
parent
9f4993d9e7
commit
192942244e
8 changed files with 755 additions and 49 deletions
|
|
@ -19,8 +19,10 @@ from activity_core.review_cli.merge import (
|
|||
summarize_runs,
|
||||
)
|
||||
from activity_core.review_cli.repo import resolve_repo
|
||||
from activity_core.review_cli import services as svc_mod
|
||||
from activity_core.review_cli.sources import (
|
||||
activity_core_url,
|
||||
apply_endpoint_overrides,
|
||||
fetch_hub_progress,
|
||||
fetch_ops_automations,
|
||||
fetch_ops_runs_for_automation,
|
||||
|
|
@ -530,6 +532,13 @@ def cmd_status(args: argparse.Namespace) -> int:
|
|||
"Sources: "
|
||||
+ " ".join(f"{k}={v}" for k, v in sorted(sources.items()))
|
||||
)
|
||||
ep = getattr(args, "_endpoints", None) or {}
|
||||
if ep:
|
||||
print(
|
||||
f"Service: {ep.get('service') or '(none)'} "
|
||||
f"(source={ep.get('source')}) "
|
||||
f"api={ep.get('activity_core_url') or '(unset)'}"
|
||||
)
|
||||
for w in ctx["warnings"]:
|
||||
print(f"warn: {w}", file=sys.stderr)
|
||||
if run_summary.get("failed"):
|
||||
|
|
@ -569,6 +578,152 @@ def cmd_checkpoint(args: argparse.Namespace) -> int:
|
|||
return 2
|
||||
|
||||
|
||||
def cmd_service(args: argparse.Namespace) -> int:
|
||||
"""Manage named activity-core service endpoints."""
|
||||
action = args.service_action
|
||||
cfg_path = Path(args.config_dir).expanduser() if args.config_dir else None
|
||||
path = svc_mod.config_path(cfg_path) if cfg_path else None
|
||||
|
||||
try:
|
||||
if action == "list":
|
||||
cfg = svc_mod.load_config(path)
|
||||
if args.format == "json":
|
||||
_print_json(
|
||||
{
|
||||
"config_path": str(path or svc_mod.config_path()),
|
||||
"default": cfg.get("default"),
|
||||
"services": cfg.get("services") or {},
|
||||
}
|
||||
)
|
||||
return 0
|
||||
services = cfg.get("services") or {}
|
||||
default = cfg.get("default")
|
||||
print(f"config: {path or svc_mod.config_path()}")
|
||||
if not services:
|
||||
print("(no services configured)")
|
||||
print(
|
||||
" activity service add railiance "
|
||||
"--url https://activity.coulomb.social"
|
||||
)
|
||||
return 0
|
||||
print(f"default: {default or '(none)'}")
|
||||
for name, svc in sorted(services.items()):
|
||||
mark = "*" if name == default else " "
|
||||
desc = svc.get("description") or ""
|
||||
print(f" {mark} {name}")
|
||||
print(f" activity_core_url: {svc.get('activity_core_url')}")
|
||||
if svc.get("state_hub_url"):
|
||||
print(f" state_hub_url: {svc.get('state_hub_url')}")
|
||||
if desc:
|
||||
print(f" description: {desc}")
|
||||
return 0
|
||||
|
||||
if action == "show":
|
||||
name = args.name or svc_mod.load_config(path).get("default")
|
||||
if not name:
|
||||
print("no service name and no default set", file=sys.stderr)
|
||||
return 2
|
||||
svc = svc_mod.get_service(name, path)
|
||||
if not svc:
|
||||
print(f"unknown service {name!r}", file=sys.stderr)
|
||||
return 2
|
||||
report = {"name": name, **svc, "is_default": name == svc_mod.load_config(path).get("default")}
|
||||
if args.format == "json":
|
||||
_print_json(report)
|
||||
else:
|
||||
print(f"service: {name}")
|
||||
for k, v in svc.items():
|
||||
print(f" {k}: {v}")
|
||||
return 0
|
||||
|
||||
if action == "add":
|
||||
if not args.name or not args.url:
|
||||
print("usage: activity service add NAME --url URL [--hub-url URL]", file=sys.stderr)
|
||||
return 2
|
||||
cfg = svc_mod.add_service(
|
||||
args.name,
|
||||
activity_core_url=args.url,
|
||||
state_hub_url=args.hub_url,
|
||||
description=args.description or "",
|
||||
make_default=bool(args.make_default),
|
||||
path=path,
|
||||
)
|
||||
if args.format == "json":
|
||||
_print_json(cfg)
|
||||
else:
|
||||
print(f"added service {args.name!r}")
|
||||
print(f" activity_core_url={args.url}")
|
||||
if args.hub_url:
|
||||
print(f" state_hub_url={args.hub_url}")
|
||||
print(f" default={cfg.get('default')}")
|
||||
print(f" config={path or svc_mod.config_path()}")
|
||||
return 0
|
||||
|
||||
if action == "remove":
|
||||
if not args.name:
|
||||
print("usage: activity service remove NAME", file=sys.stderr)
|
||||
return 2
|
||||
cfg = svc_mod.remove_service(args.name, path)
|
||||
print(f"removed {args.name!r}; default={cfg.get('default')}")
|
||||
return 0
|
||||
|
||||
if action == "use":
|
||||
if not args.name:
|
||||
print("usage: activity service use NAME", file=sys.stderr)
|
||||
return 2
|
||||
cfg = svc_mod.set_default(args.name, path)
|
||||
print(f"default service → {cfg.get('default')}")
|
||||
return 0
|
||||
|
||||
if action == "default":
|
||||
cfg = svc_mod.load_config(path)
|
||||
name = cfg.get("default")
|
||||
if args.format == "json":
|
||||
_print_json(
|
||||
{
|
||||
"default": name,
|
||||
"service": cfg["services"].get(name) if name else None,
|
||||
}
|
||||
)
|
||||
else:
|
||||
if not name:
|
||||
print("default: (none)")
|
||||
else:
|
||||
print(f"default: {name}")
|
||||
svc = cfg["services"].get(name) or {}
|
||||
print(f" activity_core_url: {svc.get('activity_core_url')}")
|
||||
return 0
|
||||
|
||||
if action == "which":
|
||||
# Show what this invocation would use (after flags)
|
||||
try:
|
||||
ep = svc_mod.resolve_endpoints(
|
||||
service=getattr(args, "service", None),
|
||||
activity_url=getattr(args, "activity_url", None),
|
||||
hub_url=getattr(args, "hub_url", None),
|
||||
config_path_override=path,
|
||||
)
|
||||
except KeyError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if args.format == "json":
|
||||
_print_json(ep)
|
||||
else:
|
||||
print(f"source: {ep['source']}")
|
||||
print(f"service: {ep.get('service') or '(none)'}")
|
||||
print(f"activity_core_url: {ep.get('activity_core_url') or '(unset)'}")
|
||||
print(f"state_hub_url: {ep.get('state_hub_url') or '(unset)'}")
|
||||
print(f"config: {ep.get('config_path')}")
|
||||
return 0
|
||||
|
||||
except (ValueError, KeyError) as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print("usage: activity service list|show|add|remove|use|default|which", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
def cmd_ack(args: argparse.Namespace) -> int:
|
||||
repo = resolve_repo(cwd=Path(args.cwd) if args.cwd else None, explicit=args.repo)
|
||||
state_dir = Path(args.state_dir).expanduser() if args.state_dir else None
|
||||
|
|
@ -617,19 +772,15 @@ def cmd_ack(args: argparse.Namespace) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser(
|
||||
prog="activity",
|
||||
description=(
|
||||
"Repo-scoped automation review CLI (ACTIVITY-WP-0028). "
|
||||
"Run from a consumer repo (e.g. freedom-intelligence)."
|
||||
),
|
||||
)
|
||||
def _common_parent() -> argparse.ArgumentParser:
|
||||
"""Shared flags available on every subcommand (before or after the verb)."""
|
||||
p = argparse.ArgumentParser(add_help=False)
|
||||
p.add_argument("--repo", help="Repo slug (default: auto-detect from cwd)")
|
||||
p.add_argument("--cwd", help="Working directory (default: process cwd)")
|
||||
p.add_argument("--state-dir", help="Override checkpoint state directory")
|
||||
p.add_argument(
|
||||
"--state-dir",
|
||||
help="Override checkpoint state directory",
|
||||
"--config-dir",
|
||||
help="Override config dir (default: ~/.config/activity)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--format",
|
||||
|
|
@ -642,6 +793,41 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
action="store_true",
|
||||
help="Exit 3 when inbox has open items",
|
||||
)
|
||||
p.add_argument(
|
||||
"--service",
|
||||
"-s",
|
||||
metavar="NAME",
|
||||
help=(
|
||||
"Use named activity-core service for this call only "
|
||||
"(does not change default; see: activity service)"
|
||||
),
|
||||
)
|
||||
p.add_argument(
|
||||
"--activity-url",
|
||||
metavar="URL",
|
||||
help="One-shot activity-core API base URL (overrides --service / config)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--hub-url",
|
||||
metavar="URL",
|
||||
dest="hub_url",
|
||||
help="One-shot State Hub base URL (or per-service hub when adding)",
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
# Shared options live on each subcommand (not the top parser) so
|
||||
# `activity list --format json` and `activity -s railiance runs` both work.
|
||||
common = _common_parent()
|
||||
p = argparse.ArgumentParser(
|
||||
prog="activity",
|
||||
description=(
|
||||
"Repo-scoped automation review CLI (ACTIVITY-WP-0028). "
|
||||
"Run from a consumer repo (e.g. freedom-intelligence). "
|
||||
"Shared flags go after the verb: activity list --format json"
|
||||
),
|
||||
)
|
||||
sub = p.add_subparsers(dest="command", required=True)
|
||||
|
||||
for name, help_text in (
|
||||
|
|
@ -651,22 +837,26 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
("deliverables", "What was produced"),
|
||||
("inbox", "Open deliverables for review"),
|
||||
):
|
||||
sp = sub.add_parser(name, help=help_text)
|
||||
sp = sub.add_parser(name, help=help_text, parents=[common])
|
||||
if name in {"runs", "deliverables", "status", "inbox"}:
|
||||
sp.add_argument(
|
||||
"--since",
|
||||
default=None,
|
||||
help="today|yesterday|week|sunday|checkpoint|ISO (default: checkpoint or today)",
|
||||
help="today|yesterday|week|sunday|checkpoint|ISO (default: week or checkpoint)",
|
||||
)
|
||||
|
||||
sp_cp = sub.add_parser("checkpoint", help="Show/set/clear local review cursor")
|
||||
sp_cp = sub.add_parser(
|
||||
"checkpoint", help="Show/set/clear local review cursor", parents=[common]
|
||||
)
|
||||
sp_cp.add_argument(
|
||||
"checkpoint_action",
|
||||
choices=("show", "set", "clear"),
|
||||
help="Action",
|
||||
)
|
||||
|
||||
sp_ack = sub.add_parser("ack", help="Mark path(s) or ops_run id(s) reviewed")
|
||||
sp_ack = sub.add_parser(
|
||||
"ack", help="Mark path(s) or ops_run id(s) reviewed", parents=[common]
|
||||
)
|
||||
sp_ack.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
|
|
@ -677,13 +867,66 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
action="store_true",
|
||||
help="Ack all deliverable paths since checkpoint",
|
||||
)
|
||||
|
||||
sp_svc = sub.add_parser(
|
||||
"service",
|
||||
help="Configure named activity-core services (list/add/use/…)",
|
||||
parents=[common],
|
||||
)
|
||||
sp_svc.add_argument(
|
||||
"service_action",
|
||||
choices=("list", "show", "add", "remove", "use", "default", "which"),
|
||||
help="Action",
|
||||
)
|
||||
sp_svc.add_argument(
|
||||
"name",
|
||||
nargs="?",
|
||||
help="Service name (for show/add/remove/use)",
|
||||
)
|
||||
sp_svc.add_argument(
|
||||
"--url",
|
||||
help="activity-core API base URL (for add)",
|
||||
)
|
||||
sp_svc.add_argument(
|
||||
"--description",
|
||||
default="",
|
||||
help="Optional description (for add)",
|
||||
)
|
||||
sp_svc.add_argument(
|
||||
"--make-default",
|
||||
action="store_true",
|
||||
help="Set as default when adding",
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
# propagate format/strict onto subcommands that share globals
|
||||
|
||||
# Resolve multi-service endpoints before command handlers hit the network.
|
||||
cfg_dir = (
|
||||
Path(args.config_dir).expanduser()
|
||||
if getattr(args, "config_dir", None)
|
||||
else None
|
||||
)
|
||||
cfg_path = svc_mod.config_path(cfg_dir) if cfg_dir else None
|
||||
try:
|
||||
endpoints = svc_mod.resolve_endpoints(
|
||||
service=getattr(args, "service", None),
|
||||
activity_url=getattr(args, "activity_url", None),
|
||||
hub_url=getattr(args, "hub_url", None),
|
||||
config_path_override=cfg_path,
|
||||
)
|
||||
except KeyError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
apply_endpoint_overrides(
|
||||
endpoints.get("activity_core_url"),
|
||||
endpoints.get("state_hub_url"),
|
||||
)
|
||||
args._endpoints = endpoints # noqa: SLF001
|
||||
|
||||
handlers = {
|
||||
"list": cmd_list,
|
||||
"status": cmd_status,
|
||||
|
|
@ -692,6 +935,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"inbox": cmd_inbox,
|
||||
"checkpoint": cmd_checkpoint,
|
||||
"ack": cmd_ack,
|
||||
"service": cmd_service,
|
||||
}
|
||||
handler = handlers.get(args.command)
|
||||
if handler is None:
|
||||
|
|
|
|||
232
src/activity_core/review_cli/services.py
Normal file
232
src/activity_core/review_cli/services.py
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
"""Named activity-core service endpoints (multi-env config).
|
||||
|
||||
Config path: ${XDG_CONFIG_HOME:-~/.config}/activity/services.json
|
||||
|
||||
Example::
|
||||
|
||||
{
|
||||
"default": "railiance",
|
||||
"services": {
|
||||
"railiance": {
|
||||
"activity_core_url": "https://activity.coulomb.social",
|
||||
"state_hub_url": "http://127.0.0.1:18000",
|
||||
"description": "Railiance prod (SSO)"
|
||||
},
|
||||
"local": {
|
||||
"activity_core_url": "http://127.0.0.1:8010",
|
||||
"state_hub_url": "http://127.0.0.1:8000",
|
||||
"description": "Local docker/dev stack"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Resolution for activity_core_url (highest first):
|
||||
|
||||
1. ``--activity-url`` CLI flag
|
||||
2. ``--service NAME`` → services[NAME].activity_core_url
|
||||
3. ``ACTIVITY_CORE_URL`` environment variable
|
||||
4. config default service's activity_core_url
|
||||
5. unset
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
_SERVICE_NAME_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]{0,63}$")
|
||||
|
||||
|
||||
def config_path(config_dir: Path | None = None) -> Path:
|
||||
if config_dir is not None:
|
||||
return Path(config_dir).expanduser() / "services.json"
|
||||
override = os.environ.get("ACTIVITY_CONFIG_DIR", "").strip()
|
||||
if override:
|
||||
return Path(override).expanduser() / "services.json"
|
||||
xdg = os.environ.get("XDG_CONFIG_HOME", "").strip()
|
||||
if xdg:
|
||||
return Path(xdg).expanduser() / "activity" / "services.json"
|
||||
return Path.home() / ".config" / "activity" / "services.json"
|
||||
|
||||
|
||||
def empty_config() -> dict[str, Any]:
|
||||
return {"default": None, "services": {}}
|
||||
|
||||
|
||||
def load_config(path: Path | None = None) -> dict[str, Any]:
|
||||
p = path or config_path()
|
||||
if not p.is_file():
|
||||
return empty_config()
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return empty_config()
|
||||
if not isinstance(data, dict):
|
||||
return empty_config()
|
||||
services = data.get("services") or {}
|
||||
if not isinstance(services, dict):
|
||||
services = {}
|
||||
# normalize service entries
|
||||
clean: dict[str, Any] = {}
|
||||
for name, svc in services.items():
|
||||
if not isinstance(svc, dict):
|
||||
continue
|
||||
clean[str(name)] = {
|
||||
"activity_core_url": _norm_url(svc.get("activity_core_url")),
|
||||
"state_hub_url": _norm_url(svc.get("state_hub_url")),
|
||||
"description": str(svc.get("description") or ""),
|
||||
}
|
||||
default = data.get("default")
|
||||
if default is not None:
|
||||
default = str(default)
|
||||
if default not in clean:
|
||||
default = None
|
||||
return {"default": default, "services": clean}
|
||||
|
||||
|
||||
def save_config(data: dict[str, Any], path: Path | None = None) -> Path:
|
||||
p = path or config_path()
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
payload = {
|
||||
"default": data.get("default"),
|
||||
"services": data.get("services") or {},
|
||||
}
|
||||
p.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
return p
|
||||
|
||||
|
||||
def _norm_url(value: Any) -> str | None:
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
v = value.strip().rstrip("/")
|
||||
return v or None
|
||||
|
||||
|
||||
def validate_service_name(name: str) -> str:
|
||||
name = name.strip()
|
||||
if not _SERVICE_NAME_RE.match(name):
|
||||
raise ValueError(
|
||||
f"invalid service name {name!r}: use letters, digits, _- "
|
||||
"(start with a letter, max 64 chars)"
|
||||
)
|
||||
return name
|
||||
|
||||
|
||||
def add_service(
|
||||
name: str,
|
||||
*,
|
||||
activity_core_url: str,
|
||||
state_hub_url: str | None = None,
|
||||
description: str = "",
|
||||
make_default: bool = False,
|
||||
path: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
name = validate_service_name(name)
|
||||
url = _norm_url(activity_core_url)
|
||||
if not url:
|
||||
raise ValueError("activity_core_url is required")
|
||||
cfg = load_config(path)
|
||||
cfg["services"][name] = {
|
||||
"activity_core_url": url,
|
||||
"state_hub_url": _norm_url(state_hub_url),
|
||||
"description": description or "",
|
||||
}
|
||||
if make_default or not cfg.get("default"):
|
||||
cfg["default"] = name
|
||||
save_config(cfg, path)
|
||||
return cfg
|
||||
|
||||
|
||||
def remove_service(name: str, path: Path | None = None) -> dict[str, Any]:
|
||||
cfg = load_config(path)
|
||||
if name not in cfg["services"]:
|
||||
raise KeyError(f"unknown service {name!r}")
|
||||
del cfg["services"][name]
|
||||
if cfg.get("default") == name:
|
||||
cfg["default"] = next(iter(cfg["services"]), None)
|
||||
save_config(cfg, path)
|
||||
return cfg
|
||||
|
||||
|
||||
def set_default(name: str, path: Path | None = None) -> dict[str, Any]:
|
||||
cfg = load_config(path)
|
||||
if name not in cfg["services"]:
|
||||
raise KeyError(f"unknown service {name!r}")
|
||||
cfg["default"] = name
|
||||
save_config(cfg, path)
|
||||
return cfg
|
||||
|
||||
|
||||
def get_service(name: str, path: Path | None = None) -> dict[str, Any] | None:
|
||||
cfg = load_config(path)
|
||||
return cfg["services"].get(name)
|
||||
|
||||
|
||||
def resolve_endpoints(
|
||||
*,
|
||||
service: str | None = None,
|
||||
activity_url: str | None = None,
|
||||
hub_url: str | None = None,
|
||||
config_path_override: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Resolve which activity-core / hub URLs to use for this invocation.
|
||||
|
||||
Returns dict with keys:
|
||||
activity_core_url, state_hub_url, service (name or None),
|
||||
source (flag|service|env|default|none)
|
||||
"""
|
||||
cfg = load_config(config_path_override)
|
||||
selected_name: str | None = None
|
||||
source = "none"
|
||||
core: str | None = None
|
||||
hub: str | None = None
|
||||
|
||||
# 1) explicit one-shot URL flags
|
||||
if activity_url and activity_url.strip():
|
||||
core = _norm_url(activity_url)
|
||||
source = "flag"
|
||||
# 2) named service (one-shot; does not change default)
|
||||
elif service and service.strip():
|
||||
name = service.strip()
|
||||
svc = cfg["services"].get(name)
|
||||
if not svc:
|
||||
known = ", ".join(sorted(cfg["services"])) or "(none)"
|
||||
raise KeyError(f"unknown service {name!r}; known: {known}")
|
||||
selected_name = name
|
||||
core = svc.get("activity_core_url")
|
||||
hub = svc.get("state_hub_url")
|
||||
source = "service"
|
||||
# 3) environment
|
||||
elif os.environ.get("ACTIVITY_CORE_URL", "").strip():
|
||||
core = _norm_url(os.environ.get("ACTIVITY_CORE_URL"))
|
||||
source = "env"
|
||||
# 4) config default
|
||||
elif cfg.get("default") and cfg["default"] in cfg["services"]:
|
||||
selected_name = cfg["default"]
|
||||
svc = cfg["services"][selected_name]
|
||||
core = svc.get("activity_core_url")
|
||||
hub = svc.get("state_hub_url")
|
||||
source = "default"
|
||||
|
||||
# Hub precedence: --hub-url > service-selected hub > env > none
|
||||
# (do not let env override an explicit --service / default service hub)
|
||||
if hub_url and hub_url.strip():
|
||||
hub = _norm_url(hub_url)
|
||||
elif hub is None and (
|
||||
os.environ.get("STATE_HUB_URL", "").strip()
|
||||
or os.environ.get("STATEHUB_URL", "").strip()
|
||||
):
|
||||
hub = _norm_url(
|
||||
os.environ.get("STATE_HUB_URL") or os.environ.get("STATEHUB_URL")
|
||||
)
|
||||
|
||||
return {
|
||||
"activity_core_url": core,
|
||||
"state_hub_url": hub,
|
||||
"service": selected_name,
|
||||
"source": source,
|
||||
"config_path": str(config_path_override or config_path()),
|
||||
}
|
||||
|
|
@ -8,13 +8,42 @@ from typing import Any
|
|||
|
||||
import httpx
|
||||
|
||||
# Set by main after resolving --service / config / env (see services.resolve_endpoints).
|
||||
_endpoint_override_active = False
|
||||
_override_activity_url: str | None = None
|
||||
_override_hub_url: str | None = None
|
||||
|
||||
|
||||
def apply_endpoint_overrides(
|
||||
activity_core_url: str | None = None,
|
||||
state_hub_url: str | None = None,
|
||||
) -> None:
|
||||
"""Pin URLs for this process (call once after service resolution)."""
|
||||
global _endpoint_override_active, _override_activity_url, _override_hub_url
|
||||
_endpoint_override_active = True
|
||||
_override_activity_url = (
|
||||
activity_core_url.strip().rstrip("/") if activity_core_url else None
|
||||
)
|
||||
_override_hub_url = state_hub_url.strip().rstrip("/") if state_hub_url else None
|
||||
|
||||
|
||||
def clear_endpoint_overrides() -> None:
|
||||
global _endpoint_override_active, _override_activity_url, _override_hub_url
|
||||
_endpoint_override_active = False
|
||||
_override_activity_url = None
|
||||
_override_hub_url = None
|
||||
|
||||
|
||||
def activity_core_url() -> str | None:
|
||||
if _endpoint_override_active:
|
||||
return _override_activity_url
|
||||
raw = (os.environ.get("ACTIVITY_CORE_URL") or "").strip().rstrip("/")
|
||||
return raw or None
|
||||
|
||||
|
||||
def state_hub_url() -> str | None:
|
||||
if _endpoint_override_active:
|
||||
return _override_hub_url
|
||||
raw = (
|
||||
os.environ.get("STATE_HUB_URL")
|
||||
or os.environ.get("STATEHUB_URL")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue