added naming to monitors, handlers and conductors so runners can identify them, in prep for in-workflow modification of patterns and recipes'

This commit is contained in:
PatchOfScotland
2023-03-16 13:53:01 +01:00
parent 9547df7612
commit f1f16ca3b8
12 changed files with 142 additions and 21 deletions

View File

@ -24,12 +24,12 @@ from meow_base.functionality.file_io import make_dir, read_yaml, write_file, \
class LocalPythonConductor(BaseConductor): class LocalPythonConductor(BaseConductor):
def __init__(self, job_queue_dir:str=DEFAULT_JOB_QUEUE_DIR, def __init__(self, job_queue_dir:str=DEFAULT_JOB_QUEUE_DIR,
job_output_dir:str=DEFAULT_JOB_OUTPUT_DIR)->None: job_output_dir:str=DEFAULT_JOB_OUTPUT_DIR, name:str="")->None:
"""LocalPythonConductor Constructor. This should be used to execute """LocalPythonConductor Constructor. This should be used to execute
Python jobs, and will then pass any internal job runner files to the Python jobs, and will then pass any internal job runner files to the
output directory. Note that if this handler is given to a MeowRunner output directory. Note that if this handler is given to a MeowRunner
object, the job_queue_dir and job_output_dir will be overwridden.""" object, the job_queue_dir and job_output_dir will be overwridden."""
super().__init__() super().__init__(name=name)
self._is_valid_job_queue_dir(job_queue_dir) self._is_valid_job_queue_dir(job_queue_dir)
self.job_queue_dir = job_queue_dir self.job_queue_dir = job_queue_dir
self._is_valid_job_output_dir(job_output_dir) self._is_valid_job_output_dir(job_output_dir)

View File

