-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from DS4SD/DaemonMode
feat: Start Command
- Loading branch information
Showing
8 changed files
with
183 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
""" | ||
The start command initializes ragnardoc to run as a service that continuously | ||
maintains the state of your documents in all of your connected RAG apps. | ||
""" | ||
# Standard | ||
from datetime import timedelta | ||
import argparse | ||
import re | ||
import shlex | ||
import subprocess | ||
import sys | ||
import time | ||
|
||
# First Party | ||
import alog | ||
|
||
# Local | ||
from .. import config | ||
from .base import CommandBase | ||
|
||
log = alog.use_channel("START") | ||
|
||
|
||
class StartCommand(CommandBase): | ||
__doc__ = __doc__ | ||
name = "start" | ||
|
||
def __init__(self): | ||
self._period = self._parse_time(config.service.period) | ||
self._cmd = f"{sys.executable} -m ragnardoc run" | ||
self._running = False | ||
|
||
def add_args(self, parser: argparse.ArgumentParser): | ||
"""Add the args to configure the periodic scraping""" | ||
parser.add_argument( | ||
"--period", | ||
"-p", | ||
default=None, | ||
help="The period to run the ingestion service", | ||
) | ||
|
||
def stop(self): | ||
self._running = False | ||
|
||
def run(self, args: argparse.Namespace): | ||
"""Start the infinite loop to run periodically""" | ||
period = self._period | ||
if args.period: | ||
period = self._parse_time(args.period) | ||
self._running = True | ||
while self._running: | ||
log.info("Running ingestion service") | ||
self._ingest() | ||
log.info("Sleeping for %s", period) | ||
time.sleep(period.total_seconds()) | ||
|
||
def _ingest(self): | ||
"""Run the ingestion as a subprocess. This is done so that config | ||
changes are re-parsed on very run. | ||
""" | ||
with alog.ContextTimer(log.debug, "Ingestion done in: %s"): | ||
subprocess.run(shlex.split(self._cmd)) | ||
|
||
@staticmethod | ||
def _parse_time(time_str: str) -> timedelta: | ||
"""Parse a time string into a timedelta object""" | ||
pattern = r"(\d+\.?\d*)([dhms])\s*" | ||
seconds = 0 | ||
for match in re.finditer(pattern, time_str): | ||
value = float(match.group(1)) | ||
unit = match.group(2) | ||
if unit == "s": | ||
seconds += value | ||
elif unit == "m": | ||
seconds += value * 60 | ||
elif unit == "h": | ||
seconds += value * 60 * 60 | ||
elif unit == "d": | ||
seconds += value * 60 * 60 * 24 | ||
if not seconds: | ||
raise ValueError(f"Invalid time string: {time_str}") | ||
return timedelta(seconds=seconds) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
""" | ||
Unit tests for the start command | ||
""" | ||
# Standard | ||
from datetime import timedelta | ||
from unittest import mock | ||
import argparse | ||
import threading | ||
import time | ||
|
||
# Third Party | ||
import pytest | ||
|
||
# First Party | ||
import aconfig | ||
|
||
# Local | ||
from ragnardoc.cli.start import StartCommand | ||
|
||
|
||
@pytest.mark.parametrize( | ||
["time_str", "expected_delta"], | ||
[ | ||
("35s", timedelta(seconds=35)), | ||
("1d 2h 35s", timedelta(seconds=35 + 2 * 60 * 60 + 60 * 60 * 24)), | ||
("16s 6h", timedelta(seconds=16 + 6 * 60 * 60)), | ||
("2hours 1minute", timedelta(seconds=60 + 2 * 60 * 60)), | ||
("0.5s", timedelta(seconds=0.5)), | ||
], | ||
) | ||
def test_parse_time(time_str, expected_delta): | ||
"""Test that time parsing works for various combinations""" | ||
assert StartCommand._parse_time(time_str) == expected_delta | ||
|
||
|
||
@pytest.mark.parametrize("time_str", ["", " ", "1 d", "1w"]) | ||
def test_parse_time_invalid(time_str): | ||
"""Test that ValueError is raised for invalid time strings""" | ||
with pytest.raises(ValueError): | ||
StartCommand._parse_time(time_str) | ||
|
||
|
||
@mock.patch("subprocess.run") | ||
def test_run(run_mock): | ||
"""Test that running the command launches the infinite loop correctly""" | ||
cmd = StartCommand() | ||
args = aconfig.Config({"period": "0.1s"}, override_env_vars=False) | ||
run_thread = threading.Thread(target=cmd.run, args=(args,)) | ||
run_thread.start() | ||
time.sleep(0.05) | ||
cmd.stop() | ||
run_thread.join() | ||
run_mock.assert_called_once() | ||
|
||
|
||
def test_add_args(): | ||
"""Test that the command adds the expected arguments""" | ||
parser = argparse.ArgumentParser() | ||
StartCommand().add_args(parser) | ||
args = parser.parse_args([]) | ||
assert hasattr(args, "period") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters