updated code so should work on windows, with exception of waiting on multiple connections

This commit is contained in:
PatchOfScotland
2023-02-08 14:46:03 +01:00
parent d787e37adc
commit a1451881ae
17 changed files with 280 additions and 205 deletions

View File

@ -19,3 +19,8 @@ Pytest unittests are provided within the 'tests directory, as well as a script *
with individual tests runnable with: with individual tests runnable with:
pytest test_runner.py::MeowTests::testMeowRunnerLinkedPythonExecution -W ignore::DeprecationWarning pytest test_runner.py::MeowTests::testMeowRunnerLinkedPythonExecution -W ignore::DeprecationWarning
to run locally, update your '~/.bashrc' file to inclue:
# Manually added to get local testing of Python files working easier
export PYTHONPATH=/home/patch/Documents/Research/Python

View File

@ -9,7 +9,7 @@ import os
import shutil import shutil
from datetime import datetime from datetime import datetime
from typing import Any, Tuple from typing import Any, Tuple, Dict
from core.correctness.vars import JOB_TYPE_PYTHON, PYTHON_FUNC, JOB_STATUS, \ from core.correctness.vars import JOB_TYPE_PYTHON, PYTHON_FUNC, JOB_STATUS, \
STATUS_RUNNING, JOB_START_TIME, PYTHON_EXECUTION_BASE, JOB_ID, META_FILE, \ STATUS_RUNNING, JOB_START_TIME, PYTHON_EXECUTION_BASE, JOB_ID, META_FILE, \
@ -25,7 +25,7 @@ class LocalPythonConductor(BaseConductor):
def __init__(self)->None: def __init__(self)->None:
super().__init__() super().__init__()
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. This conductor will accept any Python job type""" process it or not. This conductor will accept any Python job type"""
try: try:
@ -36,7 +36,7 @@ class LocalPythonConductor(BaseConductor):
pass pass
return False, str(e) return False, str(e)
def execute(self, job:dict[str,Any])->None: def execute(self, job:Dict[str,Any])->None:
valid_job(job) valid_job(job)
job_dir = os.path.join(job[PYTHON_EXECUTION_BASE], job[JOB_ID]) job_dir = os.path.join(job[PYTHON_EXECUTION_BASE], job[JOB_ID])

View File

