Skip to content

Commit de71240

Browse files
committed
Add Autoupdater
1 parent dddcd9f commit de71240

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

apex/validator/auto_update.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import asyncio
2+
import subprocess
3+
import sys
4+
import time
5+
from loguru import logger
6+
from pathlib import Path
7+
from shlex import split
8+
9+
ROOT_DIR = Path(__file__).parent.parent
10+
11+
12+
def get_version() -> str:
13+
"""Extract the version as current git commit hash"""
14+
result = subprocess.run(
15+
split("git rev-parse HEAD"),
16+
check=True,
17+
capture_output=True,
18+
cwd=ROOT_DIR,
19+
)
20+
commit = result.stdout.decode().strip()
21+
assert len(commit) == 40, f"Invalid commit hash: {commit}"
22+
return commit[:8]
23+
24+
25+
def pull_latest_version() -> None:
26+
"""
27+
Pull the latest version from git.
28+
This uses `git pull --rebase`, so if any changes were made to the local repository,
29+
this will try to apply them on top of origin's changes. This is intentional, as we
30+
don't want to overwrite any local changes. However, if there are any conflicts,
31+
this will abort the rebase and return to the original state.
32+
The conflicts are expected to happen rarely since validator is expected
33+
to be used as-is.
34+
"""
35+
try:
36+
subprocess.run(split("git pull --rebase --autostash"), check=True, cwd=ROOT_DIR)
37+
except subprocess.CalledProcessError as exc:
38+
logger.error("Failed to pull, reverting: %s", exc)
39+
subprocess.run(split("git rebase --abort"), check=True, cwd=ROOT_DIR)
40+
41+
42+
def upgrade_packages() -> None:
43+
"""
44+
Upgrade python packages by running `pip install --upgrade -r requirements.txt`.
45+
Notice: this won't work if some package in `requirements.txt` is downgraded.
46+
Ignored as this is unlikely to happen.
47+
"""
48+
49+
logger.info("Upgrading packages")
50+
try:
51+
subprocess.run(
52+
split(f"{sys.executable} -m pip install -e ."),
53+
check=True,
54+
cwd=ROOT_DIR,
55+
)
56+
except subprocess.CalledProcessError as exc:
57+
logger.error("Failed to upgrade packages, proceeding anyway. %s", exc)
58+
59+
60+
async def autoupdate_loop() -> None:
61+
"""
62+
Async version of autoupdate that runs alongside the validator.
63+
Checks for updates every hour and applies them if available.
64+
"""
65+
current_version = latest_version = get_version()
66+
logger.info("Current version: %s", current_version)
67+
68+
try:
69+
while True:
70+
await asyncio.sleep(3600) # Wait 1 hour between checks
71+
72+
pull_latest_version()
73+
latest_version = get_version()
74+
logger.info("Latest version: %s", latest_version)
75+
76+
if latest_version != current_version:
77+
logger.info(
78+
"Upgraded to latest version: %s -> %s",
79+
current_version,
80+
latest_version,
81+
)
82+
upgrade_packages()
83+
current_version = latest_version
84+
85+
except asyncio.CancelledError:
86+
logger.info("Autoupdate task cancelled")
87+
raise

validator.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from apex.validator.miner_sampler import MinerSampler
1414
from apex.validator.miner_scorer import MinerScorer
1515
from apex.validator.pipeline import Pipeline
16+
from apex.validator.auto_update import autoupdate_loop
1617

1718

1819
async def read_args() -> argparse.Namespace:
@@ -25,6 +26,12 @@ async def read_args() -> argparse.Namespace:
2526
help="Config file path (e.g. config/mainnet.yaml).",
2627
type=Path,
2728
)
29+
parser.add_argument(
30+
"--no-autoupdate",
31+
action="store_true",
32+
default=False,
33+
help="Disable automatic updates (checks every hour) (default: enabled)",
34+
)
2835
args = parser.parse_args()
2936
return args
3037

@@ -48,6 +55,12 @@ async def main() -> None:
4855
miner_scorer = MinerScorer(chain=chain, **config.miner_scorer.kwargs)
4956
asyncio.create_task(miner_scorer.start_loop())
5057

58+
# Start autoupdate task if enabled
59+
autoupdate_task = None
60+
if not args.no_autoupdate:
61+
logger.info("Autoupdate enabled - will check for updates every hour")
62+
autoupdate_task = asyncio.create_task(autoupdate_loop())
63+
5164
llm = LLM(**config.llm.kwargs)
5265

5366
deep_research = DeepResearchLangchain(websearch=websearch, **config.deep_research.kwargs)
@@ -68,6 +81,14 @@ async def main() -> None:
6881
except BaseException as exc:
6982
logger.exception(f"Unknown exception caught, exiting validator: {exc}")
7083
finally:
84+
# Cancel autoupdate task if it was started
85+
if autoupdate_task is not None:
86+
autoupdate_task.cancel()
87+
try:
88+
await autoupdate_task
89+
except asyncio.CancelledError:
90+
pass
91+
7192
await chain.shutdown()
7293
await logger_db.shutdown()
7394
await miner_scorer.shutdown()

0 commit comments

Comments
 (0)