@ -8,11 +8,17 @@ Author(s): David Marchant
from typing import Any, Tuple, Dict from typing import Any, Tuple, Dict
from meow_base.core.correctness.vars import get_drt_imp_msg from meow_base.core.correctness.vars import VALID_CONDUCTOR_NAME_CHARS, \
from meow_base.core.correctness.validation import check_implementation get_drt_imp_msg
from meow_base.core.correctness.validation import check_implementation, \
valid_string
from meow_base.functionality.naming import generate_conductor_id
class BaseConductor: class BaseConductor:
# An identifier for a conductor within the runner. Can be manually set in
# the constructor, or autogenerated if no name provided.
name:str
# Directory where queued jobs are initially written to. Note that this # Directory where queued jobs are initially written to. Note that this
# will be overridden by a MeowRunner, if a handler instance is passed to # will be overridden by a MeowRunner, if a handler instance is passed to
# it, and so does not need to be initialised within the handler itself. # it, and so does not need to be initialised within the handler itself.
@ -21,11 +27,15 @@ class BaseConductor:
# will be overridden by a MeowRunner, if a handler instance is passed to # will be overridden by a MeowRunner, if a handler instance is passed to
# it, and so does not need to be initialised within the handler itself. # it, and so does not need to be initialised within the handler itself.
job_output_dir:str job_output_dir:str
def __init__(self)->None: def __init__(self, name:str="")->None:
"""BaseConductor Constructor. This will check that any class inheriting """BaseConductor Constructor. This will check that any class inheriting
from it implements its validation functions.""" from it implements its validation functions."""
check_implementation(type(self).execute, BaseConductor) check_implementation(type(self).execute, BaseConductor)
check_implementation(type(self).valid_execute_criteria, BaseConductor) check_implementation(type(self).valid_execute_criteria, BaseConductor)
if not name:
name = generate_conductor_id()
self._is_valid_name(name)
self.name = name
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""A check that this base class is not instantiated itself, only """A check that this base class is not instantiated itself, only
@ -35,6 +45,12 @@ class BaseConductor:
raise TypeError(msg) raise TypeError(msg)
return object.__new__(cls) return object.__new__(cls)
def _is_valid_name(self, name:str)->None:
"""Validation check for 'name' variable from main constructor. Is
automatically called during initialisation. This does not need to be
overridden by child classes."""
valid_string(name, VALID_CONDUCTOR_NAME_CHARS)
def valid_execute_criteria(self, job:Dict[str,Any])->Tuple[bool,str]: def valid_execute_criteria(self, job:Dict[str,Any])->Tuple[bool,str]:
"""Function to determine given an job defintion, if this conductor can """Function to determine given an job defintion, if this conductor can
process it or not. Must be implemented by any child process.""" process it or not. Must be implemented by any child process."""

View File

@ -8,11 +8,16 @@ Author(s): David Marchant
from typing import Any, Tuple, Dict from typing import Any, Tuple, Dict
from meow_base.core.correctness.vars import VALID_CHANNELS, get_drt_imp_msg from meow_base.core.correctness.vars import VALID_CHANNELS, \
from meow_base.core.correctness.validation import check_implementation VALID_HANDLER_NAME_CHARS, get_drt_imp_msg
from meow_base.core.correctness.validation import check_implementation, \
valid_string
from meow_base.functionality.naming import generate_handler_id
class BaseHandler: class BaseHandler:
# An identifier for a handler within the runner. Can be manually set in
# the constructor, or autogenerated if no name provided.
name:str
# A channel for sending messages to the runner. Note that this will be # A channel for sending messages to the runner. Note that this will be
# overridden by a MeowRunner, if a handler instance is passed to it, and so # overridden by a MeowRunner, if a handler instance is passed to it, and so
# does not need to be initialised within the handler itself. # does not need to be initialised within the handler itself.
@ -21,11 +26,15 @@ class BaseHandler:
# will be overridden by a MeowRunner, if a handler instance is passed to # will be overridden by a MeowRunner, if a handler instance is passed to
# it, and so does not need to be initialised within the handler itself. # it, and so does not need to be initialised within the handler itself.
job_queue_dir:str job_queue_dir:str
def __init__(self)->None: def __init__(self, name:str='')->None:
"""BaseHandler Constructor. This will check that any class inheriting """BaseHandler Constructor. This will check that any class inheriting
from it implements its validation functions.""" from it implements its validation functions."""
check_implementation(type(self).handle, BaseHandler) check_implementation(type(self).handle, BaseHandler)
check_implementation(type(self).valid_handle_criteria, BaseHandler) check_implementation(type(self).valid_handle_criteria, BaseHandler)
if not name:
name = generate_handler_id()
self._is_valid_name(name)
self.name = name
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""A check that this base class is not instantiated itself, only """A check that this base class is not instantiated itself, only
@ -35,6 +44,12 @@ class BaseHandler:
raise TypeError(msg) raise TypeError(msg)
return object.__new__(cls) return object.__new__(cls)
def _is_valid_name(self, name:str)->None:
"""Validation check for 'name' variable from main constructor. Is
automatically called during initialisation. This does not need to be
overridden by child classes."""
valid_string(name, VALID_HANDLER_NAME_CHARS)
def valid_handle_criteria(self, event:Dict[str,Any])->Tuple[bool,str]: def valid_handle_criteria(self, event:Dict[str,Any])->Tuple[bool,str]:
"""Function to determine given an event defintion, if this handler can """Function to determine given an event defintion, if this handler can
process it or not. Must be implemented by any child process.""" process it or not. Must be implemented by any child process."""

View File

@ -12,12 +12,18 @@ from typing import Union, Dict
from meow_base.core.base_pattern import BasePattern from meow_base.core.base_pattern import BasePattern
from meow_base.core.base_recipe import BaseRecipe from meow_base.core.base_recipe import BaseRecipe
from meow_base.core.base_rule import BaseRule from meow_base.core.base_rule import BaseRule
from meow_base.core.correctness.vars import VALID_CHANNELS, get_drt_imp_msg from meow_base.core.correctness.vars import VALID_CHANNELS, \
from meow_base.core.correctness.validation import check_implementation VALID_MONITOR_NAME_CHARS, get_drt_imp_msg
from meow_base.core.correctness.validation import check_implementation, \
valid_string
from meow_base.functionality.meow import create_rules from meow_base.functionality.meow import create_rules
from meow_base.functionality.naming import generate_monitor_id
class BaseMonitor: class BaseMonitor:
# An identifier for a monitor within the runner. Can be manually set in
# the constructor, or autogenerated if no name provided.
name:str
# A collection of patterns # A collection of patterns
_patterns: Dict[str, BasePattern] _patterns: Dict[str, BasePattern]
# A collection of recipes # A collection of recipes
@ -29,7 +35,7 @@ class BaseMonitor:
# monitor is passed to it. # monitor is passed to it.
to_runner: VALID_CHANNELS to_runner: VALID_CHANNELS
def __init__(self, patterns:Dict[str,BasePattern], def __init__(self, patterns:Dict[str,BasePattern],
recipes:Dict[str,BaseRecipe])->None: recipes:Dict[str,BaseRecipe], name:str="")->None:
"""BaseMonitor Constructor. This will check that any class inheriting """BaseMonitor Constructor. This will check that any class inheriting
from it implements its validation functions. It will then call these on from it implements its validation functions. It will then call these on
the input parameters.""" the input parameters."""
@ -53,6 +59,10 @@ class BaseMonitor:
self._patterns = deepcopy(patterns) self._patterns = deepcopy(patterns)
self._recipes = deepcopy(recipes) self._recipes = deepcopy(recipes)
self._rules = create_rules(patterns, recipes) self._rules = create_rules(patterns, recipes)
if not name:
name = generate_monitor_id()
self._is_valid_name(name)
self.name = name
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
"""A check that this base class is not instantiated itself, only """A check that this base class is not instantiated itself, only
@ -62,6 +72,12 @@ class BaseMonitor:
raise TypeError(msg) raise TypeError(msg)
return object.__new__(cls) return object.__new__(cls)
def _is_valid_name(self, name:str)->None:
"""Validation check for 'name' variable from main constructor. Is
automatically called during initialisation. This does not need to be
overridden by child classes."""
valid_string(name, VALID_MONITOR_NAME_CHARS)
def _is_valid_patterns(self, patterns:Dict[str,BasePattern])->None: def _is_valid_patterns(self, patterns:Dict[str,BasePattern])->None:
"""Validation check for 'patterns' variable from main constructor. Must """Validation check for 'patterns' variable from main constructor. Must
be implemented by any child class.""" be implemented by any child class."""

