From 9dd2d0c209ff9ceab46e739e3cd93806a4019d4c Mon Sep 17 00:00:00 2001 From: PatchOfScotland Date: Tue, 29 Nov 2022 17:15:14 +0100 Subject: [PATCH] initial commit with barebones project structure --- conftest.py | 0 core/correctness/__init__.py | 0 core/correctness/validation.py | 51 +++++++++++++++++ core/correctness/vars.py | 10 ++++ core/meow.py | 96 ++++++++++++++++++++++++++++++++ patterns/FileEventPattern.py | 5 ++ recipes/JupyterNotebookRecipe.py | 5 ++ tests/testAll.sh | 21 +++++++ tests/testCore.py | 67 ++++++++++++++++++++++ tests/testPatterns.py | 16 ++++++ tests/testRecipes.py | 16 ++++++ 11 files changed, 287 insertions(+) create mode 100644 conftest.py create mode 100644 core/correctness/__init__.py create mode 100644 core/correctness/validation.py create mode 100644 core/correctness/vars.py create mode 100644 core/meow.py create mode 100644 patterns/FileEventPattern.py create mode 100644 recipes/JupyterNotebookRecipe.py create mode 100755 tests/testAll.sh create mode 100644 tests/testCore.py create mode 100644 tests/testPatterns.py create mode 100644 tests/testRecipes.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/core/correctness/__init__.py b/core/correctness/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/correctness/validation.py b/core/correctness/validation.py new file mode 100644 index 0000000..04afb69 --- /dev/null +++ b/core/correctness/validation.py @@ -0,0 +1,51 @@ + +def check_input(variable, expected_type, or_none=False): + """ + Checks if a given variable is of the expected type. Raises TypeError or + ValueError as appropriate if any issues are encountered. + + :param variable: (any) variable to check type of + + :param expected_type: (type) expected type of the provided variable + + :param or_none: (optional) boolean of if the variable can be unset. + Default value is False. + + :return: No return. + """ + + if not or_none: + if not isinstance(variable, expected_type): + raise TypeError( + 'Expected type was %s, got %s' + % (expected_type, type(variable)) + ) + else: + if not isinstance(variable, expected_type) \ + and not isinstance(variable, type(None)): + raise TypeError( + 'Expected type was %s or None, got %s' + % (expected_type, type(variable)) + ) + +def valid_string(variable, valid_chars): + """ + Checks that all characters in a given string are present in a provided + list of characters. Will raise an ValueError if unexpected character is + encountered. + + :param variable: (str) variable to check. + + :param valid_chars: (str) collection of valid characters. + + :return: No return. + """ + check_input(variable, str) + check_input(valid_chars, str) + + for char in variable: + if char not in valid_chars: + raise ValueError( + "Invalid character '%s'. Only valid characters are: " + "%s" % (char, valid_chars) + ) diff --git a/core/correctness/vars.py b/core/correctness/vars.py new file mode 100644 index 0000000..bb441d4 --- /dev/null +++ b/core/correctness/vars.py @@ -0,0 +1,10 @@ + +CHAR_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz' +CHAR_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +CHAR_NUMERIC = '0123456789' + +VALID_NAME_CHARS = CHAR_UPPERCASE + CHAR_LOWERCASE + CHAR_NUMERIC + "_-" + +VALID_RECIPE_NAME_CHARS = VALID_NAME_CHARS +VALID_PATTERN_NAME_CHARS = VALID_NAME_CHARS +VALID_RULE_NAME_CHARS = VALID_NAME_CHARS diff --git a/core/meow.py b/core/meow.py new file mode 100644 index 0000000..bc8db1c --- /dev/null +++ b/core/meow.py @@ -0,0 +1,96 @@ + +import core.correctness.vars +import core.correctness.validation + +from typing import Any + +class BaseRecipe: + name: str + recipe: Any + paramaters: dict[str, Any] + def __init__(self, name:str, recipe:Any, parameters:dict[str,Any]={}): + self.__is_valid_name(name) + self.name = name + self.__is_valid_recipe(recipe) + self.recipe = recipe + self.__is_valid_parameters(parameters) + self.paramaters = parameters + + def __new__(cls, *args, **kwargs): + if cls is BaseRecipe: + raise TypeError("BaseRecipe may not be instantiated directly") + + def __is_valid_name(self, name): + core.correctness.validation.valid_string( + name, core.correctness.vars.VALID_RECIPE_NAME_CHARS) + + def __is_valid_recipe(self, recipe): + raise NotImplementedError( + f"Recipe '{self.__class__.__name__}' has not implemented " + "'__is_valid_recipe(self, recipe)' function.") + + def __is_valid_parameters(self, parameters): + raise NotImplementedError( + f"Recipe '{self.__class__.__name__}' has not implemented " + "'__is_valid_parameters(self, parameters)' function.") + +class BasePattern: + name: str + recipe: BaseRecipe + parameters: dict[str, Any] + outputs: dict[str, Any] + def __init__(self, name:str, recipe:BaseRecipe, + parameters:dict[str,Any]={}, outputs:dict[str,Any]={}): + self.__is_valid_name(name) + self.name = name + self.__is_valid_recipe(recipe) + self.recipe = recipe + self.__is_valid_parameters(parameters) + self.paramaters = parameters + self.__is_valid_output(outputs) + self.outputs = outputs + + def __new__(cls, *args, **kwargs): + if cls is BasePattern: + raise TypeError("BasePattern may not be instantiated directly") + + def __is_valid_name(self, name): + core.correctness.validation.valid_string( + name, core.correctness.vars.VALID_PATTERN_NAME_CHARS) + + def __is_valid_recipe(self, recipe): + raise NotImplementedError( + f"Pattern '{self.__class__.__name__}' has not implemented " + "'__is_valid_recipe(self, recipe)' function.") + + def __is_valid_parameters(self, parameters): + raise NotImplementedError( + f"Pattern '{self.__class__.__name__}' has not implemented " + "'__is_valid_parameters(self, parameters)' function.") + + def __is_valid_output(self, outputs): + raise NotImplementedError( + f"Pattern '{self.__class__.__name__}' has not implemented " + "'__is_valid_output(self, outputs)' function.") + +class BaseRule: + name: str + patterns: list[BasePattern] + def __init__(self, name:str, patterns:list[BasePattern]): + self.__is_valid_name(name) + self.name = name + self.__is_valid_patterns(patterns) + self.patterns = patterns + + def __new__(cls, *args, **kwargs): + if cls is BaseRule: + raise TypeError("BaseRule may not be instantiated directly") + + def __is_valid_name(self, name): + core.correctness.validation.valid_string( + name, core.correctness.vars.VALID_RULE_NAME_CHARS) + + def __is_valid_patterns(self, patterns): + raise NotImplementedError( + f"Rule '{self.__class__.__name__}' has not implemented " + "'__is_valid_patterns(self, patterns)' function.") diff --git a/patterns/FileEventPattern.py b/patterns/FileEventPattern.py new file mode 100644 index 0000000..21b534f --- /dev/null +++ b/patterns/FileEventPattern.py @@ -0,0 +1,5 @@ + +from core.meow import BasePattern + +class FileEventPattern(BasePattern): + pass \ No newline at end of file diff --git a/recipes/JupyterNotebookRecipe.py b/recipes/JupyterNotebookRecipe.py new file mode 100644 index 0000000..1e0f45c --- /dev/null +++ b/recipes/JupyterNotebookRecipe.py @@ -0,0 +1,5 @@ + +from core.meow import BaseRecipe + +class JupyterNotebookRecipe(BaseRecipe): + pass \ No newline at end of file diff --git a/tests/testAll.sh b/tests/testAll.sh new file mode 100755 index 0000000..dfab5dd --- /dev/null +++ b/tests/testAll.sh @@ -0,0 +1,21 @@ +#! /bin/bash + +# Need to more to local dir to run tests +starting_working_dir=$(pwd) +script_name=$(basename "$0") +script_dir=$(dirname "$(realpath "$0")") + +cd $script_dir + +# Gather all other test files and run pytest +search_dir=. +for entry in "$search_dir"/* +do + if [[ $entry == ./test* ]] && [[ $entry != ./$script_name ]]; + then + pytest $entry + fi +done + +# Move back to where we called from +cd $starting_working_dir \ No newline at end of file diff --git a/tests/testCore.py b/tests/testCore.py new file mode 100644 index 0000000..6b8872e --- /dev/null +++ b/tests/testCore.py @@ -0,0 +1,67 @@ + +import unittest + +from core.correctness.validation import check_input, valid_string +from core.correctness.vars import VALID_NAME_CHARS +from core.meow import BasePattern, BaseRecipe, BaseRule + + +class CorrectnessTests(unittest.TestCase): + def setUp(self) -> None: + return super().setUp() + + def tearDown(self) -> None: + return super().tearDown() + + def testCheckInput(self): + # Valid input + check_input(1, int) + check_input(0, int) + check_input(False, bool) + check_input(True, bool) + + # Misstyped input + with self.assertRaises(TypeError): + check_input(1, str) + + # Or none + check_input(None, int, or_none=True) + with self.assertRaises(TypeError): + check_input(None, int, or_none=False) + + def testValidString(self): + # Valid input + valid_string("", "") + valid_string("David_Marchant", VALID_NAME_CHARS) + + # Misstyped input + with self.assertRaises(TypeError): + valid_string(1, VALID_NAME_CHARS) + with self.assertRaises(TypeError): + valid_string("David_Marchant", 1) + + # Missing chars + with self.assertRaises(ValueError): + valid_string("David Marchant", VALID_NAME_CHARS) + +class MeowTests(unittest.TestCase): + def setUp(self) -> None: + return super().setUp() + + def tearDown(self) -> None: + return super().tearDown() + + def testBaseRecipe(self): + # Should not be implementable on its own + with self.assertRaises(TypeError): + BaseRecipe("", "") + + def testBasePattern(self): + # Should not be implementable on its own + with self.assertRaises(TypeError): + BasePattern("", "") + + def testBaseRule(self): + # Should not be implementable on its own + with self.assertRaises(TypeError): + BaseRule("", "") diff --git a/tests/testPatterns.py b/tests/testPatterns.py new file mode 100644 index 0000000..fa169b3 --- /dev/null +++ b/tests/testPatterns.py @@ -0,0 +1,16 @@ + +import unittest + +from patterns.FileEventPattern import FileEventPattern + + +class CorrectnessTests(unittest.TestCase): + def setUp(self) -> None: + return super().setUp() + + def tearDown(self) -> None: + return super().tearDown() + + def testFileEventPattern(self): + pass + diff --git a/tests/testRecipes.py b/tests/testRecipes.py new file mode 100644 index 0000000..5d46c52 --- /dev/null +++ b/tests/testRecipes.py @@ -0,0 +1,16 @@ + + +import unittest + +from recipes.JupyterNotebookRecipe import JupyterNotebookRecipe + + +class CorrectnessTests(unittest.TestCase): + def setUp(self) -> None: + return super().setUp() + + def tearDown(self) -> None: + return super().tearDown() + + def testJupyterNotebookRecipe(self): + pass \ No newline at end of file