diff --git a/setup.py b/setup.py index 056aa6d..adc3beb 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,8 @@ setup( 'pytest-cov', 'pytest-sugar', 'tox', - 'responses' + 'responses', + 'debugpy' ], 'test': [ 'pytest', diff --git a/shepherd/agent/cli.py b/shepherd/agent/cli.py index c668b48..2323558 100644 --- a/shepherd/agent/cli.py +++ b/shepherd/agent/cli.py @@ -1,16 +1,18 @@ import logging -import os +# import os import sys from pathlib import Path import glob from types import SimpleNamespace from datetime import datetime -from pprint import pprint +import inspect +# from pprint import pprint import pkg_resources -import chromalog + +# import chromalog import click import toml @@ -165,7 +167,7 @@ def test(ctx, plugin_name, interface_function): if not interface_function: echo_section(F"Interface functions [{plugin_name}]:", on_nl=False) - for name, func in interface._functions.items(): + for name in interface._functions: click.echo(F" {name}") return diff --git a/shepherd/agent/control.py b/shepherd/agent/control.py index e55c38b..b59373d 100644 --- a/shepherd/agent/control.py +++ b/shepherd/agent/control.py @@ -49,7 +49,8 @@ class CoreUpdateState(): def __init__(self, cmd_reader, cmd_result_writer): """ - Control update handler for the `/update` core endpoint. Needs a reference to the CommandRunner + Control update handler for the `/update` core endpoint. Needs a reference to the + CommandRunner """ self.topic_bundle = statesman.TopicBundle({ 'status': statesman.StateWriter(), diff --git a/shepherd/agent/plugin.py b/shepherd/agent/plugin.py index 7e15dc5..d5c90c0 100644 --- a/shepherd/agent/plugin.py +++ b/shepherd/agent/plugin.py @@ -120,7 +120,7 @@ def plugin_class(cls): interface.register_plugin_class(MyPluginClass) """ if not inspect.isclass(cls): - raise PluginLoadError(F"@plugin_class can only be used to decorate a class") + raise PluginLoadError("@plugin_class can only be used to decorate a class") cls._shepherd_load_marker = ClassMarker() return cls @@ -482,7 +482,7 @@ class PluginInterface(): """ self._load_guard() if not isinstance(name, str): - raise PluginLoadError(F"Hook name must be a string") + raise PluginLoadError("Hook name must be a string") if name in self._hooks: raise PluginLoadError(F"Hook with name '{name}' already exists") diff --git a/shepherd/agent/tasks.py b/shepherd/agent/tasks.py index 121997c..851a6ee 100644 --- a/shepherd/agent/tasks.py +++ b/shepherd/agent/tasks.py @@ -18,7 +18,7 @@ from apscheduler.triggers.cron import CronTrigger as APCronTrigger from configspec import * import preserve -from .util import HoldLock +# from .util import HoldLock log = logging.getLogger("shepherd.agent.tasks") @@ -98,8 +98,8 @@ class CronTrigger(TaskTrigger): return fire_time.astimezone(pytz.utc).astimezone(tz.tzutc()) -TriggerSpec = ConfigSpecification() -TriggerSpec.add_specs({ +CronTriggerSpec = ConfigSpecification() +CronTriggerSpec.add_specs({ 'month': StringSpec(helptext="Month in year, 1-12"), 'day': StringSpec(helptext="Day of month, 1-32"), 'day_of_week': StringSpec(helptext="Day of week, 0-6 or mon,tue,wed,thu,fri,sat,sun"), @@ -326,27 +326,7 @@ def _tasks_update_loop(config, suspend_hook, session): _update_thread_init_done.clear() - # Right, so we have our task list, and calls have been resolved. Assume that any waiting period - # for control has already been handled. - - # Eventually also need a system to wait for time to stabilise (hook?) - - # What do we do when task list is empty? Return directly? - - # That list then gets sorted by that time, and the main loop simply waits for the next scheduled - # time to pass before calling the task (on a different thread). The scheduled_for time gets updated, - # the scheduled_tasks list gets sorted again. The loop then looks at the next task, probably with - # some log if "scheduled_for" is too far in the past, but run the task anyway. - - # Lowpower handling can then be in the section of the cycle where we'd otherwise be doing a delay. - # Have a minimum sleep delay (10ms or something), so we can have a task only run if it's past - # the scheduled_for, and then we can happily use that time to get the _next_ trigger time. - - # Start our scheduler thread to fire off tasks, including the initial grace period checks - # Due to our task constraints, our scheduler can be somewhat simplified. We know that task - # triggers won't change after init, and they will be infrequent enough that we can just - # make a new thread for each task. - - # Maybe have "cycles", where we determine what task is going to occur next, and when. - # A main dispatch loop then pretty much delays until that point, or triggers the low power - # stuff somehow. + # TODO Handle case when tasks return None as next trigger time, and when no triggers are left + # TODO Add maximum suspend period + # TODO Add "snooze" task checking even on new session, to catch tasks we miss if we restart + # due to new config