View File

@ -21,6 +21,9 @@ CHAR_NUMERIC = '0123456789'
VALID_NAME_CHARS = CHAR_UPPERCASE + CHAR_LOWERCASE + CHAR_NUMERIC + "_-" VALID_NAME_CHARS = CHAR_UPPERCASE + CHAR_LOWERCASE + CHAR_NUMERIC + "_-"
VALID_CONDUCTOR_NAME_CHARS = VALID_NAME_CHARS
VALID_HANDLER_NAME_CHARS = VALID_NAME_CHARS
VALID_MONITOR_NAME_CHARS = VALID_NAME_CHARS
VALID_RECIPE_NAME_CHARS = VALID_NAME_CHARS VALID_RECIPE_NAME_CHARS = VALID_NAME_CHARS
VALID_PATTERN_NAME_CHARS = VALID_NAME_CHARS VALID_PATTERN_NAME_CHARS = VALID_NAME_CHARS
VALID_RULE_NAME_CHARS = VALID_NAME_CHARS VALID_RULE_NAME_CHARS = VALID_NAME_CHARS

View File

@ -27,3 +27,12 @@ def generate_rule_id():
def generate_job_id(): def generate_job_id():
return _generate_id(prefix="job_") return _generate_id(prefix="job_")
def generate_conductor_id():
return _generate_id(prefix="conductor_")
def generate_handler_id():
return _generate_id(prefix="handler_")
def generate_monitor_id():
return _generate_id(prefix="monitor_")

View File

@ -129,12 +129,12 @@ class WatchdogMonitor(BaseMonitor):
def __init__(self, base_dir:str, patterns:Dict[str,FileEventPattern], def __init__(self, base_dir:str, patterns:Dict[str,FileEventPattern],
recipes:Dict[str,BaseRecipe], autostart=False, settletime:int=1, recipes:Dict[str,BaseRecipe], autostart=False, settletime:int=1,
print:Any=sys.stdout, logging:int=0)->None: name:str="", print:Any=sys.stdout, logging:int=0)->None:
"""WatchdogEventHandler Constructor. This uses the watchdog module to """WatchdogEventHandler Constructor. This uses the watchdog module to
monitor a directory and all its sub-directories. Watchdog will provide monitor a directory and all its sub-directories. Watchdog will provide
the monitor with an caught events, with the monitor comparing them the monitor with an caught events, with the monitor comparing them
against its rules, and informing the runner of match.""" against its rules, and informing the runner of match."""
super().__init__(patterns, recipes) super().__init__(patterns, recipes, name=name)
self._is_valid_base_dir(base_dir) self._is_valid_base_dir(base_dir)
self.base_dir = base_dir self.base_dir = base_dir
check_type(settletime, int, hint="WatchdogMonitor.settletime") check_type(settletime, int, hint="WatchdogMonitor.settletime")

View File

