diff --git a/core/correctness/validation.py b/core/correctness/validation.py index 846363d..38ae081 100644 --- a/core/correctness/validation.py +++ b/core/correctness/validation.py @@ -221,3 +221,5 @@ def valid_non_existing_path(variable:str, allow_base:bool=False): if dirname(variable) and not exists(dirname(variable)): raise ValueError( f"Route to requested path '{variable}' does not exist.") + +# TODO add validation for requirement functions \ No newline at end of file diff --git a/functionality/requirements.py b/functionality/requirements.py index ce45521..4b2643d 100644 --- a/functionality/requirements.py +++ b/functionality/requirements.py @@ -5,6 +5,9 @@ requirements, and assessed by handlers and conductors. Author(s): David Marchant """ +from importlib.util import find_spec +from os.path import basename +from sys import version_info, prefix, base_prefix from typing import Any, Dict, List, Tuple, Union from core.correctness.validation import check_type @@ -14,7 +17,6 @@ REQ_PYTHON_MODULES = "modules" REQ_PYTHON_VERSION = "version" REQ_PYTHON_ENVIRONMENT = "environment" - def create_requirement_dict(key:str, entires:Dict[str,Any] )->Tuple[str,Dict[str,Any]]: return key, entires @@ -41,3 +43,65 @@ def create_python_requirements(modules:Union[str,List[str]]="", python_reqs[REQ_PYTHON_ENVIRONMENT] = environment return create_requirement_dict(REQUIREMENT_PYTHON, python_reqs) + +def check_requirements(requirements:Dict[str,Any])->bool: + check_type(requirements, dict, hint="check_requirements.requirements") + result = True + reason = "" + for key, value in SUPPORTERD_REQS.items(): + if key in requirements: + status, msg = value(requirements[key]) + if not status: + result = False + reason = msg + return result, reason + +# TODO integrate me into conductors +def check_python_requirements(reqs:Dict[str,Any])->bool: + check_type(reqs, dict, + hint=f"check_requirements.reqs[{REQUIREMENT_PYTHON}]") + + if REQ_PYTHON_ENVIRONMENT in reqs: + if base_prefix == prefix: + return False, "" + + if basename(prefix) != reqs[REQ_PYTHON_ENVIRONMENT]: + return False, "" + + # TODO expand these so you can specify versions + if REQ_PYTHON_MODULES in reqs: + for module in reqs[REQ_PYTHON_MODULES]: + found_spec = find_spec(module) + if found_spec is None: + return False, f"Could not find module '{module}'." + + if REQ_PYTHON_VERSION in reqs: + major, minor, micro = parse_versions(reqs[REQ_PYTHON_VERSION]) + + msg = f"Avaiable Python version number '{version_info[0]}." \ + f"{version_info[1]}.{version_info[2]}' does not meet requested " \ + f"{reqs[REQ_PYTHON_VERSION]}." + if major and int(version_info[0]) < major: + return False, msg + if minor and int(version_info[0]) <= major \ + and int(version_info[1]) < minor: + return False, msg + if micro and int(version_info[0]) <= major \ + and int(version_info[1]) <= minor \ + and int(version_info[2]) < micro: + return False, msg + + return True, "" + +def parse_versions(version:str)->None: + parts = version.split('.') + if len(parts) == 1: + return int(parts[0]), None, None + elif len(parts) == 2: + return int(parts[0]), int(parts[1]), None + else: + return int(parts[0]), int(parts[1]), int(parts[2]) + +SUPPORTERD_REQS = { + REQUIREMENT_PYTHON: check_python_requirements, +} diff --git a/tests/test_functionality.py b/tests/test_functionality.py index d971cd1..0fd915a 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -6,6 +6,8 @@ import os from datetime import datetime from multiprocessing import Pipe, Queue +from os.path import basename +from sys import prefix, base_prefix from time import sleep from typing import Dict @@ -32,6 +34,7 @@ from functionality.parameterisation import parameterize_jupyter_notebook, \ parameterize_python_script from functionality.process_io import wait from functionality.requirements import create_python_requirements, \ + check_requirements, \ REQUIREMENT_PYTHON, REQ_PYTHON_ENVIRONMENT, REQ_PYTHON_MODULES, \ REQ_PYTHON_VERSION from patterns import FileEventPattern @@ -899,8 +902,8 @@ class RequirementsTest(unittest.TestCase): super().tearDown() teardown() - # Test Python requirement testings - def testPythonRequirementCreation(self)->None: + # Test structure of Python requirement testings + def testPythonRequirementStructuring(self)->None: key, reqs = create_python_requirements() self.assertIsInstance(key, str) @@ -971,5 +974,99 @@ class RequirementsTest(unittest.TestCase): self.assertIsInstance(reqs[REQ_PYTHON_ENVIRONMENT], str) self.assertEqual(reqs[REQ_PYTHON_ENVIRONMENT], "env") - # TODO expand me and add values for other attributes - \ No newline at end of file + # Test version values of Python requirement testings + def testPythonRequirementsVersion(self)->None: + key, python_reqs = create_python_requirements(version="3.10.6") + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertTrue(status) + + key, python_reqs = create_python_requirements(version="2.5.9") + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertTrue(status) + + key, python_reqs = create_python_requirements(version="4.1.1") + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertFalse(status) + + # Test module values of Python requirement testings + def testPythonRequirementsModules(self)->None: + key, python_reqs = create_python_requirements(modules=[ + "papermill", "sys", "typing" + ]) + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertTrue(status) + + key, python_reqs = create_python_requirements(modules=[ + "does", "not", "exist" + ]) + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertFalse(status) + + key, python_reqs = create_python_requirements(modules=[ + "papermill", "sys", "doesnotexist" + ]) + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertFalse(status) + + # Test environment value of Python requirement testings + def testPythonRequirementsEnvironment(self)->None: + # TODO rework this test so that it actually create and runs in a new + # environment + if prefix != base_prefix: + key, python_reqs = create_python_requirements( + environment=basename(prefix) + ) + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertTrue(status) + + key, python_reqs = create_python_requirements(environment="bad_env") + + reqs = { + key: python_reqs + } + + status, _ = check_requirements(reqs) + + self.assertFalse(status)