differentiated papermill and python jobs more clearly
This commit is contained in:
@ -14,7 +14,7 @@ from typing import Any, Tuple
|
|||||||
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, \
|
||||||
STATUS_DONE, JOB_END_TIME, STATUS_FAILED, JOB_ERROR, PYTHON_OUTPUT_DIR, \
|
STATUS_DONE, JOB_END_TIME, STATUS_FAILED, JOB_ERROR, PYTHON_OUTPUT_DIR, \
|
||||||
JOB_TYPE
|
JOB_TYPE, JOB_TYPE_PAPERMILL
|
||||||
from core.correctness.validation import valid_job
|
from core.correctness.validation import valid_job
|
||||||
from core.functionality import read_yaml, write_yaml
|
from core.functionality import read_yaml, write_yaml
|
||||||
from core.meow import BaseConductor
|
from core.meow import BaseConductor
|
||||||
@ -30,7 +30,7 @@ class LocalPythonConductor(BaseConductor):
|
|||||||
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:
|
||||||
valid_job(job)
|
valid_job(job)
|
||||||
if job[JOB_TYPE] == JOB_TYPE_PYTHON:
|
if job[JOB_TYPE] in [JOB_TYPE_PYTHON, JOB_TYPE_PAPERMILL]:
|
||||||
return True, ""
|
return True, ""
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
@ -72,6 +72,12 @@ def check_type(variable:Any, expected_type:type, alt_types:list[type]=[],
|
|||||||
% (get_args(expected_type), type(variable))
|
% (get_args(expected_type), type(variable))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def check_callable(call:Any)->None:
|
||||||
|
"""Checks if a given variable is a callable function. Raises TypeError if
|
||||||
|
not."""
|
||||||
|
if not callable(call):
|
||||||
|
raise TypeError(f"Given object '{call}' is not a callable function")
|
||||||
|
|
||||||
def check_implementation(child_func, parent_class):
|
def check_implementation(child_func, parent_class):
|
||||||
"""Checks if the given function has been overridden from the one inherited
|
"""Checks if the given function has been overridden from the one inherited
|
||||||
from the parent class. Raises a NotImplementedError if this is the case."""
|
from the parent class. Raises a NotImplementedError if this is the case."""
|
||||||
@ -94,6 +100,14 @@ def check_implementation(child_func, parent_class):
|
|||||||
msg = get_not_imp_msg(parent_class, parent_func)
|
msg = get_not_imp_msg(parent_class, parent_func)
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
def check_script(script:Any):
|
||||||
|
"""Checks if a given variable is a valid script. Raises TypeError if
|
||||||
|
not."""
|
||||||
|
# TODO investigate more robust check here
|
||||||
|
check_type(script, list)
|
||||||
|
for line in script:
|
||||||
|
check_type(line, str)
|
||||||
|
|
||||||
def valid_string(variable:str, valid_chars:str, min_length:int=1)->None:
|
def valid_string(variable:str, valid_chars:str, min_length:int=1)->None:
|
||||||
"""Checks that all characters in a given string are present in a provided
|
"""Checks that all characters in a given string are present in a provided
|
||||||
list of characters. Will raise an ValueError if unexpected character is
|
list of characters. Will raise an ValueError if unexpected character is
|
||||||
|
@ -80,10 +80,24 @@ DIR_EVENTS = [
|
|||||||
# meow jobs
|
# meow jobs
|
||||||
JOB_TYPE = "job_type"
|
JOB_TYPE = "job_type"
|
||||||
JOB_TYPE_PYTHON = "python"
|
JOB_TYPE_PYTHON = "python"
|
||||||
|
JOB_TYPE_PAPERMILL = "papermill"
|
||||||
PYTHON_FUNC = "func"
|
PYTHON_FUNC = "func"
|
||||||
PYTHON_EXECUTION_BASE = "exection_base"
|
PYTHON_EXECUTION_BASE = "exection_base"
|
||||||
PYTHON_OUTPUT_DIR = "output_dir"
|
PYTHON_OUTPUT_DIR = "output_dir"
|
||||||
|
|
||||||
|
JOB_TYPES = {
|
||||||
|
JOB_TYPE_PAPERMILL: [
|
||||||
|
"base.ipynb",
|
||||||
|
"job.ipynb",
|
||||||
|
"result.ipynb",
|
||||||
|
],
|
||||||
|
JOB_TYPE_PYTHON: [
|
||||||
|
"base.py",
|
||||||
|
"job.py",
|
||||||
|
"result.py",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# job definitions
|
# job definitions
|
||||||
JOB_ID = "id"
|
JOB_ID = "id"
|
||||||
JOB_EVENT = "event"
|
JOB_EVENT = "event"
|
||||||
@ -108,10 +122,7 @@ STATUS_DONE = "done"
|
|||||||
|
|
||||||
# job definition files
|
# job definition files
|
||||||
META_FILE = "job.yml"
|
META_FILE = "job.yml"
|
||||||
BASE_FILE = "base.ipynb"
|
|
||||||
PARAMS_FILE = "params.yml"
|
PARAMS_FILE = "params.yml"
|
||||||
JOB_FILE = "job.ipynb"
|
|
||||||
RESULT_FILE = "result.ipynb"
|
|
||||||
|
|
||||||
# Parameter sweep keys
|
# Parameter sweep keys
|
||||||
SWEEP_START = "start"
|
SWEEP_START = "start"
|
||||||
@ -132,3 +143,12 @@ def get_not_imp_msg(parent_class, class_function):
|
|||||||
return f"Children of the '{parent_class.__name__}' class must implement " \
|
return f"Children of the '{parent_class.__name__}' class must implement " \
|
||||||
f"the '{class_function.__name__}({signature(class_function)})' " \
|
f"the '{class_function.__name__}({signature(class_function)})' " \
|
||||||
"function"
|
"function"
|
||||||
|
|
||||||
|
def get_base_file(job_type:str):
|
||||||
|
return JOB_TYPES[job_type][0]
|
||||||
|
|
||||||
|
def get_job_file(job_type:str):
|
||||||
|
return JOB_TYPES[job_type][1]
|
||||||
|
|
||||||
|
def get_result_file(job_type:str):
|
||||||
|
return JOB_TYPES[job_type][2]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
# TODO comments
|
||||||
import copy
|
import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
@ -15,7 +15,7 @@ from typing import Any
|
|||||||
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, \
|
||||||
valid_path
|
valid_path, check_script
|
||||||
from core.correctness.vars import CHAR_LOWERCASE, CHAR_UPPERCASE, \
|
from core.correctness.vars import CHAR_LOWERCASE, CHAR_UPPERCASE, \
|
||||||
VALID_CHANNELS, HASH_BUFFER_SIZE, SHA256, DEBUG_WARNING, DEBUG_INFO, \
|
VALID_CHANNELS, HASH_BUFFER_SIZE, SHA256, DEBUG_WARNING, DEBUG_INFO, \
|
||||||
EVENT_TYPE, EVENT_PATH, JOB_EVENT, JOB_TYPE, JOB_ID, JOB_PATTERN, \
|
EVENT_TYPE, EVENT_PATH, JOB_EVENT, JOB_TYPE, JOB_ID, JOB_PATTERN, \
|
||||||
@ -128,6 +128,18 @@ def make_dir(path:str, can_exist:bool=True, ensure_clean:bool=False):
|
|||||||
|
|
||||||
os.makedirs(path, exist_ok=can_exist)
|
os.makedirs(path, exist_ok=can_exist)
|
||||||
|
|
||||||
|
def read_file(filepath:str):
|
||||||
|
with open(filepath, 'r') as file:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
def read_file_lines(filepath:str):
|
||||||
|
with open(filepath, 'r') as file:
|
||||||
|
return file.readlines()
|
||||||
|
|
||||||
|
def write_file(source:str, filename:str):
|
||||||
|
with open(filename, 'w') as file:
|
||||||
|
file.write(source)
|
||||||
|
|
||||||
def read_yaml(filepath:str):
|
def read_yaml(filepath:str):
|
||||||
"""
|
"""
|
||||||
Reads a file path as a yaml object.
|
Reads a file path as a yaml object.
|
||||||
@ -171,7 +183,7 @@ 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)
|
||||||
@ -244,6 +256,38 @@ 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],
|
||||||
|
expand_env_values:bool=False)->dict[str,Any]:
|
||||||
|
check_script(script)
|
||||||
|
check_type(parameters, dict)
|
||||||
|
|
||||||
|
output_script = copy.deepcopy(script)
|
||||||
|
|
||||||
|
for i, line in enumerate(output_script):
|
||||||
|
if "=" in line:
|
||||||
|
d_line = list(map(lambda x: x.replace(" ", ""),
|
||||||
|
line.split("=")))
|
||||||
|
# Matching parameter name
|
||||||
|
if len(d_line) == 2 and d_line[0] in parameters:
|
||||||
|
value = parameters[d_line[0]]
|
||||||
|
# Whether to expand value from os env
|
||||||
|
if (
|
||||||
|
expand_env_values
|
||||||
|
and isinstance(value, str)
|
||||||
|
and value.startswith("ENV_")
|
||||||
|
):
|
||||||
|
env_var = value.replace("ENV_", "")
|
||||||
|
value = os.getenv(
|
||||||
|
env_var,
|
||||||
|
"MISSING ENVIRONMENT VARIABLE: {}".format(env_var)
|
||||||
|
)
|
||||||
|
output_script[i] = f"{d_line[0]} = {repr(value)}"
|
||||||
|
|
||||||
|
# Validate that the parameterized notebook is still valid
|
||||||
|
check_script(output_script)
|
||||||
|
|
||||||
|
return output_script
|
||||||
|
|
||||||
def print_debug(print_target, debug_level, msg, level)->None:
|
def print_debug(print_target, debug_level, msg, level)->None:
|
||||||
if print_target is None:
|
if print_target is None:
|
||||||
return
|
return
|
||||||
@ -338,3 +382,6 @@ 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:
|
||||||
|
return "\n".join(lines)
|
||||||
|
14
core/meow.py
14
core/meow.py
@ -98,7 +98,6 @@ class BasePattern:
|
|||||||
check_implementation(type(self)._is_valid_recipe, BasePattern)
|
check_implementation(type(self)._is_valid_recipe, BasePattern)
|
||||||
check_implementation(type(self)._is_valid_parameters, BasePattern)
|
check_implementation(type(self)._is_valid_parameters, BasePattern)
|
||||||
check_implementation(type(self)._is_valid_output, BasePattern)
|
check_implementation(type(self)._is_valid_output, BasePattern)
|
||||||
check_implementation(type(self)._is_valid_sweep, BasePattern)
|
|
||||||
self._is_valid_name(name)
|
self._is_valid_name(name)
|
||||||
self.name = name
|
self.name = name
|
||||||
self._is_valid_recipe(recipe)
|
self._is_valid_recipe(recipe)
|
||||||
@ -140,8 +139,9 @@ class BasePattern:
|
|||||||
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. Must
|
"""Validation check for 'sweep' variable from main constructor. This
|
||||||
be implemented by any child class."""
|
function is implemented to check for the types given in the signature,
|
||||||
|
and must be overridden if these differ."""
|
||||||
check_type(sweep, dict)
|
check_type(sweep, dict)
|
||||||
if not sweep:
|
if not sweep:
|
||||||
return
|
return
|
||||||
@ -208,13 +208,19 @@ class BaseRule:
|
|||||||
the input parameters."""
|
the input parameters."""
|
||||||
check_implementation(type(self)._is_valid_pattern, BaseRule)
|
check_implementation(type(self)._is_valid_pattern, BaseRule)
|
||||||
check_implementation(type(self)._is_valid_recipe, BaseRule)
|
check_implementation(type(self)._is_valid_recipe, BaseRule)
|
||||||
|
self.__check_types_set()
|
||||||
self._is_valid_name(name)
|
self._is_valid_name(name)
|
||||||
self.name = name
|
self.name = name
|
||||||
self._is_valid_pattern(pattern)
|
self._is_valid_pattern(pattern)
|
||||||
self.pattern = pattern
|
self.pattern = pattern
|
||||||
self._is_valid_recipe(recipe)
|
self._is_valid_recipe(recipe)
|
||||||
self.recipe = recipe
|
self.recipe = recipe
|
||||||
self.__check_types_set()
|
check_type(pattern, BasePattern)
|
||||||
|
check_type(recipe, BaseRecipe)
|
||||||
|
if pattern.recipe != recipe.name:
|
||||||
|
raise ValueError(f"Cannot create Rule {name}. Pattern "
|
||||||
|
f"{pattern.name} does not identify Recipe {recipe.name}. It "
|
||||||
|
f"uses {pattern.recipe}")
|
||||||
|
|
||||||
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
|
||||||
|
@ -23,8 +23,8 @@ from core.correctness.validation import check_type, valid_string, \
|
|||||||
setup_debugging
|
setup_debugging
|
||||||
from core.correctness.vars import VALID_RECIPE_NAME_CHARS, \
|
from core.correctness.vars import VALID_RECIPE_NAME_CHARS, \
|
||||||
VALID_VARIABLE_NAME_CHARS, FILE_EVENTS, FILE_CREATE_EVENT, \
|
VALID_VARIABLE_NAME_CHARS, FILE_EVENTS, FILE_CREATE_EVENT, \
|
||||||
FILE_MODIFY_EVENT, FILE_MOVED_EVENT, DEBUG_INFO, EVENT_TYPE_WATCHDOG, \
|
FILE_MODIFY_EVENT, FILE_MOVED_EVENT, DEBUG_INFO, \
|
||||||
WATCHDOG_BASE, FILE_RETROACTIVE_EVENT, WATCHDOG_HASH, SHA256
|
FILE_RETROACTIVE_EVENT, SHA256
|
||||||
from core.functionality import print_debug, create_watchdog_event, \
|
from core.functionality import print_debug, create_watchdog_event, \
|
||||||
get_file_hash, create_fake_watchdog_event
|
get_file_hash, create_fake_watchdog_event
|
||||||
from core.meow import BasePattern, BaseMonitor, BaseRule, BaseRecipe, \
|
from core.meow import BasePattern, BaseMonitor, BaseRule, BaseRecipe, \
|
||||||
|
@ -6,7 +6,6 @@ notebooks, along with an appropriate handler for said events.
|
|||||||
Author(s): David Marchant
|
Author(s): David Marchant
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import itertools
|
|
||||||
import nbformat
|
import nbformat
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -17,12 +16,12 @@ from core.correctness.validation import check_type, valid_string, \
|
|||||||
valid_event
|
valid_event
|
||||||
from core.correctness.vars import VALID_VARIABLE_NAME_CHARS, PYTHON_FUNC, \
|
from core.correctness.vars import VALID_VARIABLE_NAME_CHARS, PYTHON_FUNC, \
|
||||||
DEBUG_INFO, EVENT_TYPE_WATCHDOG, JOB_HASH, PYTHON_EXECUTION_BASE, \
|
DEBUG_INFO, EVENT_TYPE_WATCHDOG, JOB_HASH, PYTHON_EXECUTION_BASE, \
|
||||||
EVENT_PATH, JOB_TYPE_PYTHON, WATCHDOG_HASH, JOB_PARAMETERS, \
|
EVENT_PATH, JOB_TYPE_PAPERMILL, WATCHDOG_HASH, JOB_PARAMETERS, \
|
||||||
PYTHON_OUTPUT_DIR, JOB_ID, WATCHDOG_BASE, META_FILE, BASE_FILE, \
|
PYTHON_OUTPUT_DIR, JOB_ID, WATCHDOG_BASE, META_FILE, \
|
||||||
PARAMS_FILE, JOB_STATUS, STATUS_QUEUED, EVENT_RULE, EVENT_TYPE, \
|
PARAMS_FILE, JOB_STATUS, STATUS_QUEUED, EVENT_RULE, EVENT_TYPE, \
|
||||||
EVENT_RULE
|
EVENT_RULE, get_base_file, get_job_file, get_result_file
|
||||||
from core.functionality import print_debug, create_job, replace_keywords, \
|
from core.functionality import print_debug, create_job, replace_keywords, \
|
||||||
make_dir, write_yaml, write_notebook
|
make_dir, write_yaml, write_notebook, read_notebook
|
||||||
from core.meow import BaseRecipe, BaseHandler
|
from core.meow import BaseRecipe, BaseHandler
|
||||||
|
|
||||||
|
|
||||||
@ -140,19 +139,19 @@ class PapermillHandler(BaseHandler):
|
|||||||
"""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(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
event,
|
event,
|
||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:yaml_dict,
|
JOB_PARAMETERS:yaml_dict,
|
||||||
JOB_HASH: event[WATCHDOG_HASH],
|
JOB_HASH: event[WATCHDOG_HASH],
|
||||||
PYTHON_FUNC:job_func,
|
PYTHON_FUNC:papermill_job_func,
|
||||||
PYTHON_OUTPUT_DIR:self.output_dir,
|
PYTHON_OUTPUT_DIR:self.output_dir,
|
||||||
PYTHON_EXECUTION_BASE:self.handler_base
|
PYTHON_EXECUTION_BASE:self.handler_base
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
print_debug(self._print_target, self.debug_level,
|
print_debug(self._print_target, self.debug_level,
|
||||||
f"Creating job from event at {event[EVENT_PATH]} of type "
|
f"Creating job from event at {event[EVENT_PATH]} of type "
|
||||||
f"{JOB_TYPE_PYTHON}.", DEBUG_INFO)
|
f"{JOB_TYPE_PAPERMILL}.", DEBUG_INFO)
|
||||||
|
|
||||||
# replace MEOW keyworks within variables dict
|
# replace MEOW keyworks within variables dict
|
||||||
yaml_dict = replace_keywords(
|
yaml_dict = replace_keywords(
|
||||||
@ -172,7 +171,7 @@ class PapermillHandler(BaseHandler):
|
|||||||
write_yaml(meow_job, meta_file)
|
write_yaml(meow_job, meta_file)
|
||||||
|
|
||||||
# write an executable notebook to the job directory
|
# write an executable notebook to the job directory
|
||||||
base_file = os.path.join(job_dir, BASE_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))
|
||||||
write_notebook(event[EVENT_RULE].recipe.recipe, base_file)
|
write_notebook(event[EVENT_RULE].recipe.recipe, base_file)
|
||||||
|
|
||||||
# write a parameter file to the job directory
|
# write a parameter file to the job directory
|
||||||
@ -188,25 +187,26 @@ class PapermillHandler(BaseHandler):
|
|||||||
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 job_func(job):
|
def papermill_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
|
||||||
import os
|
import os
|
||||||
import papermill
|
import papermill
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.functionality import write_yaml, read_yaml, write_notebook, \
|
from core.functionality import write_yaml, read_yaml, write_notebook, \
|
||||||
get_file_hash, parameterize_jupyter_notebook
|
get_file_hash, parameterize_jupyter_notebook
|
||||||
from core.correctness.vars import JOB_EVENT, EVENT_RULE, JOB_ID, \
|
from core.correctness.vars import JOB_EVENT, JOB_ID, \
|
||||||
EVENT_PATH, META_FILE, PARAMS_FILE, JOB_FILE, RESULT_FILE, \
|
EVENT_PATH, META_FILE, PARAMS_FILE, \
|
||||||
JOB_STATUS, JOB_HASH, SHA256, STATUS_SKIPPED, JOB_END_TIME, \
|
JOB_STATUS, JOB_HASH, SHA256, STATUS_SKIPPED, JOB_END_TIME, \
|
||||||
JOB_ERROR, STATUS_FAILED, PYTHON_EXECUTION_BASE
|
JOB_ERROR, STATUS_FAILED, PYTHON_EXECUTION_BASE, get_job_file, \
|
||||||
|
get_result_file
|
||||||
event = job[JOB_EVENT]
|
|
||||||
|
|
||||||
# Identify job files
|
# Identify job files
|
||||||
job_dir = os.path.join(job[PYTHON_EXECUTION_BASE], job[JOB_ID])
|
job_dir = os.path.join(job[PYTHON_EXECUTION_BASE], job[JOB_ID])
|
||||||
meta_file = os.path.join(job_dir, META_FILE)
|
meta_file = os.path.join(job_dir, META_FILE)
|
||||||
job_file = os.path.join(job_dir, JOB_FILE)
|
# TODO fix these paths so they are dynamic
|
||||||
result_file = os.path.join(job_dir, RESULT_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))
|
||||||
|
job_file = os.path.join(job_dir, get_job_file(JOB_TYPE_PAPERMILL))
|
||||||
|
result_file = os.path.join(job_dir, get_result_file(JOB_TYPE_PAPERMILL))
|
||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
|
|
||||||
yaml_dict = read_yaml(param_file)
|
yaml_dict = read_yaml(param_file)
|
||||||
@ -233,8 +233,10 @@ def job_func(job):
|
|||||||
|
|
||||||
# Create a parameterised version of the executable notebook
|
# Create a parameterised version of the executable notebook
|
||||||
try:
|
try:
|
||||||
|
base_notebook = read_notebook(base_file)
|
||||||
|
# TODO read notebook from already written file rather than event
|
||||||
job_notebook = parameterize_jupyter_notebook(
|
job_notebook = parameterize_jupyter_notebook(
|
||||||
event[EVENT_RULE].recipe.recipe, yaml_dict
|
base_notebook, yaml_dict
|
||||||
)
|
)
|
||||||
write_notebook(job_notebook, job_file)
|
write_notebook(job_notebook, job_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -6,36 +6,34 @@ along with an appropriate handler for said events.
|
|||||||
Author(s): David Marchant
|
Author(s): David Marchant
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import itertools
|
|
||||||
import nbformat
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from typing import Any, Tuple
|
from typing import Any, Tuple
|
||||||
|
|
||||||
from core.correctness.validation import check_type, 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
|
||||||
from core.correctness.vars import VALID_VARIABLE_NAME_CHARS, PYTHON_FUNC, \
|
from core.correctness.vars import VALID_VARIABLE_NAME_CHARS, PYTHON_FUNC, \
|
||||||
DEBUG_INFO, EVENT_TYPE_WATCHDOG, JOB_HASH, PYTHON_EXECUTION_BASE, \
|
DEBUG_INFO, EVENT_TYPE_WATCHDOG, JOB_HASH, PYTHON_EXECUTION_BASE, \
|
||||||
EVENT_RULE, EVENT_PATH, JOB_TYPE_PYTHON, WATCHDOG_HASH, JOB_PARAMETERS, \
|
EVENT_RULE, EVENT_PATH, JOB_TYPE_PYTHON, WATCHDOG_HASH, JOB_PARAMETERS, \
|
||||||
PYTHON_OUTPUT_DIR, JOB_ID, WATCHDOG_BASE, META_FILE, BASE_FILE, \
|
PYTHON_OUTPUT_DIR, JOB_ID, WATCHDOG_BASE, META_FILE, \
|
||||||
PARAMS_FILE, JOB_STATUS, STATUS_QUEUED, EVENT_TYPE, EVENT_RULE
|
PARAMS_FILE, JOB_STATUS, STATUS_QUEUED, EVENT_TYPE, EVENT_RULE, \
|
||||||
|
get_job_file, get_base_file, get_result_file
|
||||||
from core.functionality import print_debug, create_job, replace_keywords, \
|
from core.functionality import print_debug, create_job, replace_keywords, \
|
||||||
make_dir, write_yaml, write_notebook
|
make_dir, write_yaml, write_file, lines_to_string, read_file_lines
|
||||||
from core.meow import BaseRecipe, BaseHandler
|
from core.meow import BaseRecipe, BaseHandler
|
||||||
|
|
||||||
|
|
||||||
class PythonRecipe(BaseRecipe):
|
class PythonRecipe(BaseRecipe):
|
||||||
def __init__(self, name:str, recipe:Any, 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:dict[str,Any])->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_type(recipe, dict)
|
check_script(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.
|
||||||
@ -135,7 +133,7 @@ class PythonHandler(BaseHandler):
|
|||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:yaml_dict,
|
JOB_PARAMETERS:yaml_dict,
|
||||||
JOB_HASH: event[WATCHDOG_HASH],
|
JOB_HASH: event[WATCHDOG_HASH],
|
||||||
PYTHON_FUNC:job_func,
|
PYTHON_FUNC:python_job_func,
|
||||||
PYTHON_OUTPUT_DIR:self.output_dir,
|
PYTHON_OUTPUT_DIR:self.output_dir,
|
||||||
PYTHON_EXECUTION_BASE:self.handler_base
|
PYTHON_EXECUTION_BASE:self.handler_base
|
||||||
}
|
}
|
||||||
@ -161,9 +159,9 @@ class PythonHandler(BaseHandler):
|
|||||||
meta_file = os.path.join(job_dir, META_FILE)
|
meta_file = os.path.join(job_dir, META_FILE)
|
||||||
write_yaml(meow_job, meta_file)
|
write_yaml(meow_job, meta_file)
|
||||||
|
|
||||||
# write an executable notebook to the job directory
|
# write an executable script to the job directory
|
||||||
base_file = os.path.join(job_dir, BASE_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PYTHON))
|
||||||
write_notebook(event[EVENT_RULE].recipe.recipe, base_file)
|
write_file(lines_to_string(event[EVENT_RULE].recipe.recipe), base_file)
|
||||||
|
|
||||||
# write a parameter file to the job directory
|
# write a parameter file to the job directory
|
||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
@ -178,25 +176,26 @@ class PythonHandler(BaseHandler):
|
|||||||
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 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
|
||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
import papermill
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.functionality import write_yaml, read_yaml, write_notebook, \
|
from io import StringIO
|
||||||
get_file_hash, parameterize_jupyter_notebook
|
from core.functionality import write_yaml, read_yaml, \
|
||||||
from core.correctness.vars import JOB_EVENT, EVENT_RULE, JOB_ID, \
|
get_file_hash, parameterize_python_script
|
||||||
EVENT_PATH, META_FILE, PARAMS_FILE, JOB_FILE, RESULT_FILE, \
|
from core.correctness.vars import JOB_EVENT, JOB_ID, \
|
||||||
|
EVENT_PATH, META_FILE, PARAMS_FILE, \
|
||||||
JOB_STATUS, JOB_HASH, SHA256, STATUS_SKIPPED, JOB_END_TIME, \
|
JOB_STATUS, JOB_HASH, SHA256, STATUS_SKIPPED, JOB_END_TIME, \
|
||||||
JOB_ERROR, STATUS_FAILED, PYTHON_EXECUTION_BASE
|
JOB_ERROR, STATUS_FAILED, PYTHON_EXECUTION_BASE, get_base_file, \
|
||||||
|
get_job_file, get_result_file
|
||||||
event = job[JOB_EVENT]
|
|
||||||
|
|
||||||
# Identify job files
|
# Identify job files
|
||||||
job_dir = os.path.join(job[PYTHON_EXECUTION_BASE], job[JOB_ID])
|
job_dir = os.path.join(job[PYTHON_EXECUTION_BASE], job[JOB_ID])
|
||||||
meta_file = os.path.join(job_dir, META_FILE)
|
meta_file = os.path.join(job_dir, META_FILE)
|
||||||
job_file = os.path.join(job_dir, JOB_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PYTHON))
|
||||||
result_file = os.path.join(job_dir, RESULT_FILE)
|
job_file = os.path.join(job_dir, get_job_file(JOB_TYPE_PYTHON))
|
||||||
|
result_file = os.path.join(job_dir, get_result_file(JOB_TYPE_PYTHON))
|
||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
|
|
||||||
yaml_dict = read_yaml(param_file)
|
yaml_dict = read_yaml(param_file)
|
||||||
@ -221,12 +220,13 @@ def job_func(job):
|
|||||||
write_yaml(job, meta_file)
|
write_yaml(job, meta_file)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create a parameterised version of the executable notebook
|
# Create a parameterised version of the executable script
|
||||||
try:
|
try:
|
||||||
job_notebook = parameterize_jupyter_notebook(
|
base_script = read_file_lines(base_file)
|
||||||
event[EVENT_RULE].recipe.recipe, yaml_dict
|
job_script = parameterize_python_script(
|
||||||
|
base_script, yaml_dict
|
||||||
)
|
)
|
||||||
write_notebook(job_notebook, job_file)
|
write_file(lines_to_string(job_script), job_file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
job[JOB_STATUS] = STATUS_FAILED
|
job[JOB_STATUS] = STATUS_FAILED
|
||||||
job[JOB_END_TIME] = datetime.now()
|
job[JOB_END_TIME] = datetime.now()
|
||||||
@ -235,13 +235,33 @@ def job_func(job):
|
|||||||
write_yaml(job, meta_file)
|
write_yaml(job, meta_file)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Execute the parameterised notebook
|
# Execute the parameterised script
|
||||||
|
std_stdout = sys.stdout
|
||||||
|
std_stderr = sys.stderr
|
||||||
try:
|
try:
|
||||||
papermill.execute_notebook(job_file, result_file, {})
|
redirected_output = sys.stdout
|
||||||
|
redirected_error = sys.stderr
|
||||||
|
|
||||||
|
exec(open(job_file).read())
|
||||||
|
|
||||||
|
write_file(f"""--STDOUT--
|
||||||
|
{redirected_output}
|
||||||
|
|
||||||
|
--STDERR--
|
||||||
|
{redirected_error}
|
||||||
|
""",
|
||||||
|
result_file)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
sys.stdout = std_stdout
|
||||||
|
sys.stderr = std_stderr
|
||||||
|
|
||||||
job[JOB_STATUS] = STATUS_FAILED
|
job[JOB_STATUS] = STATUS_FAILED
|
||||||
job[JOB_END_TIME] = datetime.now()
|
job[JOB_END_TIME] = datetime.now()
|
||||||
msg = f"Result file {result_file} was not created successfully. {e}"
|
msg = f"Result file {result_file} was not created successfully. {e}"
|
||||||
job[JOB_ERROR] = msg
|
job[JOB_ERROR] = msg
|
||||||
write_yaml(job, meta_file)
|
write_yaml(job, meta_file)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
sys.stdout = std_stdout
|
||||||
|
sys.stderr = std_stderr
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
|
|
||||||
from rules.file_event_jupyter_notebook_rule import FileEventJupyterNotebookRule
|
from rules.file_event_jupyter_notebook_rule import FileEventJupyterNotebookRule
|
||||||
|
from rules.file_event_python_rule import FileEventPythonRule
|
30
rules/file_event_python_rule.py
Normal file
30
rules/file_event_python_rule.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
This file contains definitions for a MEOW rule connecting the FileEventPattern
|
||||||
|
and PythonRecipe.
|
||||||
|
|
||||||
|
Author(s): David Marchant
|
||||||
|
"""
|
||||||
|
from core.correctness.validation import check_type
|
||||||
|
from core.meow import BaseRule
|
||||||
|
from patterns.file_event_pattern import FileEventPattern
|
||||||
|
from recipes.python_recipe import PythonRecipe
|
||||||
|
|
||||||
|
# TODO potentailly remove this and just invoke BaseRule directly, as does not
|
||||||
|
# add any functionality other than some validation.
|
||||||
|
class FileEventPythonRule(BaseRule):
|
||||||
|
pattern_type = "FileEventPattern"
|
||||||
|
recipe_type = "PythonRecipe"
|
||||||
|
def __init__(self, name: str, pattern:FileEventPattern,
|
||||||
|
recipe:PythonRecipe):
|
||||||
|
super().__init__(name, pattern, recipe)
|
||||||
|
|
||||||
|
def _is_valid_pattern(self, pattern:FileEventPattern)->None:
|
||||||
|
"""Validation check for 'pattern' variable from main constructor. Is
|
||||||
|
automatically called during initialisation."""
|
||||||
|
check_type(pattern, FileEventPattern)
|
||||||
|
|
||||||
|
def _is_valid_recipe(self, recipe:PythonRecipe)->None:
|
||||||
|
"""Validation check for 'recipe' variable from main constructor. Is
|
||||||
|
automatically called during initialisation."""
|
||||||
|
check_type(recipe, PythonRecipe)
|
@ -22,6 +22,31 @@ def teardown():
|
|||||||
rmtree(TEST_JOB_OUTPUT)
|
rmtree(TEST_JOB_OUTPUT)
|
||||||
rmtree("first")
|
rmtree("first")
|
||||||
|
|
||||||
|
# Recipe funcs
|
||||||
|
BAREBONES_PYTHON_SCRIPT = [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
COMPLETE_PYTHON_SCRIPT = [
|
||||||
|
"# Setup parameters",
|
||||||
|
"num = 1000",
|
||||||
|
"infile = 'somehere/particular'",
|
||||||
|
"outfile = 'nowhere/particular'",
|
||||||
|
"",
|
||||||
|
"with open(infile, 'r') as file:",
|
||||||
|
" s = int(file.read())",
|
||||||
|
""
|
||||||
|
"for i in range(num):",
|
||||||
|
" s += i",
|
||||||
|
"",
|
||||||
|
"div_by = 4",
|
||||||
|
"result = s / div_by",
|
||||||
|
"",
|
||||||
|
"print(result)",
|
||||||
|
"",
|
||||||
|
"with open(outfile, 'w') as file:",
|
||||||
|
" file.write(str(result))"
|
||||||
|
]
|
||||||
|
|
||||||
# Jupyter notebooks
|
# Jupyter notebooks
|
||||||
BAREBONES_NOTEBOOK = {
|
BAREBONES_NOTEBOOK = {
|
||||||
"cells": [],
|
"cells": [],
|
||||||
|
@ -4,15 +4,20 @@ import unittest
|
|||||||
|
|
||||||
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, BASE_FILE, PARAMS_FILE, JOB_FILE, RESULT_FILE
|
META_FILE, PARAMS_FILE, JOB_STATUS, JOB_ERROR, \
|
||||||
|
STATUS_DONE, JOB_TYPE_PAPERMILL, get_base_file, get_result_file, \
|
||||||
|
get_job_file
|
||||||
from core.functionality import get_file_hash, create_watchdog_event, \
|
from core.functionality import get_file_hash, create_watchdog_event, \
|
||||||
create_job, make_dir, write_yaml, write_notebook
|
create_job, make_dir, write_yaml, write_notebook, read_yaml, write_file, \
|
||||||
|
lines_to_string
|
||||||
from core.meow import create_rule
|
from core.meow import create_rule
|
||||||
from conductors import LocalPythonConductor
|
from conductors import LocalPythonConductor
|
||||||
from patterns import FileEventPattern
|
from patterns import FileEventPattern
|
||||||
from recipes.jupyter_notebook_recipe import JupyterNotebookRecipe, job_func
|
from recipes.jupyter_notebook_recipe import JupyterNotebookRecipe, \
|
||||||
|
papermill_job_func
|
||||||
|
from recipes.python_recipe import PythonRecipe, python_job_func
|
||||||
from shared import setup, teardown, TEST_MONITOR_BASE, APPENDING_NOTEBOOK, \
|
from shared import setup, teardown, TEST_MONITOR_BASE, APPENDING_NOTEBOOK, \
|
||||||
TEST_JOB_OUTPUT, TEST_HANDLER_BASE
|
TEST_JOB_OUTPUT, TEST_HANDLER_BASE, COMPLETE_PYTHON_SCRIPT
|
||||||
|
|
||||||
|
|
||||||
def failing_func():
|
def failing_func():
|
||||||
@ -32,10 +37,94 @@ class MeowTests(unittest.TestCase):
|
|||||||
def testLocalPythonConductorCreation(self)->None:
|
def testLocalPythonConductorCreation(self)->None:
|
||||||
LocalPythonConductor()
|
LocalPythonConductor()
|
||||||
|
|
||||||
#TODO Test LocalPythonConductor execution criteria
|
#TODO Test LocalPythonConductor executes valid python jobs
|
||||||
|
def testLocalPythonConductorValidPythonJob(self)->None:
|
||||||
|
lpc = LocalPythonConductor()
|
||||||
|
|
||||||
# Test LocalPythonConductor executes valid jobs
|
file_path = os.path.join(TEST_MONITOR_BASE, "test")
|
||||||
def testLocalPythonConductorValidJob(self)->None:
|
result_path = os.path.join(TEST_MONITOR_BASE, "output")
|
||||||
|
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
f.write("150")
|
||||||
|
|
||||||
|
file_hash = get_file_hash(file_path, SHA256)
|
||||||
|
|
||||||
|
pattern = FileEventPattern(
|
||||||
|
"pattern",
|
||||||
|
file_path,
|
||||||
|
"recipe_one",
|
||||||
|
"infile",
|
||||||
|
parameters={
|
||||||
|
"num":450,
|
||||||
|
"outfile":result_path
|
||||||
|
})
|
||||||
|
recipe = PythonRecipe(
|
||||||
|
"recipe_one", COMPLETE_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
rule = create_rule(pattern, recipe)
|
||||||
|
|
||||||
|
params_dict = {
|
||||||
|
"num":450,
|
||||||
|
"infile":file_path,
|
||||||
|
"outfile":result_path
|
||||||
|
}
|
||||||
|
|
||||||
|
job_dict = create_job(
|
||||||
|
JOB_TYPE_PYTHON,
|
||||||
|
create_watchdog_event(
|
||||||
|
file_path,
|
||||||
|
rule,
|
||||||
|
TEST_MONITOR_BASE,
|
||||||
|
file_hash
|
||||||
|
),
|
||||||
|
extras={
|
||||||
|
JOB_PARAMETERS:params_dict,
|
||||||
|
JOB_HASH: file_hash,
|
||||||
|
PYTHON_FUNC:python_job_func,
|
||||||
|
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
||||||
|
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
job_dir = os.path.join(TEST_HANDLER_BASE, job_dict[JOB_ID])
|
||||||
|
make_dir(job_dir)
|
||||||
|
|
||||||
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
|
write_yaml(params_dict, param_file)
|
||||||
|
|
||||||
|
meta_path = os.path.join(job_dir, META_FILE)
|
||||||
|
write_yaml(job_dict, meta_path)
|
||||||
|
|
||||||
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PYTHON))
|
||||||
|
write_file(lines_to_string(COMPLETE_PYTHON_SCRIPT), base_file)
|
||||||
|
|
||||||
|
lpc.execute(job_dict)
|
||||||
|
|
||||||
|
self.assertFalse(os.path.exists(job_dir))
|
||||||
|
|
||||||
|
output_dir = os.path.join(TEST_JOB_OUTPUT, job_dict[JOB_ID])
|
||||||
|
self.assertTrue(os.path.exists(output_dir))
|
||||||
|
|
||||||
|
meta_path = os.path.join(output_dir, META_FILE)
|
||||||
|
self.assertTrue(os.path.exists(meta_path))
|
||||||
|
status = read_yaml(meta_path)
|
||||||
|
self.assertIsInstance(status, dict)
|
||||||
|
self.assertIn(JOB_STATUS, status)
|
||||||
|
self.assertEqual(status[JOB_STATUS], STATUS_DONE)
|
||||||
|
|
||||||
|
self.assertNotIn(JOB_ERROR, status)
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_base_file(JOB_TYPE_PYTHON))))
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(output_dir, PARAMS_FILE)))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_job_file(JOB_TYPE_PYTHON))))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_result_file(JOB_TYPE_PYTHON))))
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists(result_path))
|
||||||
|
|
||||||
|
# Test LocalPythonConductor executes valid papermill jobs
|
||||||
|
def testLocalPythonConductorValidPapermillJob(self)->None:
|
||||||
lpc = LocalPythonConductor()
|
lpc = LocalPythonConductor()
|
||||||
|
|
||||||
file_path = os.path.join(TEST_MONITOR_BASE, "test")
|
file_path = os.path.join(TEST_MONITOR_BASE, "test")
|
||||||
@ -67,7 +156,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
job_dict = create_job(
|
job_dict = create_job(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
create_watchdog_event(
|
create_watchdog_event(
|
||||||
file_path,
|
file_path,
|
||||||
rule,
|
rule,
|
||||||
@ -77,7 +166,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:params_dict,
|
JOB_PARAMETERS:params_dict,
|
||||||
JOB_HASH: file_hash,
|
JOB_HASH: file_hash,
|
||||||
PYTHON_FUNC:job_func,
|
PYTHON_FUNC:papermill_job_func,
|
||||||
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
||||||
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
||||||
}
|
}
|
||||||
@ -89,7 +178,10 @@ class MeowTests(unittest.TestCase):
|
|||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
write_yaml(params_dict, param_file)
|
write_yaml(params_dict, param_file)
|
||||||
|
|
||||||
base_file = os.path.join(job_dir, BASE_FILE)
|
meta_path = os.path.join(job_dir, META_FILE)
|
||||||
|
write_yaml(job_dict, meta_path)
|
||||||
|
|
||||||
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))
|
||||||
write_notebook(APPENDING_NOTEBOOK, base_file)
|
write_notebook(APPENDING_NOTEBOOK, base_file)
|
||||||
|
|
||||||
lpc.execute(job_dict)
|
lpc.execute(job_dict)
|
||||||
@ -99,11 +191,22 @@ class MeowTests(unittest.TestCase):
|
|||||||
|
|
||||||
output_dir = os.path.join(TEST_JOB_OUTPUT, job_dict[JOB_ID])
|
output_dir = os.path.join(TEST_JOB_OUTPUT, job_dict[JOB_ID])
|
||||||
self.assertTrue(os.path.exists(output_dir))
|
self.assertTrue(os.path.exists(output_dir))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, META_FILE)))
|
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, BASE_FILE)))
|
|
||||||
|
meta_path = os.path.join(output_dir, META_FILE)
|
||||||
|
self.assertTrue(os.path.exists(meta_path))
|
||||||
|
status = read_yaml(meta_path)
|
||||||
|
self.assertIsInstance(status, dict)
|
||||||
|
self.assertIn(JOB_STATUS, status)
|
||||||
|
self.assertEqual(status[JOB_STATUS], STATUS_DONE)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_base_file(JOB_TYPE_PAPERMILL))))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, PARAMS_FILE)))
|
self.assertTrue(os.path.exists(os.path.join(output_dir, PARAMS_FILE)))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, JOB_FILE)))
|
self.assertTrue(os.path.exists(
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, RESULT_FILE)))
|
os.path.join(output_dir, get_job_file(JOB_TYPE_PAPERMILL))))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_result_file(JOB_TYPE_PAPERMILL))))
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(result_path))
|
self.assertTrue(os.path.exists(result_path))
|
||||||
|
|
||||||
@ -140,7 +243,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
bad_job_dict = create_job(
|
bad_job_dict = create_job(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
create_watchdog_event(
|
create_watchdog_event(
|
||||||
file_path,
|
file_path,
|
||||||
rule,
|
rule,
|
||||||
@ -150,7 +253,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:params_dict,
|
JOB_PARAMETERS:params_dict,
|
||||||
JOB_HASH: file_hash,
|
JOB_HASH: file_hash,
|
||||||
PYTHON_FUNC:job_func,
|
PYTHON_FUNC:papermill_job_func,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -160,7 +263,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
write_yaml(params_dict, param_file)
|
write_yaml(params_dict, param_file)
|
||||||
|
|
||||||
base_file = os.path.join(job_dir, BASE_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))
|
||||||
write_notebook(APPENDING_NOTEBOOK, base_file)
|
write_notebook(APPENDING_NOTEBOOK, base_file)
|
||||||
|
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
@ -168,7 +271,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
|
|
||||||
# Ensure execution can continue after one failed job
|
# Ensure execution can continue after one failed job
|
||||||
good_job_dict = create_job(
|
good_job_dict = create_job(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
create_watchdog_event(
|
create_watchdog_event(
|
||||||
file_path,
|
file_path,
|
||||||
rule,
|
rule,
|
||||||
@ -178,7 +281,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:params_dict,
|
JOB_PARAMETERS:params_dict,
|
||||||
JOB_HASH: file_hash,
|
JOB_HASH: file_hash,
|
||||||
PYTHON_FUNC:job_func,
|
PYTHON_FUNC:papermill_job_func,
|
||||||
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
||||||
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
||||||
}
|
}
|
||||||
@ -190,7 +293,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
write_yaml(params_dict, param_file)
|
write_yaml(params_dict, param_file)
|
||||||
|
|
||||||
base_file = os.path.join(job_dir, BASE_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))
|
||||||
write_notebook(APPENDING_NOTEBOOK, base_file)
|
write_notebook(APPENDING_NOTEBOOK, base_file)
|
||||||
|
|
||||||
lpc.execute(good_job_dict)
|
lpc.execute(good_job_dict)
|
||||||
@ -201,10 +304,13 @@ class MeowTests(unittest.TestCase):
|
|||||||
output_dir = os.path.join(TEST_JOB_OUTPUT, good_job_dict[JOB_ID])
|
output_dir = os.path.join(TEST_JOB_OUTPUT, good_job_dict[JOB_ID])
|
||||||
self.assertTrue(os.path.exists(output_dir))
|
self.assertTrue(os.path.exists(output_dir))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, META_FILE)))
|
self.assertTrue(os.path.exists(os.path.join(output_dir, META_FILE)))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, BASE_FILE)))
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_base_file(JOB_TYPE_PAPERMILL))))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, PARAMS_FILE)))
|
self.assertTrue(os.path.exists(os.path.join(output_dir, PARAMS_FILE)))
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, JOB_FILE)))
|
self.assertTrue(os.path.exists(
|
||||||
self.assertTrue(os.path.exists(os.path.join(output_dir, RESULT_FILE)))
|
os.path.join(output_dir, get_job_file(JOB_TYPE_PAPERMILL))))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(output_dir, get_result_file(JOB_TYPE_PAPERMILL))))
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(result_path))
|
self.assertTrue(os.path.exists(result_path))
|
||||||
|
|
||||||
@ -235,7 +341,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
rule = create_rule(pattern, recipe)
|
rule = create_rule(pattern, recipe)
|
||||||
|
|
||||||
job_dict = create_job(
|
job_dict = create_job(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
create_watchdog_event(
|
create_watchdog_event(
|
||||||
file_path,
|
file_path,
|
||||||
rule,
|
rule,
|
||||||
|
@ -12,7 +12,7 @@ from core.correctness.vars import CHAR_LOWERCASE, CHAR_UPPERCASE, \
|
|||||||
WATCHDOG_BASE, WATCHDOG_HASH, EVENT_RULE, JOB_PARAMETERS, JOB_HASH, \
|
WATCHDOG_BASE, WATCHDOG_HASH, EVENT_RULE, JOB_PARAMETERS, JOB_HASH, \
|
||||||
PYTHON_FUNC, PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, JOB_ID, JOB_EVENT, \
|
PYTHON_FUNC, PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, JOB_ID, JOB_EVENT, \
|
||||||
JOB_TYPE, JOB_PATTERN, JOB_RECIPE, JOB_RULE, JOB_STATUS, JOB_CREATE_TIME, \
|
JOB_TYPE, JOB_PATTERN, JOB_RECIPE, JOB_RULE, JOB_STATUS, JOB_CREATE_TIME, \
|
||||||
JOB_REQUIREMENTS, STATUS_QUEUED
|
JOB_REQUIREMENTS, STATUS_QUEUED, JOB_TYPE_PAPERMILL
|
||||||
from core.functionality import generate_id, wait, get_file_hash, rmtree, \
|
from core.functionality import generate_id, wait, get_file_hash, rmtree, \
|
||||||
make_dir, parameterize_jupyter_notebook, create_event, create_job, \
|
make_dir, parameterize_jupyter_notebook, create_event, create_job, \
|
||||||
replace_keywords, write_yaml, write_notebook, read_yaml, read_notebook, \
|
replace_keywords, write_yaml, write_notebook, read_yaml, read_notebook, \
|
||||||
@ -240,6 +240,8 @@ class CorrectnessTests(unittest.TestCase):
|
|||||||
pn["cells"][0]["source"],
|
pn["cells"][0]["source"],
|
||||||
"# The first cell\n\ns = 4\nnum = 1000")
|
"# The first cell\n\ns = 4\nnum = 1000")
|
||||||
|
|
||||||
|
# TODO Test that parameterize_python_script parameterises given script
|
||||||
|
|
||||||
# Test that create_event produces valid event dictionary
|
# Test that create_event produces valid event dictionary
|
||||||
def testCreateEvent(self)->None:
|
def testCreateEvent(self)->None:
|
||||||
pattern = FileEventPattern(
|
pattern = FileEventPattern(
|
||||||
@ -307,7 +309,7 @@ class CorrectnessTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
job_dict = create_job(
|
job_dict = create_job(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
event,
|
event,
|
||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:{
|
JOB_PARAMETERS:{
|
||||||
@ -328,7 +330,7 @@ class CorrectnessTests(unittest.TestCase):
|
|||||||
self.assertIn(JOB_EVENT, job_dict)
|
self.assertIn(JOB_EVENT, job_dict)
|
||||||
self.assertEqual(job_dict[JOB_EVENT], event)
|
self.assertEqual(job_dict[JOB_EVENT], event)
|
||||||
self.assertIn(JOB_TYPE, job_dict)
|
self.assertIn(JOB_TYPE, job_dict)
|
||||||
self.assertEqual(job_dict[JOB_TYPE], JOB_TYPE_PYTHON)
|
self.assertEqual(job_dict[JOB_TYPE], JOB_TYPE_PAPERMILL)
|
||||||
self.assertIn(JOB_PATTERN, job_dict)
|
self.assertIn(JOB_PATTERN, job_dict)
|
||||||
self.assertEqual(job_dict[JOB_PATTERN], pattern.name)
|
self.assertEqual(job_dict[JOB_PATTERN], pattern.name)
|
||||||
self.assertIn(JOB_RECIPE, job_dict)
|
self.assertIn(JOB_RECIPE, job_dict)
|
||||||
@ -659,3 +661,8 @@ class CorrectnessTests(unittest.TestCase):
|
|||||||
self.assertEqual(event[EVENT_RULE], rule)
|
self.assertEqual(event[EVENT_RULE], rule)
|
||||||
self.assertEqual(event["a"], 1)
|
self.assertEqual(event["a"], 1)
|
||||||
self.assertEqual(event[WATCHDOG_BASE], "base")
|
self.assertEqual(event[WATCHDOG_BASE], "base")
|
||||||
|
|
||||||
|
#TODO test read file
|
||||||
|
#TODO test readlines file
|
||||||
|
#TODO test write file
|
||||||
|
#TODO test lines to str
|
@ -3,6 +3,7 @@ import unittest
|
|||||||
|
|
||||||
from typing import Any, Union, Tuple
|
from typing import Any, Union, Tuple
|
||||||
|
|
||||||
|
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, \
|
||||||
BaseHandler, BaseConductor, create_rules, create_rule
|
BaseHandler, BaseConductor, create_rules, create_rule
|
||||||
from patterns import FileEventPattern
|
from patterns import FileEventPattern
|
||||||
@ -70,6 +71,78 @@ class MeowTests(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
FullPattern("name", "", "", "", "")
|
FullPattern("name", "", "", "", "")
|
||||||
|
|
||||||
|
# Test expansion of parameter sweeps
|
||||||
|
def testBasePatternExpandSweeps(self)->None:
|
||||||
|
pattern_one = FileEventPattern(
|
||||||
|
"pattern_one", "A", "recipe_one", "file_one", sweep={
|
||||||
|
"s1":{
|
||||||
|
SWEEP_START: 10, SWEEP_STOP: 20, SWEEP_JUMP:5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
es = pattern_one.expand_sweeps()
|
||||||
|
|
||||||
|
self.assertIsInstance(es, list)
|
||||||
|
self.assertEqual(len(es), 3)
|
||||||
|
|
||||||
|
values = [
|
||||||
|
"s1-10", "s1-15", "s1-20",
|
||||||
|
]
|
||||||
|
|
||||||
|
for sweep_vals in es:
|
||||||
|
self.assertIsInstance(sweep_vals, tuple)
|
||||||
|
self.assertEqual(len(sweep_vals), 1)
|
||||||
|
|
||||||
|
val1 = None
|
||||||
|
for sweep_val in sweep_vals:
|
||||||
|
self.assertIsInstance(sweep_val, tuple)
|
||||||
|
self.assertEqual(len(sweep_val), 2)
|
||||||
|
if sweep_val[0] == "s1":
|
||||||
|
val1 = f"s1-{sweep_val[1]}"
|
||||||
|
if val1:
|
||||||
|
values.remove(val1)
|
||||||
|
self.assertEqual(len(values), 0)
|
||||||
|
|
||||||
|
pattern_one = FileEventPattern(
|
||||||
|
"pattern_one", "A", "recipe_one", "file_one", sweep={
|
||||||
|
"s1":{
|
||||||
|
SWEEP_START: 0, SWEEP_STOP: 2, SWEEP_JUMP:1
|
||||||
|
},
|
||||||
|
"s2":{
|
||||||
|
SWEEP_START: 20, SWEEP_STOP: 80, SWEEP_JUMP:15
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
es = pattern_one.expand_sweeps()
|
||||||
|
|
||||||
|
self.assertIsInstance(es, list)
|
||||||
|
self.assertEqual(len(es), 15)
|
||||||
|
|
||||||
|
values = [
|
||||||
|
"s1-0/s2-20", "s1-1/s2-20", "s1-2/s2-20",
|
||||||
|
"s1-0/s2-35", "s1-1/s2-35", "s1-2/s2-35",
|
||||||
|
"s1-0/s2-50", "s1-1/s2-50", "s1-2/s2-50",
|
||||||
|
"s1-0/s2-65", "s1-1/s2-65", "s1-2/s2-65",
|
||||||
|
"s1-0/s2-80", "s1-1/s2-80", "s1-2/s2-80",
|
||||||
|
]
|
||||||
|
|
||||||
|
for sweep_vals in es:
|
||||||
|
self.assertIsInstance(sweep_vals, tuple)
|
||||||
|
self.assertEqual(len(sweep_vals), 2)
|
||||||
|
|
||||||
|
val1 = None
|
||||||
|
val2 = None
|
||||||
|
for sweep_val in sweep_vals:
|
||||||
|
self.assertIsInstance(sweep_val, tuple)
|
||||||
|
self.assertEqual(len(sweep_val), 2)
|
||||||
|
if sweep_val[0] == "s1":
|
||||||
|
val1 = f"s1-{sweep_val[1]}"
|
||||||
|
if sweep_val[0] == "s2":
|
||||||
|
val2 = f"s2-{sweep_val[1]}"
|
||||||
|
if val1 and val2:
|
||||||
|
values.remove(f"{val1}/{val2}")
|
||||||
|
self.assertEqual(len(values), 0)
|
||||||
|
|
||||||
# Test that BaseRecipe instantiation
|
# Test that BaseRecipe instantiation
|
||||||
def testBaseRule(self)->None:
|
def testBaseRule(self)->None:
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
@ -87,7 +160,7 @@ class MeowTests(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
def _is_valid_pattern(self, pattern:Any)->None:
|
def _is_valid_pattern(self, pattern:Any)->None:
|
||||||
pass
|
pass
|
||||||
FullRule("name", "", "")
|
FullRule("name", valid_pattern_one, valid_recipe_one)
|
||||||
|
|
||||||
# Test that create_rule creates a rule from pattern and recipe
|
# Test that create_rule creates a rule from pattern and recipe
|
||||||
def testCreateRule(self)->None:
|
def testCreateRule(self)->None:
|
||||||
@ -227,5 +300,3 @@ class MeowTests(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
FullTestConductor()
|
FullTestConductor()
|
||||||
|
|
||||||
# TODO Test expansion of parameter sweeps
|
|
||||||
|
@ -7,20 +7,25 @@ from multiprocessing import Pipe
|
|||||||
|
|
||||||
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, \
|
||||||
JOB_TYPE_PYTHON, JOB_PARAMETERS, JOB_HASH, PYTHON_FUNC, \
|
JOB_TYPE_PYTHON, JOB_PARAMETERS, JOB_HASH, PYTHON_FUNC, JOB_STATUS, \
|
||||||
PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, META_FILE, BASE_FILE, \
|
PYTHON_OUTPUT_DIR, PYTHON_EXECUTION_BASE, META_FILE, JOB_ERROR, \
|
||||||
PARAMS_FILE, JOB_FILE, RESULT_FILE, SWEEP_STOP, SWEEP_JUMP, SWEEP_START
|
PARAMS_FILE, SWEEP_STOP, SWEEP_JUMP, SWEEP_START, JOB_TYPE_PAPERMILL, \
|
||||||
|
get_base_file, get_job_file, get_result_file
|
||||||
from core.correctness.validation import valid_job
|
from core.correctness.validation import valid_job
|
||||||
from core.functionality import get_file_hash, create_job, \
|
from core.functionality import get_file_hash, create_job, \
|
||||||
create_watchdog_event, make_dir, write_yaml, write_notebook, read_yaml
|
create_watchdog_event, make_dir, write_yaml, write_notebook, read_yaml, \
|
||||||
|
write_file, lines_to_string
|
||||||
from core.meow import create_rules, create_rule
|
from core.meow import create_rules, create_rule
|
||||||
from patterns.file_event_pattern import FileEventPattern
|
from patterns.file_event_pattern import FileEventPattern
|
||||||
from recipes.jupyter_notebook_recipe import JupyterNotebookRecipe, \
|
from recipes.jupyter_notebook_recipe import JupyterNotebookRecipe, \
|
||||||
PapermillHandler, job_func
|
PapermillHandler, papermill_job_func
|
||||||
from rules.file_event_jupyter_notebook_rule import FileEventJupyterNotebookRule
|
from recipes.python_recipe import PythonRecipe, PythonHandler, python_job_func
|
||||||
from shared import setup, teardown, TEST_HANDLER_BASE, TEST_MONITOR_BASE, \
|
from rules import FileEventJupyterNotebookRule, FileEventPythonRule
|
||||||
|
from shared import setup, teardown, BAREBONES_PYTHON_SCRIPT, \
|
||||||
|
COMPLETE_PYTHON_SCRIPT, TEST_HANDLER_BASE, TEST_MONITOR_BASE, \
|
||||||
TEST_JOB_OUTPUT, BAREBONES_NOTEBOOK, APPENDING_NOTEBOOK, COMPLETE_NOTEBOOK
|
TEST_JOB_OUTPUT, BAREBONES_NOTEBOOK, APPENDING_NOTEBOOK, COMPLETE_NOTEBOOK
|
||||||
|
|
||||||
|
|
||||||
class JupyterNotebookTests(unittest.TestCase):
|
class JupyterNotebookTests(unittest.TestCase):
|
||||||
def setUp(self)->None:
|
def setUp(self)->None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@ -349,7 +354,7 @@ class JupyterNotebookTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
job_dict = create_job(
|
job_dict = create_job(
|
||||||
JOB_TYPE_PYTHON,
|
JOB_TYPE_PAPERMILL,
|
||||||
create_watchdog_event(
|
create_watchdog_event(
|
||||||
file_path,
|
file_path,
|
||||||
rule,
|
rule,
|
||||||
@ -359,7 +364,7 @@ class JupyterNotebookTests(unittest.TestCase):
|
|||||||
extras={
|
extras={
|
||||||
JOB_PARAMETERS:params_dict,
|
JOB_PARAMETERS:params_dict,
|
||||||
JOB_HASH: file_hash,
|
JOB_HASH: file_hash,
|
||||||
PYTHON_FUNC:job_func,
|
PYTHON_FUNC:papermill_job_func,
|
||||||
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
||||||
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
||||||
}
|
}
|
||||||
@ -375,25 +380,35 @@ class JupyterNotebookTests(unittest.TestCase):
|
|||||||
param_file = os.path.join(job_dir, PARAMS_FILE)
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
write_yaml(params_dict, param_file)
|
write_yaml(params_dict, param_file)
|
||||||
|
|
||||||
base_file = os.path.join(job_dir, BASE_FILE)
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))
|
||||||
write_notebook(APPENDING_NOTEBOOK, base_file)
|
write_notebook(APPENDING_NOTEBOOK, base_file)
|
||||||
|
|
||||||
job_func(job_dict)
|
papermill_job_func(job_dict)
|
||||||
|
|
||||||
job_dir = os.path.join(TEST_HANDLER_BASE, job_dict[JOB_ID])
|
job_dir = os.path.join(TEST_HANDLER_BASE, job_dict[JOB_ID])
|
||||||
self.assertTrue(os.path.exists(job_dir))
|
self.assertTrue(os.path.exists(job_dir))
|
||||||
self.assertTrue(os.path.exists(os.path.join(job_dir, META_FILE)))
|
|
||||||
self.assertTrue(os.path.exists(os.path.join(job_dir, BASE_FILE)))
|
meta_path = os.path.join(job_dir, META_FILE)
|
||||||
|
self.assertTrue(os.path.exists(meta_path))
|
||||||
|
status = read_yaml(meta_path)
|
||||||
|
self.assertIsInstance(status, dict)
|
||||||
|
self.assertIn(JOB_STATUS, status)
|
||||||
|
self.assertEqual(status[JOB_STATUS], job_dict[JOB_STATUS])
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(job_dir, get_base_file(JOB_TYPE_PAPERMILL))))
|
||||||
self.assertTrue(os.path.exists(os.path.join(job_dir, PARAMS_FILE)))
|
self.assertTrue(os.path.exists(os.path.join(job_dir, PARAMS_FILE)))
|
||||||
self.assertTrue(os.path.exists(os.path.join(job_dir, JOB_FILE)))
|
self.assertTrue(os.path.exists(
|
||||||
self.assertTrue(os.path.exists(os.path.join(job_dir, RESULT_FILE)))
|
os.path.join(job_dir, get_job_file(JOB_TYPE_PAPERMILL))))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(job_dir, get_result_file(JOB_TYPE_PAPERMILL))))
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(result_path))
|
self.assertTrue(os.path.exists(result_path))
|
||||||
|
|
||||||
# Test jobFunc doesn't execute with no args
|
# Test jobFunc doesn't execute with no args
|
||||||
def testJobFuncBadArgs(self)->None:
|
def testJobFuncBadArgs(self)->None:
|
||||||
try:
|
try:
|
||||||
job_func({})
|
papermill_job_func({})
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -411,3 +426,363 @@ class PythonTests(unittest.TestCase):
|
|||||||
def tearDown(self)->None:
|
def tearDown(self)->None:
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
teardown()
|
teardown()
|
||||||
|
|
||||||
|
# Test PythonRecipe can be created
|
||||||
|
def testPythonRecipeCreationMinimum(self)->None:
|
||||||
|
PythonRecipe("test_recipe", BAREBONES_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
# Test PythonRecipe cannot be created without name
|
||||||
|
def testPythonRecipeCreationNoName(self)->None:
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PythonRecipe("", BAREBONES_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
# Test PythonRecipe cannot be created with invalid name
|
||||||
|
def testPythonRecipeCreationInvalidName(self)->None:
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
PythonRecipe("@test_recipe", BAREBONES_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
# Test PythonRecipe cannot be created with invalid recipe
|
||||||
|
def testPythonRecipeCreationInvalidRecipe(self)->None:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PythonRecipe("test_recipe", BAREBONES_NOTEBOOK)
|
||||||
|
|
||||||
|
# Test PythonRecipe name setup correctly
|
||||||
|
def testPythonRecipeSetupName(self)->None:
|
||||||
|
name = "name"
|
||||||
|
pr = PythonRecipe(name, BAREBONES_PYTHON_SCRIPT)
|
||||||
|
self.assertEqual(pr.name, name)
|
||||||
|
|
||||||
|
# Test PythonRecipe recipe setup correctly
|
||||||
|
def testPythonRecipeSetupRecipe(self)->None:
|
||||||
|
pr = PythonRecipe("name", BAREBONES_PYTHON_SCRIPT)
|
||||||
|
self.assertEqual(pr.recipe, BAREBONES_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
# Test PythonRecipe parameters setup correctly
|
||||||
|
def testPythonRecipeSetupParameters(self)->None:
|
||||||
|
parameters = {
|
||||||
|
"a": 1,
|
||||||
|
"b": True
|
||||||
|
}
|
||||||
|
pr = PythonRecipe(
|
||||||
|
"name", BAREBONES_PYTHON_SCRIPT, parameters=parameters)
|
||||||
|
self.assertEqual(pr.parameters, parameters)
|
||||||
|
|
||||||
|
# Test PythonRecipe requirements setup correctly
|
||||||
|
def testPythonRecipeSetupRequirements(self)->None:
|
||||||
|
requirements = {
|
||||||
|
"a": 1,
|
||||||
|
"b": True
|
||||||
|
}
|
||||||
|
pr = PythonRecipe(
|
||||||
|
"name", BAREBONES_PYTHON_SCRIPT, requirements=requirements)
|
||||||
|
self.assertEqual(pr.requirements, requirements)
|
||||||
|
|
||||||
|
# Test PythonHandler can be created
|
||||||
|
def testPythonHandlerMinimum(self)->None:
|
||||||
|
PythonHandler(
|
||||||
|
TEST_HANDLER_BASE,
|
||||||
|
TEST_JOB_OUTPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test PythonHandler will handle given events
|
||||||
|
def testPythonHandlerHandling(self)->None:
|
||||||
|
from_handler_reader, from_handler_writer = Pipe()
|
||||||
|
ph = PythonHandler(
|
||||||
|
TEST_HANDLER_BASE,
|
||||||
|
TEST_JOB_OUTPUT
|
||||||
|
)
|
||||||
|
ph.to_runner = from_handler_writer
|
||||||
|
|
||||||
|
with open(os.path.join(TEST_MONITOR_BASE, "A"), "w") as f:
|
||||||
|
f.write("Data")
|
||||||
|
|
||||||
|
pattern_one = FileEventPattern(
|
||||||
|
"pattern_one", "A", "recipe_one", "file_one")
|
||||||
|
recipe = PythonRecipe(
|
||||||
|
"recipe_one", COMPLETE_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
patterns = {
|
||||||
|
pattern_one.name: pattern_one,
|
||||||
|
}
|
||||||
|
recipes = {
|
||||||
|
recipe.name: recipe,
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = create_rules(patterns, recipes)
|
||||||
|
self.assertEqual(len(rules), 1)
|
||||||
|
_, rule = rules.popitem()
|
||||||
|
self.assertIsInstance(rule, FileEventPythonRule)
|
||||||
|
|
||||||
|
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
|
||||||
|
|
||||||
|
event = {
|
||||||
|
EVENT_TYPE: EVENT_TYPE_WATCHDOG,
|
||||||
|
EVENT_PATH: os.path.join(TEST_MONITOR_BASE, "A"),
|
||||||
|
WATCHDOG_BASE: TEST_MONITOR_BASE,
|
||||||
|
EVENT_RULE: rule,
|
||||||
|
WATCHDOG_HASH: get_file_hash(
|
||||||
|
os.path.join(TEST_MONITOR_BASE, "A"), SHA256
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.handle(event)
|
||||||
|
|
||||||
|
if from_handler_reader.poll(3):
|
||||||
|
job_dir = from_handler_reader.recv()
|
||||||
|
|
||||||
|
self.assertIsInstance(job_dir, str)
|
||||||
|
self.assertTrue(os.path.exists(job_dir))
|
||||||
|
|
||||||
|
job = read_yaml(os.path.join(job_dir, META_FILE))
|
||||||
|
valid_job(job)
|
||||||
|
|
||||||
|
# Test PythonHandler will create enough jobs from single sweep
|
||||||
|
def testPythonHandlerHandlingSingleSweep(self)->None:
|
||||||
|
from_handler_reader, from_handler_writer = Pipe()
|
||||||
|
ph = PythonHandler(
|
||||||
|
TEST_HANDLER_BASE,
|
||||||
|
TEST_JOB_OUTPUT
|
||||||
|
)
|
||||||
|
ph.to_runner = from_handler_writer
|
||||||
|
|
||||||
|
with open(os.path.join(TEST_MONITOR_BASE, "A"), "w") as f:
|
||||||
|
f.write("Data")
|
||||||
|
|
||||||
|
pattern_one = FileEventPattern(
|
||||||
|
"pattern_one", "A", "recipe_one", "file_one", sweep={"s":{
|
||||||
|
SWEEP_START: 0, SWEEP_STOP: 2, SWEEP_JUMP:1
|
||||||
|
}})
|
||||||
|
recipe = PythonRecipe(
|
||||||
|
"recipe_one", COMPLETE_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
patterns = {
|
||||||
|
pattern_one.name: pattern_one,
|
||||||
|
}
|
||||||
|
recipes = {
|
||||||
|
recipe.name: recipe,
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = create_rules(patterns, recipes)
|
||||||
|
self.assertEqual(len(rules), 1)
|
||||||
|
_, rule = rules.popitem()
|
||||||
|
self.assertIsInstance(rule, FileEventPythonRule)
|
||||||
|
|
||||||
|
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
|
||||||
|
|
||||||
|
event = {
|
||||||
|
EVENT_TYPE: EVENT_TYPE_WATCHDOG,
|
||||||
|
EVENT_PATH: os.path.join(TEST_MONITOR_BASE, "A"),
|
||||||
|
WATCHDOG_BASE: TEST_MONITOR_BASE,
|
||||||
|
EVENT_RULE: rule,
|
||||||
|
WATCHDOG_HASH: get_file_hash(
|
||||||
|
os.path.join(TEST_MONITOR_BASE, "A"), SHA256
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.handle(event)
|
||||||
|
|
||||||
|
jobs = []
|
||||||
|
recieving = True
|
||||||
|
while recieving:
|
||||||
|
if from_handler_reader.poll(3):
|
||||||
|
jobs.append(from_handler_reader.recv())
|
||||||
|
else:
|
||||||
|
recieving = False
|
||||||
|
|
||||||
|
values = [0, 1, 2]
|
||||||
|
self.assertEqual(len(jobs), 3)
|
||||||
|
for job_dir in jobs:
|
||||||
|
self.assertIsInstance(job_dir, str)
|
||||||
|
self.assertTrue(os.path.exists(job_dir))
|
||||||
|
|
||||||
|
job = read_yaml(os.path.join(job_dir, META_FILE))
|
||||||
|
valid_job(job)
|
||||||
|
|
||||||
|
self.assertIn(JOB_PARAMETERS, job)
|
||||||
|
self.assertIn("s", job[JOB_PARAMETERS])
|
||||||
|
if job[JOB_PARAMETERS]["s"] in values:
|
||||||
|
values.remove(job[JOB_PARAMETERS]["s"])
|
||||||
|
self.assertEqual(len(values), 0)
|
||||||
|
|
||||||
|
# Test PythonHandler will create enough jobs from multiple sweeps
|
||||||
|
def testPythonHandlerHandlingMultipleSweep(self)->None:
|
||||||
|
from_handler_reader, from_handler_writer = Pipe()
|
||||||
|
ph = PythonHandler(
|
||||||
|
TEST_HANDLER_BASE,
|
||||||
|
TEST_JOB_OUTPUT
|
||||||
|
)
|
||||||
|
ph.to_runner = from_handler_writer
|
||||||
|
|
||||||
|
with open(os.path.join(TEST_MONITOR_BASE, "A"), "w") as f:
|
||||||
|
f.write("Data")
|
||||||
|
|
||||||
|
pattern_one = FileEventPattern(
|
||||||
|
"pattern_one", "A", "recipe_one", "file_one", sweep={
|
||||||
|
"s1":{
|
||||||
|
SWEEP_START: 0, SWEEP_STOP: 2, SWEEP_JUMP:1
|
||||||
|
},
|
||||||
|
"s2":{
|
||||||
|
SWEEP_START: 20, SWEEP_STOP: 80, SWEEP_JUMP:15
|
||||||
|
}
|
||||||
|
})
|
||||||
|
recipe = PythonRecipe(
|
||||||
|
"recipe_one", COMPLETE_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
patterns = {
|
||||||
|
pattern_one.name: pattern_one,
|
||||||
|
}
|
||||||
|
recipes = {
|
||||||
|
recipe.name: recipe,
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = create_rules(patterns, recipes)
|
||||||
|
self.assertEqual(len(rules), 1)
|
||||||
|
_, rule = rules.popitem()
|
||||||
|
self.assertIsInstance(rule, FileEventPythonRule)
|
||||||
|
|
||||||
|
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
|
||||||
|
|
||||||
|
event = {
|
||||||
|
EVENT_TYPE: EVENT_TYPE_WATCHDOG,
|
||||||
|
EVENT_PATH: os.path.join(TEST_MONITOR_BASE, "A"),
|
||||||
|
WATCHDOG_BASE: TEST_MONITOR_BASE,
|
||||||
|
EVENT_RULE: rule,
|
||||||
|
WATCHDOG_HASH: get_file_hash(
|
||||||
|
os.path.join(TEST_MONITOR_BASE, "A"), SHA256
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.handle(event)
|
||||||
|
|
||||||
|
jobs = []
|
||||||
|
recieving = True
|
||||||
|
while recieving:
|
||||||
|
if from_handler_reader.poll(3):
|
||||||
|
jobs.append(from_handler_reader.recv())
|
||||||
|
else:
|
||||||
|
recieving = False
|
||||||
|
|
||||||
|
values = [
|
||||||
|
"s1-0/s2-20", "s1-1/s2-20", "s1-2/s2-20",
|
||||||
|
"s1-0/s2-35", "s1-1/s2-35", "s1-2/s2-35",
|
||||||
|
"s1-0/s2-50", "s1-1/s2-50", "s1-2/s2-50",
|
||||||
|
"s1-0/s2-65", "s1-1/s2-65", "s1-2/s2-65",
|
||||||
|
"s1-0/s2-80", "s1-1/s2-80", "s1-2/s2-80",
|
||||||
|
]
|
||||||
|
self.assertEqual(len(jobs), 15)
|
||||||
|
for job_dir in jobs:
|
||||||
|
self.assertIsInstance(job_dir, str)
|
||||||
|
self.assertTrue(os.path.exists(job_dir))
|
||||||
|
|
||||||
|
job = read_yaml(os.path.join(job_dir, META_FILE))
|
||||||
|
valid_job(job)
|
||||||
|
|
||||||
|
self.assertIn(JOB_PARAMETERS, job)
|
||||||
|
val1 = None
|
||||||
|
val2 = None
|
||||||
|
if "s1" in job[JOB_PARAMETERS]:
|
||||||
|
val1 = f"s1-{job[JOB_PARAMETERS]['s1']}"
|
||||||
|
if "s2" in job[JOB_PARAMETERS]:
|
||||||
|
val2 = f"s2-{job[JOB_PARAMETERS]['s2']}"
|
||||||
|
val = None
|
||||||
|
if val1 and val2:
|
||||||
|
val = f"{val1}/{val2}"
|
||||||
|
if val and val in values:
|
||||||
|
values.remove(val)
|
||||||
|
self.assertEqual(len(values), 0)
|
||||||
|
|
||||||
|
# Test jobFunc performs as expected
|
||||||
|
def testJobFunc(self)->None:
|
||||||
|
file_path = os.path.join(TEST_MONITOR_BASE, "test")
|
||||||
|
result_path = os.path.join(TEST_MONITOR_BASE, "output")
|
||||||
|
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
f.write("250")
|
||||||
|
|
||||||
|
file_hash = get_file_hash(file_path, SHA256)
|
||||||
|
|
||||||
|
pattern = FileEventPattern(
|
||||||
|
"pattern",
|
||||||
|
file_path,
|
||||||
|
"recipe_one",
|
||||||
|
"infile",
|
||||||
|
parameters={
|
||||||
|
"extra":"A line from a test Pattern",
|
||||||
|
"outfile": result_path
|
||||||
|
})
|
||||||
|
recipe = PythonRecipe(
|
||||||
|
"recipe_one", COMPLETE_PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
rule = create_rule(pattern, recipe)
|
||||||
|
|
||||||
|
params_dict = {
|
||||||
|
"extra":"extra",
|
||||||
|
"infile":file_path,
|
||||||
|
"outfile": result_path
|
||||||
|
}
|
||||||
|
|
||||||
|
job_dict = create_job(
|
||||||
|
JOB_TYPE_PYTHON,
|
||||||
|
create_watchdog_event(
|
||||||
|
file_path,
|
||||||
|
rule,
|
||||||
|
TEST_MONITOR_BASE,
|
||||||
|
file_hash
|
||||||
|
),
|
||||||
|
extras={
|
||||||
|
JOB_PARAMETERS:params_dict,
|
||||||
|
JOB_HASH: file_hash,
|
||||||
|
PYTHON_FUNC:python_job_func,
|
||||||
|
PYTHON_OUTPUT_DIR:TEST_JOB_OUTPUT,
|
||||||
|
PYTHON_EXECUTION_BASE:TEST_HANDLER_BASE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
job_dir = os.path.join(
|
||||||
|
job_dict[PYTHON_EXECUTION_BASE], job_dict[JOB_ID])
|
||||||
|
make_dir(job_dir)
|
||||||
|
|
||||||
|
meta_file = os.path.join(job_dir, META_FILE)
|
||||||
|
write_yaml(job_dict, meta_file)
|
||||||
|
|
||||||
|
param_file = os.path.join(job_dir, PARAMS_FILE)
|
||||||
|
write_yaml(params_dict, param_file)
|
||||||
|
|
||||||
|
base_file = os.path.join(job_dir, get_base_file(JOB_TYPE_PYTHON))
|
||||||
|
write_notebook(APPENDING_NOTEBOOK, base_file)
|
||||||
|
write_file(lines_to_string(COMPLETE_PYTHON_SCRIPT), base_file)
|
||||||
|
|
||||||
|
python_job_func(job_dict)
|
||||||
|
|
||||||
|
job_dir = os.path.join(TEST_HANDLER_BASE, job_dict[JOB_ID])
|
||||||
|
self.assertTrue(os.path.exists(job_dir))
|
||||||
|
meta_path = os.path.join(job_dir, META_FILE)
|
||||||
|
self.assertTrue(os.path.exists(meta_path))
|
||||||
|
|
||||||
|
status = read_yaml(meta_path)
|
||||||
|
self.assertIsInstance(status, dict)
|
||||||
|
self.assertIn(JOB_STATUS, status)
|
||||||
|
self.assertEqual(status[JOB_STATUS], job_dict[JOB_STATUS])
|
||||||
|
self.assertNotIn(JOB_ERROR, status)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(job_dir, get_base_file(JOB_TYPE_PYTHON))))
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(job_dir, PARAMS_FILE)))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(job_dir, get_job_file(JOB_TYPE_PYTHON))))
|
||||||
|
self.assertTrue(os.path.exists(
|
||||||
|
os.path.join(job_dir, get_result_file(JOB_TYPE_PYTHON))))
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists(result_path))
|
||||||
|
|
||||||
|
# Test jobFunc doesn't execute with no args
|
||||||
|
def testJobFuncBadArgs(self)->None:
|
||||||
|
try:
|
||||||
|
python_job_func({})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(len(os.listdir(TEST_HANDLER_BASE)), 0)
|
||||||
|
self.assertEqual(len(os.listdir(TEST_JOB_OUTPUT)), 0)
|
||||||
|
|
||||||
|
# TODO test default parameter function execution
|
@ -6,7 +6,7 @@ import unittest
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from conductors import LocalPythonConductor
|
from conductors import LocalPythonConductor
|
||||||
from core.correctness.vars import RESULT_FILE
|
from core.correctness.vars import get_result_file, JOB_TYPE_PAPERMILL
|
||||||
from core.functionality import make_dir, read_notebook
|
from core.functionality import make_dir, read_notebook
|
||||||
from core.meow import BaseMonitor, BaseHandler, BaseConductor
|
from core.meow import BaseMonitor, BaseHandler, BaseConductor
|
||||||
from core.runner import MeowRunner
|
from core.runner import MeowRunner
|
||||||
@ -187,7 +187,8 @@ class MeowTests(unittest.TestCase):
|
|||||||
job_dir = os.path.join(TEST_JOB_OUTPUT, job_id)
|
job_dir = os.path.join(TEST_JOB_OUTPUT, job_id)
|
||||||
self.assertEqual(len(os.listdir(job_dir)), 5)
|
self.assertEqual(len(os.listdir(job_dir)), 5)
|
||||||
|
|
||||||
result = read_notebook(os.path.join(job_dir, RESULT_FILE))
|
result = read_notebook(
|
||||||
|
os.path.join(job_dir, get_result_file(JOB_TYPE_PAPERMILL)))
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
|
|
||||||
output_path = os.path.join(TEST_MONITOR_BASE, "output", "A.txt")
|
output_path = os.path.join(TEST_MONITOR_BASE, "output", "A.txt")
|
||||||
@ -279,7 +280,8 @@ class MeowTests(unittest.TestCase):
|
|||||||
mid_job_dir = os.path.join(TEST_JOB_OUTPUT, job_id)
|
mid_job_dir = os.path.join(TEST_JOB_OUTPUT, job_id)
|
||||||
self.assertEqual(len(os.listdir(mid_job_dir)), 5)
|
self.assertEqual(len(os.listdir(mid_job_dir)), 5)
|
||||||
|
|
||||||
result = read_notebook(os.path.join(mid_job_dir, RESULT_FILE))
|
result = read_notebook(
|
||||||
|
os.path.join(mid_job_dir, get_result_file(JOB_TYPE_PAPERMILL)))
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
|
|
||||||
mid_output_path = os.path.join(TEST_MONITOR_BASE, "middle", "A.txt")
|
mid_output_path = os.path.join(TEST_MONITOR_BASE, "middle", "A.txt")
|
||||||
@ -293,7 +295,8 @@ class MeowTests(unittest.TestCase):
|
|||||||
final_job_dir = os.path.join(TEST_JOB_OUTPUT, job_id)
|
final_job_dir = os.path.join(TEST_JOB_OUTPUT, job_id)
|
||||||
self.assertEqual(len(os.listdir(final_job_dir)), 5)
|
self.assertEqual(len(os.listdir(final_job_dir)), 5)
|
||||||
|
|
||||||
result = read_notebook(os.path.join(final_job_dir, RESULT_FILE))
|
result = read_notebook(os.path.join(final_job_dir,
|
||||||
|
get_result_file(JOB_TYPE_PAPERMILL)))
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
|
|
||||||
final_output_path = os.path.join(TEST_MONITOR_BASE, "output", "A.txt")
|
final_output_path = os.path.join(TEST_MONITOR_BASE, "output", "A.txt")
|
||||||
@ -305,8 +308,8 @@ class MeowTests(unittest.TestCase):
|
|||||||
self.assertEqual(data,
|
self.assertEqual(data,
|
||||||
"Initial Data\nA line from Pattern 1\nA line from Pattern 2")
|
"Initial Data\nA line from Pattern 1\nA line from Pattern 2")
|
||||||
|
|
||||||
# TODO sweep tests
|
# TODO sweep execution test
|
||||||
# TODO adding tests with numpy
|
# TODO adding tests with numpy or other external dependency
|
||||||
# TODO test getting job cannot handle
|
# TODO test getting job cannot handle
|
||||||
# TODO test getting event cannot handle
|
# TODO test getting event cannot handle
|
||||||
# TODO test with several matched monitors
|
# TODO test with several matched monitors
|
||||||
|
@ -9,7 +9,7 @@ from typing import Any, Union
|
|||||||
from core.correctness.validation import check_type, check_implementation, \
|
from core.correctness.validation import check_type, check_implementation, \
|
||||||
valid_string, valid_dict, valid_list, valid_existing_file_path, \
|
valid_string, valid_dict, valid_list, valid_existing_file_path, \
|
||||||
valid_existing_dir_path, valid_non_existing_path, valid_event, valid_job, \
|
valid_existing_dir_path, valid_non_existing_path, valid_event, valid_job, \
|
||||||
setup_debugging, valid_watchdog_event
|
setup_debugging, valid_watchdog_event, check_callable
|
||||||
from core.correctness.vars import VALID_NAME_CHARS, SHA256, EVENT_TYPE, \
|
from core.correctness.vars import VALID_NAME_CHARS, SHA256, EVENT_TYPE, \
|
||||||
EVENT_PATH, JOB_TYPE, JOB_EVENT, JOB_ID, JOB_PATTERN, JOB_RECIPE, \
|
EVENT_PATH, JOB_TYPE, JOB_EVENT, JOB_ID, JOB_PATTERN, JOB_RECIPE, \
|
||||||
JOB_RULE, JOB_STATUS, JOB_CREATE_TIME, EVENT_RULE, WATCHDOG_BASE, \
|
JOB_RULE, JOB_STATUS, JOB_CREATE_TIME, EVENT_RULE, WATCHDOG_BASE, \
|
||||||
@ -304,7 +304,7 @@ class CorrectnessTests(unittest.TestCase):
|
|||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
setup_debugging(stream, "1")
|
setup_debugging(stream, "1")
|
||||||
|
|
||||||
#Test watchdog event dict
|
# Test watchdog event dict
|
||||||
def testWatchdogEventValidation(self)->None:
|
def testWatchdogEventValidation(self)->None:
|
||||||
valid_watchdog_event({
|
valid_watchdog_event({
|
||||||
EVENT_TYPE: "test",
|
EVENT_TYPE: "test",
|
||||||
@ -337,3 +337,10 @@ class CorrectnessTests(unittest.TestCase):
|
|||||||
|
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
valid_event({})
|
valid_event({})
|
||||||
|
|
||||||
|
# Test check_callable
|
||||||
|
def testCheckCallable(self)->None:
|
||||||
|
check_callable(make_dir)
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
check_callable("a")
|
||||||
|
Reference in New Issue
Block a user