@ -69,14 +69,14 @@ class PapermillHandler(BaseHandler):
debug_level:int debug_level:int
# Where print messages are sent # Where print messages are sent
_print_target:Any _print_target:Any
def __init__(self, job_queue_dir:str=DEFAULT_JOB_QUEUE_DIR, def __init__(self, job_queue_dir:str=DEFAULT_JOB_QUEUE_DIR, name:str="",
print:Any=sys.stdout, logging:int=0)->None: print:Any=sys.stdout, logging:int=0)->None:
"""PapermillHandler Constructor. This creats jobs to be executed using """PapermillHandler Constructor. This creats jobs to be executed using
the papermill module. This does not run as a continuous thread to the papermill module. This does not run as a continuous thread to
handle execution, but is invoked according to a factory pattern using handle execution, but is invoked according to a factory pattern using
the handle function. Note that if this handler is given to a MeowRunner the handle function. Note that if this handler is given to a MeowRunner
object, the job_queue_dir will be overwridden.""" object, the job_queue_dir will be overwridden."""
super().__init__() super().__init__(name=name)
self._is_valid_job_queue_dir(job_queue_dir) self._is_valid_job_queue_dir(job_queue_dir)
self.job_queue_dir = job_queue_dir self.job_queue_dir = job_queue_dir
self._print_target, self.debug_level = setup_debugging(print, logging) self._print_target, self.debug_level = setup_debugging(print, logging)

View File

@ -59,14 +59,14 @@ class PythonHandler(BaseHandler):
debug_level:int debug_level:int
# Where print messages are sent # Where print messages are sent
_print_target:Any _print_target:Any
def __init__(self, job_queue_dir:str=DEFAULT_JOB_QUEUE_DIR, def __init__(self, job_queue_dir:str=DEFAULT_JOB_QUEUE_DIR, name:str="",
print:Any=sys.stdout, logging:int=0)->None: print:Any=sys.stdout, logging:int=0)->None:
"""PythonHandler Constructor. This creates jobs to be executed as """PythonHandler Constructor. This creates jobs to be executed as
python functions. This does not run as a continuous thread to python functions. This does not run as a continuous thread to
handle execution, but is invoked according to a factory pattern using handle execution, but is invoked according to a factory pattern using
the handle function. Note that if this handler is given to a MeowRunner the handle function. Note that if this handler is given to a MeowRunner
object, the job_queue_dir will be overwridden but its""" object, the job_queue_dir will be overwridden but its"""
super().__init__() super().__init__(name=name)
self._is_valid_job_queue_dir(job_queue_dir) self._is_valid_job_queue_dir(job_queue_dir)
self.job_queue_dir = job_queue_dir self.job_queue_dir = job_queue_dir
self._print_target, self.debug_level = setup_debugging(print, logging) self._print_target, self.debug_level = setup_debugging(print, logging)

View File