@ -8,7 +8,8 @@ Author(s): David Marchant
from datetime import datetime from datetime import datetime
from inspect import signature from inspect import signature
from os.path import sep, exists, isfile, isdir, dirname from os.path import sep, exists, isfile, isdir, dirname
from typing import Any, _SpecialForm, Union, Tuple, get_origin, get_args from typing import Any, _SpecialForm, Union, Tuple, Type, Dict, List, \
get_origin, get_args
from core.correctness.vars import VALID_PATH_CHARS, get_not_imp_msg, \ from core.correctness.vars import VALID_PATH_CHARS, get_not_imp_msg, \
EVENT_TYPE, EVENT_PATH, JOB_EVENT, JOB_TYPE, JOB_ID, JOB_PATTERN, \ EVENT_TYPE, EVENT_PATH, JOB_EVENT, JOB_TYPE, JOB_ID, JOB_PATTERN, \
@ -33,7 +34,7 @@ WATCHDOG_EVENT_KEYS = {
# Required keys in job dict # Required keys in job dict
JOB_KEYS = { JOB_KEYS = {
JOB_TYPE: str, JOB_TYPE: str,
JOB_EVENT: dict, JOB_EVENT: Dict,
JOB_ID: str, JOB_ID: str,
JOB_PATTERN: Any, JOB_PATTERN: Any,
JOB_RECIPE: Any, JOB_RECIPE: Any,
@ -42,7 +43,7 @@ JOB_KEYS = {
JOB_CREATE_TIME: datetime, JOB_CREATE_TIME: datetime,
} }
def check_type(variable:Any, expected_type:type, alt_types:list[type]=[], def check_type(variable:Any, expected_type:Type, alt_types:List[Type]=[],
or_none:bool=False)->None: or_none:bool=False)->None:
"""Checks if a given variable is of the expected type. Raises TypeError or """Checks if a given variable is of the expected type. Raises TypeError or
ValueError as appropriate if any issues are encountered.""" ValueError as appropriate if any issues are encountered."""
@ -52,6 +53,11 @@ def check_type(variable:Any, expected_type:type, alt_types:list[type]=[],
if get_origin(expected_type) is Union: if get_origin(expected_type) is Union:
type_list = list(get_args(expected_type)) type_list = list(get_args(expected_type))
type_list = type_list + alt_types type_list = type_list + alt_types
# # If we have any types from typing, then update to allow checks against
# # their base types too
# for t in type_list:
# if get_origin(t):
# type_list.append(get_origin(t))
# Only accept None if explicitly allowed # Only accept None if explicitly allowed
if variable is None: if variable is None:
@ -130,16 +136,16 @@ def valid_string(variable:str, valid_chars:str, min_length:int=1)->None:
"%s" % (char, valid_chars) "%s" % (char, valid_chars)
) )
def valid_dict(variable:dict[Any, Any], key_type:type, value_type:type, def valid_dict(variable:Dict[Any, Any], key_type:Type, value_type:Type,
required_keys:list[Any]=[], optional_keys:list[Any]=[], required_keys:List[Any]=[], optional_keys:List[Any]=[],
strict:bool=True, min_length:int=1)->None: strict:bool=True, min_length:int=1)->None:
"""Checks that a given dictionary is valid. Key and Value types are """Checks that a given dictionary is valid. Key and Value types are
enforced, as are required and optional keys. Will raise ValueError, enforced, as are required and optional keys. Will raise ValueError,
TypeError or KeyError depending on the problem encountered.""" TypeError or KeyError depending on the problem encountered."""
# Validate inputs # Validate inputs
check_type(variable, dict) check_type(variable, Dict)
check_type(key_type, type, alt_types=[_SpecialForm]) check_type(key_type, Type, alt_types=[_SpecialForm])
check_type(value_type, type, alt_types=[_SpecialForm]) check_type(value_type, Type, alt_types=[_SpecialForm])
check_type(required_keys, list) check_type(required_keys, list)
check_type(optional_keys, list) check_type(optional_keys, list)
check_type(strict, bool) check_type(strict, bool)
@ -172,11 +178,11 @@ def valid_dict(variable:dict[Any, Any], key_type:type, value_type:type,
raise ValueError(f"Unexpected key '{k}' should not be present " raise ValueError(f"Unexpected key '{k}' should not be present "
f"in dict '{variable}'") f"in dict '{variable}'")
def valid_list(variable:list[Any], entry_type:type, def valid_list(variable:List[Any], entry_type:Type,
alt_types:list[type]=[], min_length:int=1)->None: alt_types:List[Type]=[], min_length:int=1)->None:
"""Checks that a given list is valid. Value types are checked and a """Checks that a given list is valid. Value types are checked and a
ValueError or TypeError is raised if a problem is encountered.""" ValueError or TypeError is raised if a problem is encountered."""
check_type(variable, list) check_type(variable, List)
# Check length meets minimum # Check length meets minimum
if len(variable) < min_length: if len(variable) < min_length:
@ -256,11 +262,11 @@ def setup_debugging(print:Any=None, logging:int=0)->Tuple[Any,int]:
return print, logging return print, logging
def valid_meow_dict(meow_dict:dict[str,Any], msg:str, def valid_meow_dict(meow_dict:Dict[str,Any], msg:str,
keys:dict[str,type])->None: keys:Dict[str,Type])->None:
"""Check given dictionary expresses a meow construct. This won't do much """Check given dictionary expresses a meow construct. This won't do much
directly, but is called by more specific validation functions.""" directly, but is called by more specific validation functions."""
check_type(meow_dict, dict) check_type(meow_dict, Dict)
# Check we have all the required keys, and they are all of the expected # Check we have all the required keys, and they are all of the expected
# type # type
for key, value_type in keys.items(): for key, value_type in keys.items():
@ -268,13 +274,13 @@ def valid_meow_dict(meow_dict:dict[str,Any], msg:str,
raise KeyError(f"{msg} require key '{key}'") raise KeyError(f"{msg} require key '{key}'")
check_type(meow_dict[key], value_type) check_type(meow_dict[key], value_type)
def valid_event(event:dict[str,Any])->None: def valid_event(event:Dict[str,Any])->None:
"""Check that a given dict expresses a meow event.""" """Check that a given dict expresses a meow event."""
valid_meow_dict(event, "Event", EVENT_KEYS) valid_meow_dict(event, "Event", EVENT_KEYS)
def valid_job(job:dict[str,Any])->None: def valid_job(job:Dict[str,Any])->None:
"""Check that a given dict expresses a meow job.""" """Check that a given dict expresses a meow job."""
valid_meow_dict(job, "Job", JOB_KEYS) valid_meow_dict(job, "Job", JOB_KEYS)
def valid_watchdog_event(event:dict[str,Any])->None: def valid_watchdog_event(event:Dict[str,Any])->None:
valid_meow_dict(event, "Watchdog event", WATCHDOG_EVENT_KEYS) valid_meow_dict(event, "Watchdog event", WATCHDOG_EVENT_KEYS)

View File

@ -7,11 +7,12 @@ import os
import yaml import yaml
from datetime import datetime from datetime import datetime
from typing import List
from multiprocessing.connection import Connection, wait as multi_wait from multiprocessing.connection import Connection, wait as multi_wait
from multiprocessing.queues import Queue from multiprocessing.queues import Queue
from papermill.translators import papermill_translators from papermill.translators import papermill_translators
from typing import Any from typing import Any, Dict
from random import SystemRandom from random import SystemRandom
from core.correctness.validation import check_type, valid_existing_file_path, \ from core.correctness.validation import check_type, valid_existing_file_path, \
@ -36,7 +37,7 @@ KEYWORD_JOB = "{JOB}"
#TODO Make this guaranteed unique #TODO Make this guaranteed unique
def generate_id(prefix:str="", length:int=16, existing_ids:list[str]=[], def generate_id(prefix:str="", length:int=16, existing_ids:List[str]=[],
charset:str=CHAR_UPPERCASE+CHAR_LOWERCASE, attempts:int=24): charset:str=CHAR_UPPERCASE+CHAR_LOWERCASE, attempts:int=24):
random_length = max(length - len(prefix), 0) random_length = max(length - len(prefix), 0)
for _ in range(attempts): for _ in range(attempts):
@ -47,9 +48,11 @@ def generate_id(prefix:str="", length:int=16, existing_ids:list[str]=[],
raise ValueError(f"Could not generate ID unique from '{existing_ids}' " raise ValueError(f"Could not generate ID unique from '{existing_ids}' "
f"using values '{charset}' and length of '{length}'.") f"using values '{charset}' and length of '{length}'.")
def wait(inputs:list[VALID_CHANNELS])->list[VALID_CHANNELS]: def wait(inputs:List[VALID_CHANNELS])->List[VALID_CHANNELS]:
all_connections = [i for i in inputs if type(i) is Connection] \ all_connections = [i for i in inputs if type(i) is Connection] \
+ [i._reader for i in inputs if type(i) is Queue] + [i._reader for i in inputs if type(i) is Queue]
for i in inputs:
print(type(i))
ready = multi_wait(all_connections) ready = multi_wait(all_connections)
ready_inputs = [i for i in inputs if \ ready_inputs = [i for i in inputs if \
@ -72,7 +75,6 @@ def _get_file_sha256(file_path):
def get_file_hash(file_path:str, hash:str): def get_file_hash(file_path:str, hash:str):
check_type(hash, str) check_type(hash, str)
import os
valid_existing_file_path(file_path) valid_existing_file_path(file_path)
valid_hashes = { valid_hashes = {
@ -80,7 +82,7 @@ def get_file_hash(file_path:str, hash:str):
} }
if hash not in valid_hashes: if hash not in valid_hashes:
raise KeyError(f"Cannot use hash '{hash}'. Valid are " raise KeyError(f"Cannot use hash '{hash}'. Valid are "
"'{list(valid_hashes.keys())}") f"'{list(valid_hashes.keys())}")
return valid_hashes[hash](file_path) return valid_hashes[hash](file_path)
@ -169,7 +171,7 @@ def read_notebook(filepath:str):
with open(filepath, 'r') as read_file: with open(filepath, 'r') as read_file:
return json.load(read_file) return json.load(read_file)
def write_notebook(source:dict[str,Any], filename:str): def write_notebook(source:Dict[str,Any], filename:str):
""" """
Writes the given notebook source code to a given filename. Writes the given notebook source code to a given filename.
@ -183,10 +185,10 @@ def write_notebook(source:dict[str,Any], filename:str):
json.dump(source, job_file) json.dump(source, job_file)
# Adapted from: https://github.com/rasmunk/notebook_parameterizer # Adapted from: https://github.com/rasmunk/notebook_parameterizer
def parameterize_jupyter_notebook(jupyter_notebook:dict[str,Any], def parameterize_jupyter_notebook(jupyter_notebook:Dict[str,Any],
parameters:dict[str,Any], expand_env_values:bool=False)->dict[str,Any]: parameters:Dict[str,Any], expand_env_values:bool=False)->Dict[str,Any]:
nbformat.validate(jupyter_notebook) nbformat.validate(jupyter_notebook)
check_type(parameters, dict) check_type(parameters, Dict)
if jupyter_notebook["nbformat"] != 4: if jupyter_notebook["nbformat"] != 4:
raise Warning( raise Warning(
@ -256,10 +258,10 @@ def parameterize_jupyter_notebook(jupyter_notebook:dict[str,Any],
return output_notebook return output_notebook
def parameterize_python_script(script:list[str], parameters:dict[str,Any], def parameterize_python_script(script:List[str], parameters:Dict[str,Any],
expand_env_values:bool=False)->dict[str,Any]: expand_env_values:bool=False)->Dict[str,Any]:
check_script(script) check_script(script)
check_type(parameters, dict) check_type(parameters, Dict)
output_script = copy.deepcopy(script) output_script = copy.deepcopy(script)
@ -302,8 +304,8 @@ def print_debug(print_target, debug_level, msg, level)->None:
status = "WARNING" status = "WARNING"
print(f"{status}: {msg}", file=print_target) print(f"{status}: {msg}", file=print_target)
def replace_keywords(old_dict:dict[str,str], job_id:str, src_path:str, def replace_keywords(old_dict:Dict[str,str], job_id:str, src_path:str,
monitor_base:str)->dict[str,str]: monitor_base:str)->Dict[str,str]:
"""Function to replace all MEOW magic words in a dictionary with dynamic """Function to replace all MEOW magic words in a dictionary with dynamic
values.""" values."""
new_dict = {} new_dict = {}
@ -332,8 +334,8 @@ def replace_keywords(old_dict:dict[str,str], job_id:str, src_path:str,
return new_dict return new_dict
def create_event(event_type:str, path:str, rule:Any, extras:dict[Any,Any]={} def create_event(event_type:str, path:str, rule:Any, extras:Dict[Any,Any]={}
)->dict[Any,Any]: )->Dict[Any,Any]:
"""Function to create a MEOW dictionary.""" """Function to create a MEOW dictionary."""
return { return {
**extras, **extras,
@ -343,7 +345,7 @@ def create_event(event_type:str, path:str, rule:Any, extras:dict[Any,Any]={}
} }
def create_watchdog_event(path:str, rule:Any, base:str, hash:str, def create_watchdog_event(path:str, rule:Any, base:str, hash:str,
extras:dict[Any,Any]={})->dict[Any,Any]: extras:Dict[Any,Any]={})->Dict[Any,Any]:
"""Function to create a MEOW event dictionary.""" """Function to create a MEOW event dictionary."""
return create_event( return create_event(
EVENT_TYPE_WATCHDOG, EVENT_TYPE_WATCHDOG,
@ -358,8 +360,8 @@ def create_watchdog_event(path:str, rule:Any, base:str, hash:str,
} }
) )
def create_job(job_type:str, event:dict[str,Any], extras:dict[Any,Any]={} def create_job(job_type:str, event:Dict[str,Any], extras:Dict[Any,Any]={}
)->dict[Any,Any]: )->Dict[Any,Any]:
"""Function to create a MEOW job dictionary.""" """Function to create a MEOW job dictionary."""
job_dict = { job_dict = {
#TODO compress event? #TODO compress event?
@ -376,7 +378,7 @@ def create_job(job_type:str, event:dict[str,Any], extras:dict[Any,Any]={}
return {**extras, **job_dict} return {**extras, **job_dict}
def lines_to_string(lines:list[str])->str: def lines_to_string(lines:List[str])->str:
"""Function to convert a list of str lines, into one continuous string """Function to convert a list of str lines, into one continuous string
separated by newline characters""" separated by newline characters"""
return "\n".join(lines) return "\n".join(lines)

View File

@ -12,7 +12,7 @@ import itertools
import sys import sys
from copy import deepcopy from copy import deepcopy
from typing import Any, Union, Tuple from typing import Any, Union, Tuple, Dict, List
from core.correctness.vars import VALID_RECIPE_NAME_CHARS, \ from core.correctness.vars import VALID_RECIPE_NAME_CHARS, \
VALID_PATTERN_NAME_CHARS, VALID_RULE_NAME_CHARS, VALID_CHANNELS, \ VALID_PATTERN_NAME_CHARS, VALID_RULE_NAME_CHARS, VALID_CHANNELS, \
@ -28,11 +28,11 @@ class BaseRecipe:
# Actual code to run # Actual code to run
recipe:Any recipe:Any
# Possible parameters that could be overridden by a Pattern # Possible parameters that could be overridden by a Pattern
parameters:dict[str, Any] parameters:Dict[str, Any]
# Additional configuration options # Additional configuration options
requirements:dict[str, Any] requirements:Dict[str, Any]
def __init__(self, name:str, recipe:Any, parameters:dict[str,Any]={}, def __init__(self, name:str, recipe:Any, parameters:Dict[str,Any]={},
requirements:dict[str,Any]={}): requirements:Dict[str,Any]={}):
"""BaseRecipe Constructor. This will check that any class inheriting """BaseRecipe 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."""
@ -84,14 +84,14 @@ class BasePattern:
# An identifier of a recipe # An identifier of a recipe
recipe:str recipe:str
# Parameters to be overridden in the recipe # Parameters to be overridden in the recipe
parameters:dict[str,Any] parameters:Dict[str,Any]
# Parameters showing the potential outputs of a recipe # Parameters showing the potential outputs of a recipe
outputs:dict[str,Any] outputs:Dict[str,Any]
# A collection of variables to be swept over for job scheduling # A collection of variables to be swept over for job scheduling
sweep:dict[str,Any] sweep:Dict[str,Any]
def __init__(self, name:str, recipe:str, parameters:dict[str,Any]={}, def __init__(self, name:str, recipe:str, parameters:Dict[str,Any]={},
outputs:dict[str,Any]={}, sweep:dict[str,Any]={}): outputs:Dict[str,Any]={}, sweep:Dict[str,Any]={}):
"""BasePattern Constructor. This will check that any class inheriting """BasePattern 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."""
@ -138,11 +138,11 @@ class BasePattern:
be implemented by any child class.""" be implemented by any child class."""
pass pass
def _is_valid_sweep(self, sweep:dict[str,Union[int,float,complex]])->None: def _is_valid_sweep(self, sweep:Dict[str,Union[int,float,complex]])->None:
"""Validation check for 'sweep' variable from main constructor. This """Validation check for 'sweep' variable from main constructor. This
function is implemented to check for the types given in the signature, function is implemented to check for the types given in the signature,
and must be overridden if these differ.""" and must be overridden if these differ."""
check_type(sweep, dict) check_type(sweep, Dict)
if not sweep: if not sweep:
return return
for _, v in sweep.items(): for _, v in sweep.items():
@ -175,7 +175,7 @@ class BasePattern:
"value where the end point is smaller than the start." "value where the end point is smaller than the start."
) )
def expand_sweeps(self)->list[Tuple[str,Any]]: def expand_sweeps(self)->List[Tuple[str,Any]]:
"""Function to get all combinations of sweep parameters""" """Function to get all combinations of sweep parameters"""
values_dict = {} values_dict = {}
# get a collection of a individual sweep values # get a collection of a individual sweep values
@ -259,17 +259,17 @@ class BaseRule:
class BaseMonitor: class BaseMonitor:
# A collection of patterns # A collection of patterns
_patterns: dict[str, BasePattern] _patterns: Dict[str, BasePattern]
# A collection of recipes # A collection of recipes
_recipes: dict[str, BaseRecipe] _recipes: Dict[str, BaseRecipe]
# A collection of rules derived from _patterns and _recipes # A collection of rules derived from _patterns and _recipes
_rules: dict[str, BaseRule] _rules: Dict[str, BaseRule]
# A channel for sending messages to the runner. Note that this is not # A channel for sending messages to the runner. Note that this is not
# initialised within the constructor, but within the runner when passed the # initialised within the constructor, but within the runner when passed the
# 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])->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."""
@ -302,12 +302,12 @@ class BaseMonitor:
raise TypeError(msg) raise TypeError(msg)
return object.__new__(cls) return object.__new__(cls)
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."""
pass pass
def _is_valid_recipes(self, recipes:dict[str,BaseRecipe])->None: def _is_valid_recipes(self, recipes:Dict[str,BaseRecipe])->None:
"""Validation check for 'recipes' variable from main constructor. Must """Validation check for 'recipes' variable from main constructor. Must
be implemented by any child class.""" be implemented by any child class."""
pass pass
@ -337,7 +337,7 @@ class BaseMonitor:
implemented by any child process.""" implemented by any child process."""
pass pass
def get_patterns(self)->dict[str,BasePattern]: def get_patterns(self)->Dict[str,BasePattern]:
"""Function to get a dictionary of all current pattern definitions. """Function to get a dictionary of all current pattern definitions.
Must be implemented by any child process.""" Must be implemented by any child process."""
pass pass
@ -357,12 +357,12 @@ class BaseMonitor:
implemented by any child process.""" implemented by any child process."""
pass pass
def get_recipes(self)->dict[str,BaseRecipe]: def get_recipes(self)->Dict[str,BaseRecipe]:
"""Function to get a dictionary of all current recipe definitions. """Function to get a dictionary of all current recipe definitions.
Must be implemented by any child process.""" Must be implemented by any child process."""
pass pass
def get_rules(self)->dict[str,BaseRule]: def get_rules(self)->Dict[str,BaseRule]:
"""Function to get a dictionary of all current rule definitions. """Function to get a dictionary of all current rule definitions.
Must be implemented by any child process.""" Must be implemented by any child process."""
pass pass
@ -387,12 +387,12 @@ class BaseHandler:
raise TypeError(msg) raise TypeError(msg)
return object.__new__(cls) return object.__new__(cls)
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."""
pass pass
def handle(self, event:dict[str,Any])->None: def handle(self, event:Dict[str,Any])->None:
"""Function to handle a given event. Must be implemented by any child """Function to handle a given event. Must be implemented by any child
process.""" process."""
pass pass
@ -413,28 +413,28 @@ class BaseConductor:
raise TypeError(msg) raise TypeError(msg)
return object.__new__(cls) return object.__new__(cls)
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."""
pass pass
def execute(self, job:dict[str,Any])->None: def execute(self, job:Dict[str,Any])->None:
"""Function to execute a given job. Must be implemented by any child """Function to execute a given job. Must be implemented by any child
process.""" process."""
pass pass
def create_rules(patterns:Union[dict[str,BasePattern],list[BasePattern]], def create_rules(patterns:Union[Dict[str,BasePattern],List[BasePattern]],
recipes:Union[dict[str,BaseRecipe],list[BaseRecipe]], recipes:Union[Dict[str,BaseRecipe],List[BaseRecipe]],
new_rules:list[BaseRule]=[])->dict[str,BaseRule]: new_rules:List[BaseRule]=[])->Dict[str,BaseRule]:
"""Function to create any valid rules from a given collection of patterns """Function to create any valid rules from a given collection of patterns
and recipes. All inbuilt rule types are considered, with additional and recipes. All inbuilt rule types are considered, with additional
definitions provided through the 'new_rules' variable. Note that any definitions provided through the 'new_rules' variable. Note that any
provided pattern and recipe dictionaries must be keyed with the provided pattern and recipe dictionaries must be keyed with the
corresponding pattern and recipe names.""" corresponding pattern and recipe names."""
# Validation of inputs # Validation of inputs
check_type(patterns, dict, alt_types=[list]) check_type(patterns, Dict, alt_types=[List])
check_type(recipes, dict, alt_types=[list]) check_type(recipes, Dict, alt_types=[List])
valid_list(new_rules, BaseRule, min_length=0) valid_list(new_rules, BaseRule, min_length=0)
# Convert a pattern list to a dictionary # Convert a pattern list to a dictionary
@ -477,7 +477,7 @@ def create_rules(patterns:Union[dict[str,BasePattern],list[BasePattern]],
return generated_rules return generated_rules
def create_rule(pattern:BasePattern, recipe:BaseRecipe, def create_rule(pattern:BasePattern, recipe:BaseRecipe,
new_rules:list[BaseRule]=[])->BaseRule: new_rules:List[BaseRule]=[])->BaseRule:
"""Function to create a valid rule from a given pattern and recipe. All """Function to create a valid rule from a given pattern and recipe. All
inbuilt rule types are considered, with additional definitions provided inbuilt rule types are considered, with additional definitions provided
through the 'new_rules' variable.""" through the 'new_rules' variable."""

View File

@ -12,7 +12,7 @@ import threading
from multiprocessing import Pipe from multiprocessing import Pipe
from random import randrange from random import randrange
from typing import Any, Union from typing import Any, Union, Dict, List
from core.correctness.vars import DEBUG_WARNING, DEBUG_INFO, EVENT_TYPE, \ from core.correctness.vars import DEBUG_WARNING, DEBUG_INFO, EVENT_TYPE, \
VALID_CHANNELS, JOB_ID, META_FILE VALID_CHANNELS, JOB_ID, META_FILE
@ -24,18 +24,18 @@ from core.meow import BaseHandler, BaseMonitor, BaseConductor
class MeowRunner: class MeowRunner:
# A collection of all monitors in the runner # A collection of all monitors in the runner
monitors:list[BaseMonitor] monitors:List[BaseMonitor]
# A collection of all handlers in the runner # A collection of all handlers in the runner
handlers:list[BaseHandler] handlers:List[BaseHandler]
# A collection of all conductors in the runner # A collection of all conductors in the runner
conductors:list[BaseConductor] conductors:List[BaseConductor]
# A collection of all channels from each monitor # A collection of all channels from each monitor
from_monitors: list[VALID_CHANNELS] from_monitors: List[VALID_CHANNELS]
# A collection of all channels from each handler # A collection of all channels from each handler
from_handlers: list[VALID_CHANNELS] from_handlers: List[VALID_CHANNELS]
def __init__(self, monitors:Union[BaseMonitor,list[BaseMonitor]], def __init__(self, monitors:Union[BaseMonitor,List[BaseMonitor]],
handlers:Union[BaseHandler,list[BaseHandler]], handlers:Union[BaseHandler,List[BaseHandler]],
conductors:Union[BaseConductor,list[BaseConductor]], conductors:Union[BaseConductor,List[BaseConductor]],
print:Any=sys.stdout, logging:int=0)->None: print:Any=sys.stdout, logging:int=0)->None:
"""MeowRunner constructor. This connects all provided monitors, """MeowRunner constructor. This connects all provided monitors,
handlers and conductors according to what events and jobs they produce handlers and conductors according to what events and jobs they produce
@ -178,7 +178,7 @@ class MeowRunner:
] ]
self.execute_job(conductor, job) self.execute_job(conductor, job)
def handle_event(self, handler:BaseHandler, event:dict[str:Any])->None: def handle_event(self, handler:BaseHandler, event:Dict[str,Any])->None:
"""Function for a given handler to handle a given event, without """Function for a given handler to handle a given event, without
crashing the runner in the event of a problem.""" crashing the runner in the event of a problem."""
print_debug(self._print_target, self.debug_level, print_debug(self._print_target, self.debug_level,
@ -193,7 +193,7 @@ class MeowRunner:
"Something went wrong during handling for event " "Something went wrong during handling for event "
f"'{event[EVENT_TYPE]}'. {e}", DEBUG_INFO) f"'{event[EVENT_TYPE]}'. {e}", DEBUG_INFO)
def execute_job(self, conductor:BaseConductor, job:dict[str:Any])->None: def execute_job(self, conductor:BaseConductor, job:Dict[str,Any])->None:
"""Function for a given conductor to execute a given job, without """Function for a given conductor to execute a given job, without
crashing the runner in the event of a problem.""" crashing the runner in the event of a problem."""
print_debug(self._print_target, self.debug_level, print_debug(self._print_target, self.debug_level,
@ -306,22 +306,22 @@ class MeowRunner:
"Job conductor thread stopped", DEBUG_INFO) "Job conductor thread stopped", DEBUG_INFO)
def _is_valid_monitors(self, def _is_valid_monitors(self,
monitors:Union[BaseMonitor,list[BaseMonitor]])->None: monitors:Union[BaseMonitor,List[BaseMonitor]])->None:
"""Validation check for 'monitors' variable from main constructor.""" """Validation check for 'monitors' variable from main constructor."""
check_type(monitors, BaseMonitor, alt_types=[list]) check_type(monitors, BaseMonitor, alt_types=[List])
if type(monitors) == list: if type(monitors) == list:
valid_list(monitors, BaseMonitor, min_length=1) valid_list(monitors, BaseMonitor, min_length=1)
def _is_valid_handlers(self, def _is_valid_handlers(self,
handlers:Union[BaseHandler,list[BaseHandler]])->None: handlers:Union[BaseHandler,List[BaseHandler]])->None:
"""Validation check for 'handlers' variable from main constructor.""" """Validation check for 'handlers' variable from main constructor."""
check_type(handlers, BaseHandler, alt_types=[list]) check_type(handlers, BaseHandler, alt_types=[List])
if type(handlers) == list: if type(handlers) == list:
valid_list(handlers, BaseHandler, min_length=1) valid_list(handlers, BaseHandler, min_length=1)
def _is_valid_conductors(self, def _is_valid_conductors(self,
conductors:Union[BaseConductor,list[BaseConductor]])->None: conductors:Union[BaseConductor,List[BaseConductor]])->None:
"""Validation check for 'conductors' variable from main constructor.""" """Validation check for 'conductors' variable from main constructor."""
check_type(conductors, BaseConductor, alt_types=[list]) check_type(conductors, BaseConductor, alt_types=[List])
if type(conductors) == list: if type(conductors) == list:
valid_list(conductors, BaseConductor, min_length=1) valid_list(conductors, BaseConductor, min_length=1)

View File

@ -14,7 +14,7 @@ from copy import deepcopy
from fnmatch import translate from fnmatch import translate
from re import match from re import match
from time import time, sleep from time import time, sleep
from typing import Any, Union from typing import Any, Union, Dict, List
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler from watchdog.events import PatternMatchingEventHandler
@ -44,11 +44,11 @@ class FileEventPattern(BasePattern):
# The variable name given to the triggering file within recipe code # The variable name given to the triggering file within recipe code
triggering_file:str triggering_file:str
# Which types of event the pattern responds to # Which types of event the pattern responds to
event_mask:list[str] event_mask:List[str]
def __init__(self, name:str, triggering_path:str, recipe:str, def __init__(self, name:str, triggering_path:str, recipe:str,
triggering_file:str, event_mask:list[str]=_DEFAULT_MASK, triggering_file:str, event_mask:List[str]=_DEFAULT_MASK,
parameters:dict[str,Any]={}, outputs:dict[str,Any]={}, parameters:Dict[str,Any]={}, outputs:Dict[str,Any]={},
sweep:dict[str,Any]={}): sweep:Dict[str,Any]={}):
"""FileEventPattern Constructor. This is used to match against file """FileEventPattern Constructor. This is used to match against file
system events, as caught by the python watchdog module.""" system events, as caught by the python watchdog module."""
super().__init__(name, recipe, parameters, outputs, sweep) super().__init__(name, recipe, parameters, outputs, sweep)
@ -79,14 +79,14 @@ class FileEventPattern(BasePattern):
Called within parent BasePattern constructor.""" Called within parent BasePattern constructor."""
valid_string(recipe, VALID_RECIPE_NAME_CHARS) valid_string(recipe, VALID_RECIPE_NAME_CHARS)
def _is_valid_parameters(self, parameters:dict[str,Any])->None: def _is_valid_parameters(self, parameters:Dict[str,Any])->None:
"""Validation check for 'parameters' variable from main constructor. """Validation check for 'parameters' variable from main constructor.
Called within parent BasePattern constructor.""" Called within parent BasePattern constructor."""
valid_dict(parameters, str, Any, strict=False, min_length=0) valid_dict(parameters, str, Any, strict=False, min_length=0)
for k in parameters.keys(): for k in parameters.keys():
valid_string(k, VALID_VARIABLE_NAME_CHARS) valid_string(k, VALID_VARIABLE_NAME_CHARS)
def _is_valid_output(self, outputs:dict[str,str])->None: def _is_valid_output(self, outputs:Dict[str,str])->None:
"""Validation check for 'output' variable from main constructor. """Validation check for 'output' variable from main constructor.
Called within parent BasePattern constructor.""" Called within parent BasePattern constructor."""
valid_dict(outputs, str, str, strict=False, min_length=0) valid_dict(outputs, str, str, strict=False, min_length=0)
@ -101,7 +101,7 @@ class FileEventPattern(BasePattern):
raise ValueError(f"Invalid event mask '{mask}'. Valid are: " raise ValueError(f"Invalid event mask '{mask}'. Valid are: "
f"{FILE_EVENTS}") f"{FILE_EVENTS}")
def _is_valid_sweep(self, sweep: dict[str,Union[int,float,complex]]) -> None: def _is_valid_sweep(self, sweep: Dict[str,Union[int,float,complex]]) -> None:
"""Validation check for 'sweep' variable from main constructor.""" """Validation check for 'sweep' variable from main constructor."""
return super()._is_valid_sweep(sweep) return super()._is_valid_sweep(sweep)
@ -124,8 +124,8 @@ class WatchdogMonitor(BaseMonitor):
#A lock to solve race conditions on '_rules' #A lock to solve race conditions on '_rules'
_rules_lock:threading.Lock _rules_lock:threading.Lock
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: 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
@ -189,7 +189,8 @@ class WatchdogMonitor(BaseMonitor):
# Use regex to match event paths against rule paths # Use regex to match event paths against rule paths
target_path = rule.pattern.triggering_path target_path = rule.pattern.triggering_path
recursive_regexp = translate(target_path) recursive_regexp = translate(target_path)
direct_regexp = recursive_regexp.replace('.*', '[^/]*') direct_regexp = recursive_regexp.replace(
'.*', '[^'+ os.path.sep +']*')
recursive_hit = match(recursive_regexp, handle_path) recursive_hit = match(recursive_regexp, handle_path)
direct_hit = match(direct_regexp, handle_path) direct_hit = match(direct_regexp, handle_path)
@ -261,7 +262,7 @@ class WatchdogMonitor(BaseMonitor):
else: else:
self._identify_lost_rules(lost_pattern=pattern) self._identify_lost_rules(lost_pattern=pattern)
def get_patterns(self)->dict[str,FileEventPattern]: def get_patterns(self)->Dict[str,FileEventPattern]:
"""Function to get a dict of the currently defined patterns of the """Function to get a dict of the currently defined patterns of the
monitor. Note that the result is deep-copied, and so can be manipulated monitor. Note that the result is deep-copied, and so can be manipulated
without directly manipulating the internals of the monitor.""" without directly manipulating the internals of the monitor."""
@ -324,7 +325,7 @@ class WatchdogMonitor(BaseMonitor):
else: else:
self._identify_lost_rules(lost_recipe=recipe) self._identify_lost_rules(lost_recipe=recipe)
def get_recipes(self)->dict[str,BaseRecipe]: def get_recipes(self)->Dict[str,BaseRecipe]:
"""Function to get a dict of the currently defined recipes of the """Function to get a dict of the currently defined recipes of the
monitor. Note that the result is deep-copied, and so can be manipulated monitor. Note that the result is deep-copied, and so can be manipulated
without directly manipulating the internals of the monitor.""" without directly manipulating the internals of the monitor."""
@ -338,7 +339,7 @@ class WatchdogMonitor(BaseMonitor):
self._recipes_lock.release() self._recipes_lock.release()
return to_return return to_return
def get_rules(self)->dict[str,BaseRule]: def get_rules(self)->Dict[str,BaseRule]:
"""Function to get a dict of the currently defined rules of the """Function to get a dict of the currently defined rules of the
monitor. Note that the result is deep-copied, and so can be manipulated monitor. Note that the result is deep-copied, and so can be manipulated
without directly manipulating the internals of the monitor.""" without directly manipulating the internals of the monitor."""
@ -450,12 +451,12 @@ class WatchdogMonitor(BaseMonitor):
automatically called during initialisation.""" automatically called during initialisation."""
valid_existing_dir_path(base_dir) valid_existing_dir_path(base_dir)
def _is_valid_patterns(self, patterns:dict[str,FileEventPattern])->None: def _is_valid_patterns(self, patterns:Dict[str,FileEventPattern])->None:
"""Validation check for 'patterns' variable from main constructor. Is """Validation check for 'patterns' variable from main constructor. Is
automatically called during initialisation.""" automatically called during initialisation."""
valid_dict(patterns, str, FileEventPattern, min_length=0, strict=False) valid_dict(patterns, str, FileEventPattern, min_length=0, strict=False)
def _is_valid_recipes(self, recipes:dict[str,BaseRecipe])->None: def _is_valid_recipes(self, recipes:Dict[str,BaseRecipe])->None:
"""Validation check for 'recipes' variable from main constructor. Is """Validation check for 'recipes' variable from main constructor. Is
automatically called during initialisation.""" automatically called during initialisation."""
valid_dict(recipes, str, BaseRecipe, min_length=0, strict=False) valid_dict(recipes, str, BaseRecipe, min_length=0, strict=False)
@ -511,8 +512,8 @@ class WatchdogEventHandler(PatternMatchingEventHandler):
# A time to wait per event path, during which extra events are discared # A time to wait per event path, during which extra events are discared
_settletime:int _settletime:int
# TODO clean this struct occasionally # TODO clean this struct occasionally
# A dict of recent job timestamps # A Dict of recent job timestamps
_recent_jobs:dict[str, Any] _recent_jobs:Dict[str, Any]
# A lock to solve race conditions on '_recent_jobs' # A lock to solve race conditions on '_recent_jobs'
_recent_jobs_lock:threading.Lock _recent_jobs_lock:threading.Lock
def __init__(self, monitor:WatchdogMonitor, settletime:int=1): def __init__(self, monitor:WatchdogMonitor, settletime:int=1):

View File

@ -9,7 +9,7 @@ import os
import nbformat import nbformat
import sys import sys
from typing import Any, Tuple from typing import Any, Tuple, Dict
from core.correctness.validation import check_type, valid_string, \ from core.correctness.validation import check_type, valid_string, \
valid_dict, valid_path, valid_existing_dir_path, setup_debugging, \ valid_dict, valid_path, valid_existing_dir_path, setup_debugging, \
@ -28,8 +28,8 @@ from core.meow import BaseRecipe, BaseHandler
class JupyterNotebookRecipe(BaseRecipe): class JupyterNotebookRecipe(BaseRecipe):
# A path to the jupyter notebook used to create this recipe # A path to the jupyter notebook used to create this recipe
source:str source:str
def __init__(self, name:str, recipe:Any, parameters:dict[str,Any]={}, def __init__(self, name:str, recipe:Any, parameters:Dict[str,Any]={},
requirements:dict[str,Any]={}, source:str=""): requirements:Dict[str,Any]={}, source:str=""):
"""JupyterNotebookRecipe Constructor. This is used to execute analysis """JupyterNotebookRecipe Constructor. This is used to execute analysis
code using the papermill module.""" code using the papermill module."""
super().__init__(name, recipe, parameters, requirements) super().__init__(name, recipe, parameters, requirements)
@ -41,20 +41,20 @@ class JupyterNotebookRecipe(BaseRecipe):
if source: if source:
valid_path(source, extension=".ipynb", min_length=0) valid_path(source, extension=".ipynb", min_length=0)
def _is_valid_recipe(self, recipe:dict[str,Any])->None: def _is_valid_recipe(self, recipe:Dict[str,Any])->None:
"""Validation check for 'recipe' variable from main constructor. """Validation check for 'recipe' variable from main constructor.
Called within parent BaseRecipe constructor.""" Called within parent BaseRecipe constructor."""
check_type(recipe, dict) check_type(recipe, Dict)
nbformat.validate(recipe) nbformat.validate(recipe)
def _is_valid_parameters(self, parameters:dict[str,Any])->None: def _is_valid_parameters(self, parameters:Dict[str,Any])->None:
"""Validation check for 'parameters' variable from main constructor. """Validation check for 'parameters' variable from main constructor.
Called within parent BaseRecipe constructor.""" Called within parent BaseRecipe constructor."""
valid_dict(parameters, str, Any, strict=False, min_length=0) valid_dict(parameters, str, Any, strict=False, min_length=0)
for k in parameters.keys(): for k in parameters.keys():
valid_string(k, VALID_VARIABLE_NAME_CHARS) valid_string(k, VALID_VARIABLE_NAME_CHARS)
def _is_valid_requirements(self, requirements:dict[str,Any])->None: def _is_valid_requirements(self, requirements:Dict[str,Any])->None:
"""Validation check for 'requirements' variable from main constructor. """Validation check for 'requirements' variable from main constructor.
Called within parent BaseRecipe constructor.""" Called within parent BaseRecipe constructor."""
valid_dict(requirements, str, Any, strict=False, min_length=0) valid_dict(requirements, str, Any, strict=False, min_length=0)
@ -86,7 +86,7 @@ class PapermillHandler(BaseHandler):
print_debug(self._print_target, self.debug_level, print_debug(self._print_target, self.debug_level,
"Created new PapermillHandler instance", DEBUG_INFO) "Created new PapermillHandler instance", DEBUG_INFO)
def handle(self, event:dict[str,Any])->None: def handle(self, event:Dict[str,Any])->None:
"""Function called to handle a given event.""" """Function called to handle a given event."""
print_debug(self._print_target, self.debug_level, print_debug(self._print_target, self.debug_level,
f"Handling event {event[EVENT_PATH]}", DEBUG_INFO) f"Handling event {event[EVENT_PATH]}", DEBUG_INFO)
@ -112,7 +112,7 @@ class PapermillHandler(BaseHandler):
yaml_dict[value[0]] = value[1] yaml_dict[value[0]] = value[1]
self.setup_job(event, yaml_dict) self.setup_job(event, yaml_dict)
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. This handler accepts events from watchdog with process it or not. This handler accepts events from watchdog with
jupyter notebook recipes.""" jupyter notebook recipes."""
@ -135,7 +135,7 @@ class PapermillHandler(BaseHandler):
constructor.""" constructor."""
valid_existing_dir_path(output_dir, allow_base=True) valid_existing_dir_path(output_dir, allow_base=True)
def setup_job(self, event:dict[str,Any], yaml_dict:dict[str,Any])->None: def setup_job(self, event:Dict[str,Any], yaml_dict:Dict[str,Any])->None:
"""Function to set up new job dict and send it to the runner to be """Function to set up new job dict and send it to the runner to be
executed.""" executed."""
meow_job = create_job( meow_job = create_job(

View File

@ -8,7 +8,7 @@ Author(s): David Marchant
import os import os
import sys import sys
from typing import Any, Tuple from typing import Any, Tuple, Dict, List
from core.correctness.validation import check_script, valid_string, \ from core.correctness.validation import check_script, valid_string, \
valid_dict, valid_event, valid_existing_dir_path, setup_debugging valid_dict, valid_event, valid_existing_dir_path, setup_debugging
@ -24,31 +24,32 @@ from core.meow import BaseRecipe, BaseHandler
class PythonRecipe(BaseRecipe): class PythonRecipe(BaseRecipe):
def __init__(self, name:str, recipe:list[str], parameters:dict[str,Any]={}, def __init__(self, name:str, recipe:List[str], parameters:Dict[str,Any]={},
requirements:dict[str,Any]={}): requirements:Dict[str,Any]={}):
"""PythonRecipe Constructor. This is used to execute python analysis """PythonRecipe Constructor. This is used to execute python analysis
code.""" code."""
super().__init__(name, recipe, parameters, requirements) super().__init__(name, recipe, parameters, requirements)
def _is_valid_recipe(self, recipe:list[str])->None: def _is_valid_recipe(self, recipe:List[str])->None:
"""Validation check for 'recipe' variable from main constructor. """Validation check for 'recipe' variable from main constructor.
Called within parent BaseRecipe constructor.""" Called within parent BaseRecipe constructor."""
check_script(recipe) check_script(recipe)
def _is_valid_parameters(self, parameters:dict[str,Any])->None: def _is_valid_parameters(self, parameters:Dict[str,Any])->None:
"""Validation check for 'parameters' variable from main constructor. """Validation check for 'parameters' variable from main constructor.
Called within parent BaseRecipe constructor.""" Called within parent BaseRecipe constructor."""
valid_dict(parameters, str, Any, strict=False, min_length=0) valid_dict(parameters, str, Any, strict=False, min_length=0)
for k in parameters.keys(): for k in parameters.keys():
valid_string(k, VALID_VARIABLE_NAME_CHARS) valid_string(k, VALID_VARIABLE_NAME_CHARS)
def _is_valid_requirements(self, requirements:dict[str,Any])->None: def _is_valid_requirements(self, requirements:Dict[str,Any])->None:
"""Validation check for 'requirements' variable from main constructor. """Validation check for 'requirements' variable from main constructor.
Called within parent BaseRecipe constructor.""" Called within parent BaseRecipe constructor."""
valid_dict(requirements, str, Any, strict=False, min_length=0) valid_dict(requirements, str, Any, strict=False, min_length=0)
for k in requirements.keys(): for k in requirements.keys():
valid_string(k, VALID_VARIABLE_NAME_CHARS) valid_string(k, VALID_VARIABLE_NAME_CHARS)
class PythonHandler(BaseHandler): class PythonHandler(BaseHandler):
# TODO move me to base handler # TODO move me to base handler
# handler directory to setup jobs in # handler directory to setup jobs in
@ -75,7 +76,7 @@ class PythonHandler(BaseHandler):
print_debug(self._print_target, self.debug_level, print_debug(self._print_target, self.debug_level,
"Created new PythonHandler instance", DEBUG_INFO) "Created new PythonHandler instance", DEBUG_INFO)
def handle(self, event:dict[str,Any])->None: def handle(self, event:Dict[str,Any])->None:
"""Function called to handle a given event.""" """Function called to handle a given event."""
print_debug(self._print_target, self.debug_level, print_debug(self._print_target, self.debug_level,
f"Handling event {event[EVENT_PATH]}", DEBUG_INFO) f"Handling event {event[EVENT_PATH]}", DEBUG_INFO)
@ -101,7 +102,7 @@ class PythonHandler(BaseHandler):
yaml_dict[value[0]] = value[1] yaml_dict[value[0]] = value[1]
self.setup_job(event, yaml_dict) self.setup_job(event, yaml_dict)
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. This handler accepts events from watchdog with process it or not. This handler accepts events from watchdog with
Python recipes""" Python recipes"""
@ -124,7 +125,7 @@ class PythonHandler(BaseHandler):
constructor.""" constructor."""
valid_existing_dir_path(output_dir, allow_base=True) valid_existing_dir_path(output_dir, allow_base=True)
def setup_job(self, event:dict[str,Any], yaml_dict:dict[str,Any])->None: def setup_job(self, event:Dict[str,Any], yaml_dict:Dict[str,Any])->None:
"""Function to set up new job dict and send it to the runner to be """Function to set up new job dict and send it to the runner to be
executed.""" executed."""
meow_job = create_job( meow_job = create_job(
@ -175,6 +176,7 @@ class PythonHandler(BaseHandler):
# Send job directory, as actual definitons will be read from within it # Send job directory, as actual definitons will be read from within it
self.to_runner.send(job_dir) self.to_runner.send(job_dir)
# Papermill job execution code, to be run within the conductor # Papermill job execution code, to be run within the conductor
def python_job_func(job): def python_job_func(job):
# Requires own imports as will be run in its own execution environment # Requires own imports as will be run in its own execution environment

View File

@ -3,6 +3,8 @@ This file contains shared functions and variables used within multiple tests.
Author(s): David Marchant Author(s): David Marchant
""" """
import os
from core.functionality import make_dir, rmtree from core.functionality import make_dir, rmtree
@ -30,8 +32,8 @@ COMPLETE_PYTHON_SCRIPT = [
"import os", "import os",
"# Setup parameters", "# Setup parameters",
"num = 1000", "num = 1000",
"infile = 'somehere/particular'", "infile = 'somehere"+ os.path.sep +"particular'",
"outfile = 'nowhere/particular'", "outfile = 'nowhere"+ os.path.sep +"particular'",
"", "",
"with open(infile, 'r') as file:", "with open(infile, 'r') as file:",
" s = float(file.read())", " s = float(file.read())",
@ -131,9 +133,9 @@ APPENDING_NOTEBOOK = {
"# The line to append\n", "# The line to append\n",
"extra = 'This line comes from a default pattern'\n", "extra = 'This line comes from a default pattern'\n",
"# Data input file location\n", "# Data input file location\n",
"infile = 'start/alpha.txt'\n", "infile = 'start"+ os.path.sep +"alpha.txt'\n",
"# Output file location\n", "# Output file location\n",
"outfile = 'first/alpha.txt'" "outfile = 'first"+ os.path.sep +"alpha.txt'"
] ]
}, },
{ {
@ -216,9 +218,9 @@ ADDING_NOTEBOOK = {
"# Amount to add to data\n", "# Amount to add to data\n",
"extra = 10\n", "extra = 10\n",
"# Data input file location\n", "# Data input file location\n",
"infile = 'example_data/data_0.npy'\n", "infile = 'example_data"+ os.path.sep +"data_0.npy'\n",
"# Output file location\n", "# Output file location\n",
"outfile = 'standard_output/data_0.npy'" "outfile = 'standard_output"+ os.path.sep +"data_0.npy'"
] ]
}, },
{ {

View File

@ -2,6 +2,8 @@
import os import os
import unittest import unittest
from typing import Dict
from core.correctness.vars import JOB_TYPE_PYTHON, SHA256, JOB_PARAMETERS, \ from core.correctness.vars import JOB_TYPE_PYTHON, SHA256, JOB_PARAMETERS, \
JOB_HASH, PYTHON_FUNC, PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, JOB_ID, \ JOB_HASH, PYTHON_FUNC, PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, JOB_ID, \
META_FILE, PARAMS_FILE, JOB_STATUS, JOB_ERROR, \ META_FILE, PARAMS_FILE, JOB_STATUS, JOB_ERROR, \
@ -108,7 +110,7 @@ class MeowTests(unittest.TestCase):
meta_path = os.path.join(output_dir, META_FILE) meta_path = os.path.join(output_dir, META_FILE)
self.assertTrue(os.path.exists(meta_path)) self.assertTrue(os.path.exists(meta_path))
status = read_yaml(meta_path) status = read_yaml(meta_path)
self.assertIsInstance(status, dict) self.assertIsInstance(status, Dict)
self.assertIn(JOB_STATUS, status) self.assertIn(JOB_STATUS, status)
self.assertEqual(status[JOB_STATUS], STATUS_DONE) self.assertEqual(status[JOB_STATUS], STATUS_DONE)
@ -196,7 +198,7 @@ class MeowTests(unittest.TestCase):
meta_path = os.path.join(output_dir, META_FILE) meta_path = os.path.join(output_dir, META_FILE)
self.assertTrue(os.path.exists(meta_path)) self.assertTrue(os.path.exists(meta_path))
status = read_yaml(meta_path) status = read_yaml(meta_path)
self.assertIsInstance(status, dict) self.assertIsInstance(status, Dict)
self.assertIn(JOB_STATUS, status) self.assertIn(JOB_STATUS, status)
self.assertEqual(status[JOB_STATUS], STATUS_DONE) self.assertEqual(status[JOB_STATUS], STATUS_DONE)

View File

@ -383,28 +383,38 @@ class CorrectnessTests(unittest.TestCase):
} }
replaced = replace_keywords( replaced = replace_keywords(
test_dict, "job_id", "base/src/dir/file.ext", "base/monitor/dir") test_dict,
"job_id",
os.path.join("base", "src", "dir", "file.ext"),
os.path.join("base", "monitor", "dir")
)
self.assertIsInstance(replaced, dict) self.assertIsInstance(replaced, dict)
self.assertEqual(len(test_dict.keys()), len(replaced.keys())) self.assertEqual(len(test_dict.keys()), len(replaced.keys()))
for k in test_dict.keys(): for k in test_dict.keys():
self.assertIn(k, replaced) self.assertIn(k, replaced)
self.assertEqual(replaced["A"], "--base/src/dir/file.ext--") self.assertEqual(replaced["A"],
self.assertEqual(replaced["B"], "--../../src/dir/file.ext--") os.path.join("--base", "src", "dir", "file.ext--"))
self.assertEqual(replaced["C"], "--base/src/dir--") self.assertEqual(replaced["B"],
self.assertEqual(replaced["D"], "--../../src/dir--") os.path.join("--..", "..", "src", "dir", "file.ext--"))
self.assertEqual(replaced["C"],
os.path.join("--base", "src", "dir--"))
self.assertEqual(replaced["D"],
os.path.join("--..", "..", "src", "dir--"))
self.assertEqual(replaced["E"], "--file.ext--") self.assertEqual(replaced["E"], "--file.ext--")
self.assertEqual(replaced["F"], "--file--") self.assertEqual(replaced["F"], "--file--")
self.assertEqual(replaced["G"], "--base/monitor/dir--") self.assertEqual(replaced["G"],
os.path.join("--base", "monitor", "dir--"))
self.assertEqual(replaced["H"], "--.ext--") self.assertEqual(replaced["H"], "--.ext--")
self.assertEqual(replaced["I"], "--job_id--") self.assertEqual(replaced["I"], "--job_id--")
self.assertEqual(replaced["J"], self.assertEqual(replaced["J"],
"--base/src/dir/file.ext-base/src/dir/file.ext--") os.path.join("--base", "src", "dir", "file.ext-base", "src", "dir", "file.ext--"))
self.assertEqual(replaced["K"], "base/src/dir/file.ext") self.assertEqual(replaced["K"],
os.path.join("base", "src", "dir", "file.ext"))
self.assertEqual(replaced["L"], self.assertEqual(replaced["L"],
"--base/src/dir/file.ext-../../src/dir/file.ext-base/src/dir-" os.path.join("--base", "src", "dir", "file.ext-..", "..", "src", "dir", "file.ext-base", "src", "dir-"
"../../src/dir-file.ext-file-base/monitor/dir-.ext-job_id--") "..", "..", "src", "dir-file.ext-file-base", "monitor", "dir-.ext-job_id--"))
self.assertEqual(replaced["M"], "A") self.assertEqual(replaced["M"], "A")
self.assertEqual(replaced["N"], 1) self.assertEqual(replaced["N"], 1)
@ -498,30 +508,31 @@ data"""
'"metadata": {}, "outputs": [], "source": ["# Default parameters ' '"metadata": {}, "outputs": [], "source": ["# Default parameters '
'values\\n", "# The line to append\\n", "extra = \'This line ' 'values\\n", "# The line to append\\n", "extra = \'This line '
'comes from a default pattern\'\\n", "# Data input file ' 'comes from a default pattern\'\\n", "# Data input file '
'location\\n", "infile = \'start/alpha.txt\'\\n", "# Output file ' 'location\\n", "infile = \'start'+ os.path.sep +'alpha.txt\'\\n", '
'location\\n", "outfile = \'first/alpha.txt\'"]}, {"cell_type": ' '"# Output file location\\n", "outfile = \'first'+ os.path.sep
+'alpha.txt\'"]}, {"cell_type": "code", "execution_count": null, '
'"metadata": {}, "outputs": [], "source": ["# load in dataset. '
'This should be a text file\\n", "with open(infile) as '
'input_file:\\n", " data = input_file.read()"]}, {"cell_type": '
'"code", "execution_count": null, "metadata": {}, "outputs": [], ' '"code", "execution_count": null, "metadata": {}, "outputs": [], '
'"source": ["# load in dataset. This should be a text file\\n", ' '"source": ["# Append the line\\n", "appended = data + \'\\\\n\' '
'"with open(infile) as input_file:\\n", " data = ' '+ extra"]}, {"cell_type": "code", "execution_count": null, '
'input_file.read()"]}, {"cell_type": "code", "execution_count": ' '"metadata": {}, "outputs": [], "source": ["import os\\n", "\\n", '
'null, "metadata": {}, "outputs": [], "source": ["# Append the ' '"# Create output directory if it doesn\'t exist\\n", '
'line\\n", "appended = data + \'\\\\n\' + extra"]}, {"cell_type": ' '"output_dir_path = os.path.dirname(outfile)\\n", "\\n", '
'"code", "execution_count": null, "metadata": {}, "outputs": [], ' '"if output_dir_path:\\n", " os.makedirs(output_dir_path, '
'"source": ["import os\\n", "\\n", "# Create output directory if ' 'exist_ok=True)\\n", "\\n", "# Save added array as new '
'it doesn\'t exist\\n", "output_dir_path = ' 'dataset\\n", "with open(outfile, \'w\') as output_file:\\n", " '
'os.path.dirname(outfile)\\n", "\\n", "if output_dir_path:\\n", ' 'output_file.write(appended)"]}], "metadata": {"kernelspec": '
'" os.makedirs(output_dir_path, exist_ok=True)\\n", "\\n", "# ' '{"display_name": "Python 3", "language": "python", "name": '
'Save added array as new dataset\\n", "with open(outfile, \'w\') ' '"python3"}, "language_info": {"codemirror_mode": {"name": '
'as output_file:\\n", " output_file.write(appended)"]}], ' '"ipython", "version": 3}, "file_extension": ".py", "mimetype": '
'"metadata": {"kernelspec": {"display_name": "Python 3", ' '"text/x-python", "name": "python", "nbconvert_exporter": '
'"language": "python", "name": "python3"}, "language_info": ' '"python", "pygments_lexer": "ipython3", "version": "3.10.6 '
'{"codemirror_mode": {"name": "ipython", "version": 3}, ' '(main, Nov 14 2022, 16:10:14) [GCC 11.3.0]"}, "vscode": '
'"file_extension": ".py", "mimetype": "text/x-python", "name": ' '{"interpreter": {"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c'
'"python", "nbconvert_exporter": "python", "pygments_lexer": ' '65e1c03b4aa3499c5328201f1"}}}, "nbformat": 4, "nbformat_minor": '
'"ipython3", "version": "3.10.6 (main, Nov 14 2022, 16:10:14) ' '4}'
'[GCC 11.3.0]"}, "vscode": {"interpreter": {"hash": '
'"916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1'
'"}}}, "nbformat": 4, "nbformat_minor": 4}'
] ]
self.assertEqual(data, expected_bytes) self.assertEqual(data, expected_bytes)

View File

@ -1,7 +1,7 @@
import unittest import unittest
from typing import Any, Union, Tuple from typing import Any, Union, Tuple, Dict
from core.correctness.vars import SWEEP_STOP, SWEEP_JUMP, SWEEP_START from core.correctness.vars import SWEEP_STOP, SWEEP_JUMP, SWEEP_START
from core.meow import BasePattern, BaseRecipe, BaseRule, BaseMonitor, \ from core.meow import BasePattern, BaseRecipe, BaseRule, BaseMonitor, \
@ -67,7 +67,7 @@ class MeowTests(unittest.TestCase):
def _is_valid_output(self, outputs:Any)->None: def _is_valid_output(self, outputs:Any)->None:
pass pass
def _is_valid_sweep(self, def _is_valid_sweep(self,
sweep:dict[str,Union[int,float,complex]])->None: sweep:Dict[str,Union[int,float,complex]])->None:
pass pass
FullPattern("name", "", "", "", "") FullPattern("name", "", "", "", "")
@ -188,7 +188,7 @@ class MeowTests(unittest.TestCase):
valid_recipe_two.name: valid_recipe_two valid_recipe_two.name: valid_recipe_two
} }
rules = create_rules(patterns, recipes) rules = create_rules(patterns, recipes)
self.assertIsInstance(rules, dict) self.assertIsInstance(rules, Dict)
self.assertEqual(len(rules), 2) self.assertEqual(len(rules), 2)
for k, rule in rules.items(): for k, rule in rules.items():
self.assertIsInstance(k, str) self.assertIsInstance(k, str)
@ -229,9 +229,9 @@ class MeowTests(unittest.TestCase):
pass pass
def stop(self): def stop(self):
pass pass
def _is_valid_patterns(self, patterns:dict[str,BasePattern])->None: def _is_valid_patterns(self, patterns:Dict[str,BasePattern])->None:
pass pass
def _is_valid_recipes(self, recipes:dict[str,BaseRecipe])->None: def _is_valid_recipes(self, recipes:Dict[str,BaseRecipe])->None:
pass pass
def add_pattern(self, pattern:BasePattern)->None: def add_pattern(self, pattern:BasePattern)->None:
pass pass
@ -274,7 +274,7 @@ class MeowTests(unittest.TestCase):
pass pass
def _is_valid_inputs(self, inputs:Any)->None: def _is_valid_inputs(self, inputs:Any)->None:
pass pass
def valid_handle_criteria(self, event:dict[str,Any] def valid_handle_criteria(self, event:Dict[str,Any]
)->Tuple[bool,str]: )->Tuple[bool,str]:
pass pass
@ -292,10 +292,10 @@ class MeowTests(unittest.TestCase):
TestConductor() TestConductor()
class FullTestConductor(BaseConductor): class FullTestConductor(BaseConductor):
def execute(self, job:dict[str,Any])->None: def execute(self, job:Dict[str,Any])->None:
pass pass
def valid_execute_criteria(self, job:dict[str,Any] def valid_execute_criteria(self, job:Dict[str,Any]
)->Tuple[bool,str]: )->Tuple[bool,str]:
pass pass

View File

@ -245,7 +245,10 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor identifies expected events in sub directories # Test WatchdogMonitor identifies expected events in sub directories
def testMonitoring(self)->None: def testMonitoring(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
recipe = JupyterNotebookRecipe( recipe = JupyterNotebookRecipe(
"recipe_one", BAREBONES_NOTEBOOK) "recipe_one", BAREBONES_NOTEBOOK)
@ -304,7 +307,10 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor identifies fake events for retroactive patterns # Test WatchdogMonitor identifies fake events for retroactive patterns
def testMonitoringRetroActive(self)->None: def testMonitoringRetroActive(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
recipe = JupyterNotebookRecipe( recipe = JupyterNotebookRecipe(
"recipe_one", BAREBONES_NOTEBOOK) "recipe_one", BAREBONES_NOTEBOOK)
@ -368,10 +374,16 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor get_patterns function # Test WatchdogMonitor get_patterns function
def testMonitorGetPatterns(self)->None: def testMonitorGetPatterns(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "start/B.txt", "recipe_two", "infile", "pattern_two",
os.path.join("start", "B.txt"),
"recipe_two",
"infile",
parameters={}) parameters={})
wm = WatchdogMonitor( wm = WatchdogMonitor(
@ -395,10 +407,16 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor add_pattern function # Test WatchdogMonitor add_pattern function
def testMonitorAddPattern(self)->None: def testMonitorAddPattern(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "start/B.txt", "recipe_two", "infile", "pattern_two",
os.path.join("start", "B.txt"),
"recipe_two",
"infile",
parameters={}) parameters={})
wm = WatchdogMonitor( wm = WatchdogMonitor(
@ -440,10 +458,16 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor update_patterns function # Test WatchdogMonitor update_patterns function
def testMonitorUpdatePattern(self)->None: def testMonitorUpdatePattern(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "start/B.txt", "recipe_two", "infile", "pattern_two",
os.path.join("start", "B.txt"),
"recipe_two",
"infile",
parameters={}) parameters={})
wm = WatchdogMonitor( wm = WatchdogMonitor(
@ -502,10 +526,16 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor remove_patterns function # Test WatchdogMonitor remove_patterns function
def testMonitorRemovePattern(self)->None: def testMonitorRemovePattern(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "start/B.txt", "recipe_two", "infile", "pattern_two",
os.path.join("start", "B.txt"),
"recipe_two",
"infile",
parameters={}) parameters={})
wm = WatchdogMonitor( wm = WatchdogMonitor(
@ -707,10 +737,16 @@ class CorrectnessTests(unittest.TestCase):
# Test WatchdogMonitor get_rules function # Test WatchdogMonitor get_rules function
def testMonitorGetRules(self)->None: def testMonitorGetRules(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={}) parameters={})
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "start/B.txt", "recipe_two", "infile", "pattern_two",
os.path.join("start", "B.txt"),
"recipe_two",
"infile",
parameters={}) parameters={})
recipe_one = JupyterNotebookRecipe( recipe_one = JupyterNotebookRecipe(
"recipe_one", BAREBONES_NOTEBOOK) "recipe_one", BAREBONES_NOTEBOOK)

View File

@ -4,6 +4,7 @@ import os
import unittest import unittest
from multiprocessing import Pipe from multiprocessing import Pipe
from typing import Dict
from core.correctness.vars import EVENT_TYPE, WATCHDOG_BASE, EVENT_RULE, \ from core.correctness.vars import EVENT_TYPE, WATCHDOG_BASE, EVENT_RULE, \
EVENT_TYPE_WATCHDOG, EVENT_PATH, SHA256, WATCHDOG_HASH, JOB_ID, \ EVENT_TYPE_WATCHDOG, EVENT_PATH, SHA256, WATCHDOG_HASH, JOB_ID, \
@ -391,7 +392,7 @@ class JupyterNotebookTests(unittest.TestCase):
meta_path = os.path.join(job_dir, META_FILE) meta_path = os.path.join(job_dir, META_FILE)
self.assertTrue(os.path.exists(meta_path)) self.assertTrue(os.path.exists(meta_path))
status = read_yaml(meta_path) status = read_yaml(meta_path)
self.assertIsInstance(status, dict) self.assertIsInstance(status, Dict)
self.assertIn(JOB_STATUS, status) self.assertIn(JOB_STATUS, status)
self.assertEqual(status[JOB_STATUS], job_dict[JOB_STATUS]) self.assertEqual(status[JOB_STATUS], job_dict[JOB_STATUS])
@ -759,7 +760,7 @@ class PythonTests(unittest.TestCase):
self.assertTrue(os.path.exists(meta_path)) self.assertTrue(os.path.exists(meta_path))
status = read_yaml(meta_path) status = read_yaml(meta_path)
self.assertIsInstance(status, dict) self.assertIsInstance(status, Dict)
self.assertIn(JOB_STATUS, status) self.assertIn(JOB_STATUS, status)
self.assertEqual(status[JOB_STATUS], job_dict[JOB_STATUS]) self.assertEqual(status[JOB_STATUS], job_dict[JOB_STATUS])
self.assertNotIn(JOB_ERROR, status) self.assertNotIn(JOB_ERROR, status)

View File

@ -121,10 +121,13 @@ class MeowTests(unittest.TestCase):
# Test single meow papermill job execution # Test single meow papermill job execution
def testMeowRunnerPapermillExecution(self)->None: def testMeowRunnerPapermillExecution(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={ parameters={
"extra":"A line from a test Pattern", "extra":"A line from a test Pattern",
"outfile":"{VGRID}/output/{FILENAME}" "outfile":os.path.join("{VGRID}", "output", "{FILENAME}")
}) })
recipe = JupyterNotebookRecipe( recipe = JupyterNotebookRecipe(
"recipe_one", APPENDING_NOTEBOOK) "recipe_one", APPENDING_NOTEBOOK)
@ -205,16 +208,19 @@ class MeowTests(unittest.TestCase):
# Test meow papermill job chaining within runner # Test meow papermill job chaining within runner
def testMeowRunnerLinkedPapermillExecution(self)->None: def testMeowRunnerLinkedPapermillExecution(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one",
os.path.join("start", "A.txt"),
"recipe_one",
"infile",
parameters={ parameters={
"extra":"A line from Pattern 1", "extra":"A line from Pattern 1",
"outfile":"{VGRID}/middle/{FILENAME}" "outfile":os.path.join("{VGRID}", "middle", "{FILENAME}")
}) })
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "middle/A.txt", "recipe_one", "infile", "pattern_two", os.path.join("middle", "A.txt"), "recipe_one", "infile",
parameters={ parameters={
"extra":"A line from Pattern 2", "extra":"A line from Pattern 2",
"outfile":"{VGRID}/output/{FILENAME}" "outfile":os.path.join("{VGRID}", "output", "{FILENAME}")
}) })
recipe = JupyterNotebookRecipe( recipe = JupyterNotebookRecipe(
"recipe_one", APPENDING_NOTEBOOK) "recipe_one", APPENDING_NOTEBOOK)
@ -314,10 +320,10 @@ class MeowTests(unittest.TestCase):
# Test single meow python job execution # Test single meow python job execution
def testMeowRunnerPythonExecution(self)->None: def testMeowRunnerPythonExecution(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one", os.path.join("start", "A.txt"), "recipe_one", "infile",
parameters={ parameters={
"num":10000, "num":10000,
"outfile":"{VGRID}/output/{FILENAME}" "outfile":os.path.join("{VGRID}", "output", "{FILENAME}")
}) })
recipe = PythonRecipe( recipe = PythonRecipe(
"recipe_one", COMPLETE_PYTHON_SCRIPT "recipe_one", COMPLETE_PYTHON_SCRIPT
@ -403,16 +409,16 @@ class MeowTests(unittest.TestCase):
# Test meow python job chaining within runner # Test meow python job chaining within runner
def testMeowRunnerLinkedPythonExecution(self)->None: def testMeowRunnerLinkedPythonExecution(self)->None:
pattern_one = FileEventPattern( pattern_one = FileEventPattern(
"pattern_one", "start/A.txt", "recipe_one", "infile", "pattern_one", os.path.join("start", "A.txt"), "recipe_one", "infile",
parameters={ parameters={
"num":250, "num":250,
"outfile":"{VGRID}/middle/{FILENAME}" "outfile":os.path.join("{VGRID}", "middle", "{FILENAME}")
}) })
pattern_two = FileEventPattern( pattern_two = FileEventPattern(
"pattern_two", "middle/A.txt", "recipe_one", "infile", "pattern_two", os.path.join("middle", "A.txt"), "recipe_one", "infile",
parameters={ parameters={
"num":40, "num":40,
"outfile":"{VGRID}/output/{FILENAME}" "outfile":os.path.join("{VGRID}", "output", "{FILENAME}")
}) })
recipe = PythonRecipe( recipe = PythonRecipe(
"recipe_one", COMPLETE_PYTHON_SCRIPT "recipe_one", COMPLETE_PYTHON_SCRIPT

View File

@ -239,9 +239,10 @@ class CorrectnessTests(unittest.TestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
valid_non_existing_path("first") valid_non_existing_path("first")
make_dir("first/second") test_path = os.path.join("first", "second")
make_dir(test_path)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
valid_non_existing_path("first/second") valid_non_existing_path(test_path)
# TODO modify so tests for actual rule values # TODO modify so tests for actual rule values
# Test valid_event can check given event dictionary # Test valid_event can check given event dictionary