@ -38,10 +38,19 @@ class MeowTests(unittest.TestCase):
super().tearDown() super().tearDown()
teardown() teardown()
# Test LocalPythonConductor creation and job types # Test LocalPythonConductor creation
def testLocalPythonConductorCreation(self)->None: def testLocalPythonConductorCreation(self)->None:
LocalPythonConductor() LocalPythonConductor()
# Test LocalPythonConductor naming
def testLocalPythonConductorNaming(self)->None:
test_name = "test_name"
conductor = LocalPythonConductor(name=test_name)
self.assertEqual(conductor.name, test_name)
conductor = LocalPythonConductor()
self.assertTrue(conductor.name.startswith("conductor_"))
# Test LocalPythonConductor executes valid python jobs # Test LocalPythonConductor executes valid python jobs
def testLocalPythonConductorValidPythonJob(self)->None: def testLocalPythonConductorValidPythonJob(self)->None:
lpc = LocalPythonConductor( lpc = LocalPythonConductor(

View File

@ -36,7 +36,7 @@ def recipes_equal(tester, recipe_one, recipe_two):
tester.assertEqual(recipe_one.source, recipe_two.source) tester.assertEqual(recipe_one.source, recipe_two.source)
class CorrectnessTests(unittest.TestCase): class FileEventPatternTests(unittest.TestCase):
def setUp(self)->None: def setUp(self)->None:
super().setUp() super().setUp()
setup() setup()
@ -184,11 +184,29 @@ class CorrectnessTests(unittest.TestCase):
fep = FileEventPattern("name", "path", "recipe", "file", fep = FileEventPattern("name", "path", "recipe", "file",
sweep=bad_sweep) sweep=bad_sweep)
class WatchdogMonitorTests(unittest.TestCase):
def setUp(self)->None:
super().setUp()
setup()
def tearDown(self)->None:
super().tearDown()
teardown()
# Test WatchdogMonitor created # Test WatchdogMonitor created
def testWatchdogMonitorMinimum(self)->None: def testWatchdogMonitorMinimum(self)->None:
from_monitor = Pipe() from_monitor = Pipe()
WatchdogMonitor(TEST_MONITOR_BASE, {}, {}, from_monitor[1]) WatchdogMonitor(TEST_MONITOR_BASE, {}, {}, from_monitor[1])
# Test WatchdogMonitor naming
def testWatchdogMonitorNaming(self)->None:
test_name = "test_name"
monitor = WatchdogMonitor(TEST_MONITOR_BASE, {}, {}, name=test_name)
self.assertEqual(monitor.name, test_name)
monitor = WatchdogMonitor(TEST_MONITOR_BASE, {}, {})
self.assertTrue(monitor.name.startswith("monitor_"))
# Test WatchdogMonitor identifies expected events in base directory # Test WatchdogMonitor identifies expected events in base directory
def testWatchdogMonitorEventIdentificaion(self)->None: def testWatchdogMonitorEventIdentificaion(self)->None:
from_monitor_reader, from_monitor_writer = Pipe() from_monitor_reader, from_monitor_writer = Pipe()

View File

@ -111,10 +111,28 @@ class JupyterNotebookTests(unittest.TestCase):
"name", BAREBONES_NOTEBOOK, source=source) "name", BAREBONES_NOTEBOOK, source=source)
self.assertEqual(jnr.source, source) self.assertEqual(jnr.source, source)
class PapermillHandlerTests(unittest.TestCase):
def setUp(self)->None:
super().setUp()
setup()
def tearDown(self)->None:
super().tearDown()
teardown()
# Test PapermillHandler can be created # Test PapermillHandler can be created
def testPapermillHanderMinimum(self)->None: def testPapermillHanderMinimum(self)->None:
PapermillHandler(job_queue_dir=TEST_JOB_QUEUE) PapermillHandler(job_queue_dir=TEST_JOB_QUEUE)
# Test PapermillHandler naming
def testPapermillHandlerNaming(self)->None:
test_name = "test_name"
handler = PapermillHandler(name=test_name)
self.assertEqual(handler.name, test_name)
handler = PapermillHandler()
self.assertTrue(handler.name.startswith("handler_"))
# Test PapermillHandler will handle given events # Test PapermillHandler will handle given events
def testPapermillHandlerHandling(self)->None: def testPapermillHandlerHandling(self)->None:
from_handler_reader, from_handler_writer = Pipe() from_handler_reader, from_handler_writer = Pipe()
@ -453,7 +471,6 @@ class JupyterNotebookTests(unittest.TestCase):
self.assertEqual(recipe.name, "name") self.assertEqual(recipe.name, "name")
self.assertEqual(recipe.recipe, COMPLETE_NOTEBOOK) self.assertEqual(recipe.recipe, COMPLETE_NOTEBOOK)
class PythonTests(unittest.TestCase): class PythonTests(unittest.TestCase):
def setUp(self)->None: def setUp(self)->None:
super().setUp() super().setUp()
@ -513,10 +530,28 @@ class PythonTests(unittest.TestCase):
"name", BAREBONES_PYTHON_SCRIPT, requirements=requirements) "name", BAREBONES_PYTHON_SCRIPT, requirements=requirements)
self.assertEqual(pr.requirements, requirements) self.assertEqual(pr.requirements, requirements)
class PythonHandlerTests(unittest.TestCase):
def setUp(self)->None:
super().setUp()
setup()
def tearDown(self)->None:
super().tearDown()
teardown()
# Test PythonHandler can be created # Test PythonHandler can be created
def testPythonHandlerMinimum(self)->None: def testPythonHandlerMinimum(self)->None:
PythonHandler(job_queue_dir=TEST_JOB_QUEUE) PythonHandler(job_queue_dir=TEST_JOB_QUEUE)
# Test PythonHandler naming
def testPythonHandlerNaming(self)->None:
test_name = "test_name"
handler = PythonHandler(name=test_name)
self.assertEqual(handler.name, test_name)
handler = PythonHandler()
self.assertTrue(handler.name.startswith("handler_"))
# Test PythonHandler will handle given events # Test PythonHandler will handle given events
def testPythonHandlerHandling(self)->None: def testPythonHandlerHandling(self)->None:
from_handler_reader, from_handler_writer = Pipe() from_handler_reader, from_handler_writer = Pipe()