From c30c581c15758b38e8c479686fca19b93b2671e4 Mon Sep 17 00:00:00 2001 From: Scott Lindauer Date: Tue, 19 Mar 2024 14:01:40 -0400 Subject: [PATCH 01/24] First outline draft of write_score_wrapper.py --- src/sasctl/pzmm/write_score_wrapper.py | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/sasctl/pzmm/write_score_wrapper.py diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py new file mode 100644 index 00000000..3a25e569 --- /dev/null +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -0,0 +1,36 @@ +# Copyright (c) 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +- Collect inputs and outputs from inputVar.json and outputVar.json + - Throw error or include optional arguments +- How to handle imports? + - pass a list of imports, etc +- How do we handle model loading? + - re.sub() with "Path(settings.pickle_path) / " and file name + - Document replacement of path with inclusion of settings.pickle_path +- What form do we want to take the score snippet in? + - function...we'll parse and drop in the contents + - file, but tell us the score function...include other functions and pull out score function info +""" + + +class ScoreWrapper: + score_wrapper: str = "" + + @classmethod + def write_score_wrapper( + cls, + ): + +""" + +import settings + + add Path(settings.pickle_path +function definition +docstring: output string +globalize model and try/except block + +clean up output +""" \ No newline at end of file From 991c79f6570f4001b5ec4c6fbb3298693498e7d9 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 21 Mar 2024 07:52:32 -0400 Subject: [PATCH 02/24] initial commit for score_Wrapper --- src/sasctl/pzmm/write_score_wrapper.py | 107 ++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index 3a25e569..b7525923 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -1,17 +1,20 @@ # Copyright (c) 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +from typing import List +from pathlib import Path +import re """ - Collect inputs and outputs from inputVar.json and outputVar.json - - Throw error or include optional arguments + - Throw error or include optional arguments - How to handle imports? - - pass a list of imports, etc + - pass a list of imports, etc - How do we handle model loading? - - re.sub() with "Path(settings.pickle_path) / " and file name - - Document replacement of path with inclusion of settings.pickle_path -- What form do we want to take the score snippet in? - - function...we'll parse and drop in the contents - - file, but tell us the score function...include other functions and pull out score function info + - re.sub() with "Path(settings.pickle_path) / " and file name + - Document replacement of path with inclusion of settings.pickle_path +- What form do we want to take the score snippet in? add boolean to check whether user is passing in a function or passing in the score snipped itslef if so adjust the code accordingly + - function...we'll parse and drop in the contents + - file, but tell us the score function...include other functions and pull out score function info """ @@ -19,18 +22,98 @@ class ScoreWrapper: score_wrapper: str = "" @classmethod - def write_score_wrapper( - cls, - ): + def write_score_wrapper(cls, + imports: List[str], + model_load: str, + function_definition: str, + model_scoring_code: str, + is_function: bool, + is_file: bool) -> None: + """ + Method to generate scoring code and add it to cls.score_wrapper. + + + Parameters: + imports (List[str]): List of modules to import. + model_load (str): Name of the model to load. + function_definition (str): Function definition code. + model_scoring_code (str): Model scoring code. how the function is called. + + + + Returns: + QUESTION FOR SCOTT AND DJ WHAT HAPPENS NEXT? WHERE AM I ADDING THIS TO? HOW DOES IT POPULATE THE FILE? def write_wrapper_to_file? + """ + # Adding imports to score_wrapper + for module in imports: + cls.score_wrapper += f"import {module}\n" + cls.score_wrapper += "from pathlib import Path\n" + cls.score_wrapper += "import settings\n" + + # Adding model load to score_wrapper + cls.score_wrapper += f"{model_load} = Path(settings.pickle_path) / '{model_load}'\n" + + # Adding function definition to score_wrapper + if is_function: + # if the function definition is given. + cls.score_wrapper += f"{function_definition}\n" + elif is_file: + # file, but tell us the score function...include other functions and pull out score function info use re to find all the functions specified maybe add parameters for if is_file is true to know how many functions user wants to include + # Extract all function definitions from the file + with open(function_definition, 'r') as f: + file_content = f.read() + # This only works for function definitions on one line like this def score(input_data): + function_defs = re.findall(r"(def .+?:)", file_content) + for function in function_defs: + cls.score_wrapper += f"{function}\n" + + # Adding model scoring code inside a try/except block to score_wrapper + cls.score_wrapper += "try:\n" + cls.score_wrapper += f" global {model_load}\n" + cls.score_wrapper += f" {model_scoring_code}\n" + cls.score_wrapper += "except Exception as e:\n" + cls.score_wrapper += " print(f'Error: {e}')\n" + + def write_wrapper_to_file(cls, + path: str, + model_prefix: str, + file_name: str) -> None: + """ + Method to write the score wrapper to a file. + Parameters: + file_name (str): Name of the file to write the score wrapper to. + Returns: + None + """ + score_wrapper_path = Path(score_wrapper_path) / f"score_{model_prefix}.py" + with open(score_wrapper_path, "w") as score_wrapper_file: + score_wrapper_file.write(cls.score_wrapper) + """ import settings - add Path(settings.pickle_path + add Path(settings.pickle_path function definition docstring: output string globalize model and try/except block clean up output -""" \ No newline at end of file +""" + +# example of definition and model scoring code +function_definition = """ +def score(input_data): + # Preprocess data + processed_data = preprocess(input_data) + # Score the processed data + return my_model.predict(processed_data) +""" + +model_scoring_code = """ +input_data = get_input_data() +score(input_data) +""" + + From d00f142083bfd6ff2dd8948ce545e1ddb719915c Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 21 Mar 2024 07:55:57 -0400 Subject: [PATCH 03/24] hot fix --- src/sasctl/pzmm/write_score_wrapper.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index b7525923..95dab03a 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -28,7 +28,7 @@ def write_score_wrapper(cls, function_definition: str, model_scoring_code: str, is_function: bool, - is_file: bool) -> None: + is_file: bool) -> str: """ Method to generate scoring code and add it to cls.score_wrapper. @@ -39,10 +39,7 @@ def write_score_wrapper(cls, function_definition (str): Function definition code. model_scoring_code (str): Model scoring code. how the function is called. - - - Returns: - QUESTION FOR SCOTT AND DJ WHAT HAPPENS NEXT? WHERE AM I ADDING THIS TO? HOW DOES IT POPULATE THE FILE? def write_wrapper_to_file? + Returns: cls.score_wrapper (str): Score wrapper code. """ # Adding imports to score_wrapper for module in imports: @@ -74,6 +71,8 @@ def write_score_wrapper(cls, cls.score_wrapper += "except Exception as e:\n" cls.score_wrapper += " print(f'Error: {e}')\n" + return cls.score_wrapper + def write_wrapper_to_file(cls, path: str, model_prefix: str, @@ -85,7 +84,7 @@ def write_wrapper_to_file(cls, Returns: None """ - score_wrapper_path = Path(score_wrapper_path) / f"score_{model_prefix}.py" + score_wrapper_path = Path(path) / f"score_{model_prefix}.py" with open(score_wrapper_path, "w") as score_wrapper_file: score_wrapper_file.write(cls.score_wrapper) From 3aa0c30f2b506ffae3cf487b817c402a4f38a989 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 22 Mar 2024 09:44:04 -0400 Subject: [PATCH 04/24] breaking file input and function input into different class methods --- src/sasctl/pzmm/write_score_wrapper.py | 141 +++++++++++-------------- 1 file changed, 62 insertions(+), 79 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index 95dab03a..a021889a 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -4,83 +4,93 @@ from pathlib import Path import re -""" -- Collect inputs and outputs from inputVar.json and outputVar.json - - Throw error or include optional arguments -- How to handle imports? - - pass a list of imports, etc -- How do we handle model loading? - - re.sub() with "Path(settings.pickle_path) / " and file name - - Document replacement of path with inclusion of settings.pickle_path -- What form do we want to take the score snippet in? add boolean to check whether user is passing in a function or passing in the score snipped itslef if so adjust the code accordingly - - function...we'll parse and drop in the contents - - file, but tell us the score function...include other functions and pull out score function info -""" - - class ScoreWrapper: score_wrapper: str = "" @classmethod - def write_score_wrapper(cls, - imports: List[str], - model_load: str, - function_definition: str, - model_scoring_code: str, - is_function: bool, - is_file: bool) -> str: + def write_score_wrapper_function_input(cls, + imports: List[str], + function_definition: str, + function_body: str, + model_load: str, + model_name_with_file_extension: str): """ - Method to generate scoring code and add it to cls.score_wrapper. - + Method to generate scoring code from a function and add it to cls.score_wrapper. Parameters: imports (List[str]): List of modules to import. - model_load (str): Name of the model to load. function_definition (str): Function definition code. - model_scoring_code (str): Model scoring code. how the function is called. + function_body (str): Function body code. + model_load (str): Name of the model to load. + model_name_with_file_extension (str): Name of the model file with extension. - Returns: cls.score_wrapper (str): Score wrapper code. + Returns: + cls.score_wrapper (str): The scoring code. """ - # Adding imports to score_wrapper for module in imports: cls.score_wrapper += f"import {module}\n" - cls.score_wrapper += "from pathlib import Path\n" - cls.score_wrapper += "import settings\n" - - # Adding model load to score_wrapper - cls.score_wrapper += f"{model_load} = Path(settings.pickle_path) / '{model_load}'\n" + + cls.score_wrapper += "from pathlib import Path\n" + cls.score_wrapper += "import settings\n\n\n" + + cls.score_wrapper += f"model_path = Path(settings.pickle_path) / '{model_name_with_file_extension}'\n\n" + cls.score_wrapper += f"with open(Path(settings.pickle_path) / '{model_name_with_file_extension}', 'rb') as f:\n\tmodel = {model_load}\n\n" + + cls.score_wrapper += f"{function_definition}:\n" + cls.score_wrapper += "\tglobal model\n" + cls.score_wrapper += "\ttry:\n" + cls.score_wrapper += f"\t\t{function_body}\n" + cls.score_wrapper += "\texcept Exception as e:\n" + cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" + return cls.score_wrapper - # Adding function definition to score_wrapper - if is_function: - # if the function definition is given. - cls.score_wrapper += f"{function_definition}\n" - elif is_file: - # file, but tell us the score function...include other functions and pull out score function info use re to find all the functions specified maybe add parameters for if is_file is true to know how many functions user wants to include - # Extract all function definitions from the file - with open(function_definition, 'r') as f: - file_content = f.read() - # This only works for function definitions on one line like this def score(input_data): - function_defs = re.findall(r"(def .+?:)", file_content) - for function in function_defs: - cls.score_wrapper += f"{function}\n" + @classmethod + def write_score_wrapper_file_input(cls, + imports: List[str], + file_path: str, + model_load: str, + model_scoring_code: str, + model_name_with_file_extension: str): + """ + Method to generate scoring code from a file and add it to cls.score_wrapper. - # Adding model scoring code inside a try/except block to score_wrapper - cls.score_wrapper += "try:\n" - cls.score_wrapper += f" global {model_load}\n" - cls.score_wrapper += f" {model_scoring_code}\n" - cls.score_wrapper += "except Exception as e:\n" - cls.score_wrapper += " print(f'Error: {e}')\n" + Parameters: + imports (List[str]): List of modules to import. + file_path (str): Path to the file containing the scoring code. + model_load (str): Name of the model to load. + model_scoring_code (str): Model scoring code. How the function is called. + model_name_with_file_extension (str): Name of the model file with extension. + Returns: + cls.score_wrapper (str): The scoring code. + """ + for module in imports: + cls.score_wrapper += f"import {module}\n" + + cls.score_wrapper += "from pathlib import Path\n" + cls.score_wrapper += "import settings\n\n\n" + + cls.score_wrapper += f"model_path = Path(settings.pickle_path) / '{model_name_with_file_extension}'\n\n" + cls.score_wrapper += f"with open(Path(settings.pickle_path) / '{model_name_with_file_extension}', 'rb') as f:\n\tmodel = {model_load}\n\n" + + with open(file_path, 'r') as f: + file_content = f.read() + function_defs = re.findall(r"(def .+?:.*?)(?=def |\Z)", file_content, re.DOTALL) + for function in function_defs: + cls.score_wrapper += f"{function}\n" + return cls.score_wrapper - + @classmethod def write_wrapper_to_file(cls, path: str, model_prefix: str, file_name: str) -> None: """ Method to write the score wrapper to a file. + Parameters: file_name (str): Name of the file to write the score wrapper to. + Returns: None """ @@ -89,30 +99,3 @@ def write_wrapper_to_file(cls, score_wrapper_file.write(cls.score_wrapper) -""" - -import settings - - add Path(settings.pickle_path -function definition -docstring: output string -globalize model and try/except block - -clean up output -""" - -# example of definition and model scoring code -function_definition = """ -def score(input_data): - # Preprocess data - processed_data = preprocess(input_data) - # Score the processed data - return my_model.predict(processed_data) -""" - -model_scoring_code = """ -input_data = get_input_data() -score(input_data) -""" - - From a9c92e7f1f6804b23befb07f8c02e8747a8be2f3 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 28 Mar 2024 09:33:23 -0400 Subject: [PATCH 05/24] Updated file input 3/28 --- src/sasctl/pzmm/write_score_wrapper.py | 71 +++++++++++++++++--------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index a021889a..b2ae877d 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -3,39 +3,50 @@ from typing import List from pathlib import Path import re +import importlib.util +import inspect +import importlib +import inspect +import sys +import os +import textwrap + class ScoreWrapper: score_wrapper: str = "" @classmethod - def write_score_wrapper_function_input(cls, - imports: List[str], - function_definition: str, - function_body: str, - model_load: str, + def write_score_wrapper_function_input(cls, + imports: List[str], + function_definition: str, + function_body: str, + model_load: str, model_name_with_file_extension: str): """ Method to generate scoring code from a function and add it to cls.score_wrapper. Parameters: imports (List[str]): List of modules to import. - function_definition (str): Function definition code. - function_body (str): Function body code. + function_definition (str): Function definition. + function_body (str): Function body. model_load (str): Name of the model to load. model_name_with_file_extension (str): Name of the model file with extension. Returns: cls.score_wrapper (str): The scoring code. """ + # Import Modules for module in imports: cls.score_wrapper += f"import {module}\n" - + # Import pathlib and settings cls.score_wrapper += "from pathlib import Path\n" cls.score_wrapper += "import settings\n\n\n" + # Load the model cls.score_wrapper += f"model_path = Path(settings.pickle_path) / '{model_name_with_file_extension}'\n\n" cls.score_wrapper += f"with open(Path(settings.pickle_path) / '{model_name_with_file_extension}', 'rb') as f:\n\tmodel = {model_load}\n\n" + # Define the score function and add the function body specified cls.score_wrapper += f"{function_definition}:\n" cls.score_wrapper += "\tglobal model\n" cls.score_wrapper += "\ttry:\n" @@ -45,41 +56,51 @@ def write_score_wrapper_function_input(cls, return cls.score_wrapper @classmethod - def write_score_wrapper_file_input(cls, - imports: List[str], - file_path: str, - model_load: str, - model_scoring_code: str, - model_name_with_file_extension: str): + def write_score_wrapper_file_input(cls, + imports: List[str], + file_path: str, + model_load: str, + model_name_with_file_extension: str, + score_function_body: str, + ): """ Method to generate scoring code from a file and add it to cls.score_wrapper. Parameters: imports (List[str]): List of modules to import. - file_path (str): Path to the file containing the scoring code. + file_path (str): Path to the file containing the additional classes and functions necessary for scoring. model_load (str): Name of the model to load. - model_scoring_code (str): Model scoring code. How the function is called. model_name_with_file_extension (str): Name of the model file with extension. + score_function_body (str): The code needed to evaluate the model. Returns: cls.score_wrapper (str): The scoring code. """ + # write imports to file for module in imports: cls.score_wrapper += f"import {module}\n" - cls.score_wrapper += "from pathlib import Path\n" cls.score_wrapper += "import settings\n\n\n" - cls.score_wrapper += f"model_path = Path(settings.pickle_path) / '{model_name_with_file_extension}'\n\n" - cls.score_wrapper += f"with open(Path(settings.pickle_path) / '{model_name_with_file_extension}', 'rb') as f:\n\tmodel = {model_load}\n\n" + # open a file and read all of the class definitions and functions, and write them to cls.score_wrapper maintain spacing of the code being written + with open(file_path, "r") as file: + code = file.read() + cls.score_wrapper += code + + # load model + cls.score_wrapper += f"\n\nmodel_path = Path(settings.pickle_path) / '{model_name_with_file_extension}'\n" + cls.score_wrapper += f"{model_load}\n\n" + + # define the generic score function, and append the score_function_body to evaluate the model. + cls.score_wrapper += f"def score(input_data):\n" + cls.score_wrapper += "\tglobal model\n" + cls.score_wrapper += "\ttry:" + cls.score_wrapper += f"\n{score_function_body}\n" + cls.score_wrapper += "\n\texcept Exception as e:\n" + cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" - with open(file_path, 'r') as f: - file_content = f.read() - function_defs = re.findall(r"(def .+?:.*?)(?=def |\Z)", file_content, re.DOTALL) - for function in function_defs: - cls.score_wrapper += f"{function}\n" - return cls.score_wrapper + @classmethod def write_wrapper_to_file(cls, path: str, From 35681a13a0989cf0a2bf2fc98798056ed0865eec Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 28 Mar 2024 09:56:43 -0400 Subject: [PATCH 06/24] Updated file input 3/28 --- src/sasctl/pzmm/write_score_wrapper.py | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index b2ae877d..573a8e1e 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -10,6 +10,7 @@ import sys import os import textwrap +import ast class ScoreWrapper: @@ -53,6 +54,10 @@ def write_score_wrapper_function_input(cls, cls.score_wrapper += f"\t\t{function_body}\n" cls.score_wrapper += "\texcept Exception as e:\n" cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" + # Validate syntax before returning + if not cls.validate_score_wrapper_syntax(cls.score_wrapper): + raise SyntaxError("Syntax error in generated code.") + return cls.score_wrapper @classmethod @@ -99,6 +104,10 @@ def write_score_wrapper_file_input(cls, cls.score_wrapper += "\n\texcept Exception as e:\n" cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" + # Validate Syntax before returning + if not cls.validate_score_wrapper_syntax(cls.score_wrapper): + raise SyntaxError("Syntax error in generated code.") + return cls.score_wrapper @classmethod @@ -119,4 +128,23 @@ def write_wrapper_to_file(cls, with open(score_wrapper_path, "w") as score_wrapper_file: score_wrapper_file.write(cls.score_wrapper) + @classmethod + def validate_score_wrapper_syntax(cls, code: str) -> bool: + """ + Method to perform a syntax check on the provided code. + + Parameters: + code (str): Code to be checked for syntax. + + Returns: + bool: True if syntax is correct, False otherwise. + """ + try: + ast.parse(code) + return True + except SyntaxError: + return False + + + From da8d53882dccc99ff9f1c056da044720fe568a97 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 29 Mar 2024 08:31:17 -0400 Subject: [PATCH 07/24] adding score_wrapper param to import_model.py --- src/sasctl/pzmm/import_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index dc66deab..7ec9d658 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -199,6 +199,7 @@ def import_model( target_values: Optional[List[str]] = None, overwrite_project_properties: Optional[bool] = False, target_index: Optional[int] = None, + score_wrapper: Optional[bool] = False, **kwargs, ) -> Tuple[RestObj, Union[dict, str, Path]]: """ @@ -311,7 +312,7 @@ def import_model( pickle_type = mlflow_details["serialization_format"] # Import model without generating score code (SAS Viya version invariant) - if input_data is None or not predict_method or not score_metrics: + if input_data is None or not predict_method or not score_metrics or score_wrapper: warn( "The following arguments are required for the automatic generation of " "score code: input_data, predict_method, score_metrics." From d896f8fbdabbbe24d45c9d6acf0c35cc6d1a7989 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 4 Apr 2024 09:45:54 -0400 Subject: [PATCH 08/24] adding docstring for output vars --- src/sasctl/pzmm/write_score_wrapper.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index 573a8e1e..9aeab51e 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -22,7 +22,8 @@ def write_score_wrapper_function_input(cls, function_definition: str, function_body: str, model_load: str, - model_name_with_file_extension: str): + model_name_with_file_extension: str, + output_variables: List[str]): """ Method to generate scoring code from a function and add it to cls.score_wrapper. @@ -32,6 +33,7 @@ def write_score_wrapper_function_input(cls, function_body (str): Function body. model_load (str): Name of the model to load. model_name_with_file_extension (str): Name of the model file with extension. + output_variables (List[str]): List of output variables to define in score function Returns: cls.score_wrapper (str): The scoring code. @@ -49,11 +51,16 @@ def write_score_wrapper_function_input(cls, # Define the score function and add the function body specified cls.score_wrapper += f"{function_definition}:\n" + cls.score_wrapper += '\t"' + cls.score_wrapper += "Output Variables: " + ", ".join(output_variables) # Join output variables with comma + cls.score_wrapper += '"\n' cls.score_wrapper += "\tglobal model\n" cls.score_wrapper += "\ttry:\n" cls.score_wrapper += f"\t\t{function_body}\n" cls.score_wrapper += "\texcept Exception as e:\n" cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" + cls.score_wrapper += "\t\treturn None\n" + # Validate syntax before returning if not cls.validate_score_wrapper_syntax(cls.score_wrapper): raise SyntaxError("Syntax error in generated code.") @@ -67,6 +74,7 @@ def write_score_wrapper_file_input(cls, model_load: str, model_name_with_file_extension: str, score_function_body: str, + output_variables: List[str], ): """ Method to generate scoring code from a file and add it to cls.score_wrapper. @@ -77,6 +85,7 @@ def write_score_wrapper_file_input(cls, model_load (str): Name of the model to load. model_name_with_file_extension (str): Name of the model file with extension. score_function_body (str): The code needed to evaluate the model. + output_variables (List[str]): List of output variables to define in score function Returns: cls.score_wrapper (str): The scoring code. @@ -98,11 +107,18 @@ def write_score_wrapper_file_input(cls, # define the generic score function, and append the score_function_body to evaluate the model. cls.score_wrapper += f"def score(input_data):\n" + cls.score_wrapper += '\t"' + cls.score_wrapper += "Output Variables: " + ", ".join(output_variables) # Join output variables with comma + cls.score_wrapper += '"\n' + cls.score_wrapper += "\tglobal model\n" cls.score_wrapper += "\ttry:" cls.score_wrapper += f"\n{score_function_body}\n" cls.score_wrapper += "\n\texcept Exception as e:\n" cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" + cls.score_wrapper += "\t\treturn None\n" + # Need some kind of return value here + return cls.score_wrapper # Validate Syntax before returning if not cls.validate_score_wrapper_syntax(cls.score_wrapper): @@ -147,4 +163,3 @@ def validate_score_wrapper_syntax(cls, code: str) -> bool: - From ff4db6331b88cffc7816481ebb5cc53934e2505e Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 4 Apr 2024 09:48:45 -0400 Subject: [PATCH 09/24] adding docstring for output vars --- src/sasctl/pzmm/import_model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 7ec9d658..88d9eade 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -287,6 +287,9 @@ def import_model( index should match the index of the target outcome in target_values. If target_values are not given, this index should indicate whether the the target probability variable is the first or second variable returned by the model. The default value is 1. + score_wrapper : bool, optional + if the score wrapper method is being used, it shouldn't write the score code as the score wrapper function will handle + the formulation of the score code. kwargs : dict, optional Other keyword arguments are passed to the following function: * sasctl.pzmm.ScoreCode.write_score_code(..., @@ -306,6 +309,7 @@ def import_model( model_files : dict, str, or Path Dictionary representation of all files or the path the model files were generated from. + :param score_wrapper: """ # For mlflow models, overwrite the provided or default pickle_type if mlflow_details: From 1e1dca53aa1088b2a476c362963c3023552d810f Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Sun, 7 Apr 2024 18:47:15 -0400 Subject: [PATCH 10/24] adding score_wrapper comment --- src/sasctl/pzmm/write_score_wrapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index 9aeab51e..754b8b06 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -14,6 +14,7 @@ class ScoreWrapper: + #score_wrapper string to hold score code score_wrapper: str = "" @classmethod From fb16a1ee4b718f0fe83fe1bbcc54bde0a7e387a8 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Sun, 7 Apr 2024 19:23:09 -0400 Subject: [PATCH 11/24] final pieces --- src/sasctl/pzmm/write_score_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index 754b8b06..4576ec2f 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -75,6 +75,7 @@ def write_score_wrapper_file_input(cls, model_load: str, model_name_with_file_extension: str, score_function_body: str, + score_function_input_parameters: str, output_variables: List[str], ): """ @@ -107,7 +108,7 @@ def write_score_wrapper_file_input(cls, cls.score_wrapper += f"{model_load}\n\n" # define the generic score function, and append the score_function_body to evaluate the model. - cls.score_wrapper += f"def score(input_data):\n" + cls.score_wrapper += f"def score({score_function_input_parameters}):\n" cls.score_wrapper += '\t"' cls.score_wrapper += "Output Variables: " + ", ".join(output_variables) # Join output variables with comma cls.score_wrapper += '"\n' @@ -163,4 +164,3 @@ def validate_score_wrapper_syntax(cls, code: str) -> bool: return False - From 59d73d971a696a11920cbafe0d64fb35a6affc6f Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Sun, 7 Apr 2024 19:28:56 -0400 Subject: [PATCH 12/24] hot fix, output: --- src/sasctl/pzmm/write_score_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index 4576ec2f..f0966ef6 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -53,7 +53,7 @@ def write_score_wrapper_function_input(cls, # Define the score function and add the function body specified cls.score_wrapper += f"{function_definition}:\n" cls.score_wrapper += '\t"' - cls.score_wrapper += "Output Variables: " + ", ".join(output_variables) # Join output variables with comma + cls.score_wrapper += "Output: " + ", ".join(output_variables) # Join output variables with comma cls.score_wrapper += '"\n' cls.score_wrapper += "\tglobal model\n" cls.score_wrapper += "\ttry:\n" @@ -110,7 +110,7 @@ def write_score_wrapper_file_input(cls, # define the generic score function, and append the score_function_body to evaluate the model. cls.score_wrapper += f"def score({score_function_input_parameters}):\n" cls.score_wrapper += '\t"' - cls.score_wrapper += "Output Variables: " + ", ".join(output_variables) # Join output variables with comma + cls.score_wrapper += "Output: " + ", ".join(output_variables) # Join output variables with comma cls.score_wrapper += '"\n' cls.score_wrapper += "\tglobal model\n" From 08ec1ef429a60a554af314f26a6444ce01fa2e5f Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Mon, 8 Apr 2024 09:18:28 -0400 Subject: [PATCH 13/24] pushing package import --- src/sasctl/pzmm/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sasctl/pzmm/__init__.py b/src/sasctl/pzmm/__init__.py index 4667bc65..bf341760 100644 --- a/src/sasctl/pzmm/__init__.py +++ b/src/sasctl/pzmm/__init__.py @@ -8,4 +8,5 @@ from .pickle_model import PickleModel from .write_json_files import JSONFiles from .write_score_code import ScoreCode +from .write_score_wrapper import ScoreWrapper from .zip_model import ZipModel From 3e6e167a034f383343bb17bdfd971a7229b90adf Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Thu, 11 Apr 2024 20:59:34 -0400 Subject: [PATCH 14/24] pushing return changes --- src/sasctl/pzmm/write_score_wrapper.py | 36 ++++++++++++++------------ 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/sasctl/pzmm/write_score_wrapper.py b/src/sasctl/pzmm/write_score_wrapper.py index f0966ef6..583da030 100644 --- a/src/sasctl/pzmm/write_score_wrapper.py +++ b/src/sasctl/pzmm/write_score_wrapper.py @@ -20,8 +20,10 @@ class ScoreWrapper: @classmethod def write_score_wrapper_function_input(cls, imports: List[str], - function_definition: str, - function_body: str, + score_function_definition: str, + score_function_body: str, + score_function_exception_body:str, + items_to_return: str, model_load: str, model_name_with_file_extension: str, output_variables: List[str]): @@ -51,23 +53,25 @@ def write_score_wrapper_function_input(cls, cls.score_wrapper += f"with open(Path(settings.pickle_path) / '{model_name_with_file_extension}', 'rb') as f:\n\tmodel = {model_load}\n\n" # Define the score function and add the function body specified - cls.score_wrapper += f"{function_definition}:\n" + cls.score_wrapper += f"{score_function_definition}:\n" cls.score_wrapper += '\t"' cls.score_wrapper += "Output: " + ", ".join(output_variables) # Join output variables with comma cls.score_wrapper += '"\n' cls.score_wrapper += "\tglobal model\n" - cls.score_wrapper += "\ttry:\n" - cls.score_wrapper += f"\t\t{function_body}\n" - cls.score_wrapper += "\texcept Exception as e:\n" - cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" - cls.score_wrapper += "\t\treturn None\n" - - # Validate syntax before returning - if not cls.validate_score_wrapper_syntax(cls.score_wrapper): - raise SyntaxError("Syntax error in generated code.") + cls.score_wrapper += "\ttry:" + cls.score_wrapper += f"\n{score_function_body}\n" + cls.score_wrapper += "\n\texcept Exception as e:\n" + cls.score_wrapper += f"\t\t{score_function_exception_body}\n" + cls.score_wrapper += f"\treturn {items_to_return}\n" return cls.score_wrapper + # # Validate syntax before returning + # if not cls.validate_score_wrapper_syntax(cls.score_wrapper): + # raise SyntaxError("Syntax error in generated code.") + + # return cls.score_wrapper + @classmethod def write_score_wrapper_file_input(cls, imports: List[str], @@ -77,6 +81,8 @@ def write_score_wrapper_file_input(cls, score_function_body: str, score_function_input_parameters: str, output_variables: List[str], + score_function_exception_body: str, + items_to_return: str, ): """ Method to generate scoring code from a file and add it to cls.score_wrapper. @@ -117,10 +123,8 @@ def write_score_wrapper_file_input(cls, cls.score_wrapper += "\ttry:" cls.score_wrapper += f"\n{score_function_body}\n" cls.score_wrapper += "\n\texcept Exception as e:\n" - cls.score_wrapper += "\t\tprint(f'Error: {e}')\n" - cls.score_wrapper += "\t\treturn None\n" - # Need some kind of return value here - return cls.score_wrapper + cls.score_wrapper += f"\t\t{score_function_exception_body}\n" + cls.score_wrapper += f"\treturn {items_to_return}\n" # Validate Syntax before returning if not cls.validate_score_wrapper_syntax(cls.score_wrapper): From 799233d524263e1abcd9f2c2f395af02b069ad92 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 08:19:33 -0400 Subject: [PATCH 15/24] score wrapper adjustment --- src/sasctl/pzmm/import_model.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 88d9eade..9638e1eb 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -315,8 +315,21 @@ def import_model( if mlflow_details: pickle_type = mlflow_details["serialization_format"] + if input_data is score_wrapper: + warn ( + "The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again." + ) + if isinstance(model_files, dict): + zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=True) + else: + zip_io_file = zm.zip_files( + Path(model_files), model_prefix, is_viya4=True + ) + if cls.notebook_output: + print(f"All model files were zipped to {Path(model_files)}.") + # Import model without generating score code (SAS Viya version invariant) - if input_data is None or not predict_method or not score_metrics or score_wrapper: + if input_data is None or not predict_method or not score_metrics: warn( "The following arguments are required for the automatic generation of " "score code: input_data, predict_method, score_metrics." From c24c2decd1e48ca008875096bcbbf0ca439f4378 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 08:37:29 -0400 Subject: [PATCH 16/24] import model changing viya4 flag --- src/sasctl/pzmm/import_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 9638e1eb..b3128504 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -320,10 +320,10 @@ def import_model( "The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again." ) if isinstance(model_files, dict): - zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=True) + zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=False) else: zip_io_file = zm.zip_files( - Path(model_files), model_prefix, is_viya4=True + Path(model_files), model_prefix, is_viya4=False ) if cls.notebook_output: print(f"All model files were zipped to {Path(model_files)}.") From ac912a711a162cec34df08dbdc6e4dd69af0b42c Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 08:39:45 -0400 Subject: [PATCH 17/24] hot fix setting scoreWrapper check to true --- src/sasctl/pzmm/import_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index b3128504..3ddc0dcb 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -315,7 +315,7 @@ def import_model( if mlflow_details: pickle_type = mlflow_details["serialization_format"] - if input_data is score_wrapper: + if score_wrapper is True: warn ( "The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again." ) From d59a7ae067667cad06b876fbc2b887b9c9276295 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 08:59:30 -0400 Subject: [PATCH 18/24] hotfix --- src/sasctl/pzmm/import_model.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 3ddc0dcb..34e86d51 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -327,6 +327,24 @@ def import_model( ) if cls.notebook_output: print(f"All model files were zipped to {Path(model_files)}.") + project_response = mr.get_project(project) + project = project_exists( + project, + project_response, + target_values, + model_files, + overwrite_project_properties, + ) + + # Check if model with same name already exists in project. + model_exists( + project, model_prefix, overwrite_model, version_name=project_version + ) + + model = mr.import_model_from_zip( + model_prefix, project, zip_io_file, version=project_version + ) + return model, model_files # Import model without generating score code (SAS Viya version invariant) if input_data is None or not predict_method or not score_metrics: From 098565e38d6e7eab6bac196ee3b88d0adb85693d Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 09:27:58 -0400 Subject: [PATCH 19/24] hotfix --- src/sasctl/pzmm/import_model.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 34e86d51..9c320685 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -315,9 +315,11 @@ def import_model( if mlflow_details: pickle_type = mlflow_details["serialization_format"] - if score_wrapper is True: - warn ( - "The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again." + # Import model without generating score code (SAS Viya version invariant) + if input_data is None or not predict_method or not score_metrics: + warn( + "The following arguments are required for the automatic generation of " + "score code: input_data, predict_method, score_metrics." ) if isinstance(model_files, dict): zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=False) @@ -327,6 +329,9 @@ def import_model( ) if cls.notebook_output: print(f"All model files were zipped to {Path(model_files)}.") + + # Check if project name provided exists and raise an error or create a + # new project project_response = mr.get_project(project) project = project_exists( project, @@ -345,12 +350,9 @@ def import_model( model_prefix, project, zip_io_file, version=project_version ) return model, model_files - - # Import model without generating score code (SAS Viya version invariant) - if input_data is None or not predict_method or not score_metrics: - warn( - "The following arguments are required for the automatic generation of " - "score code: input_data, predict_method, score_metrics." + elif score_wrapper: + warn ( + "The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again." ) if isinstance(model_files, dict): zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=False) @@ -361,8 +363,6 @@ def import_model( if cls.notebook_output: print(f"All model files were zipped to {Path(model_files)}.") - # Check if project name provided exists and raise an error or create a - # new project project_response = mr.get_project(project) project = project_exists( project, @@ -380,7 +380,9 @@ def import_model( model = mr.import_model_from_zip( model_prefix, project, zip_io_file, version=project_version ) + return model, model_files + # For SAS Viya 4, the score code can be written beforehand and imported with # all the model files elif current_session().version_info() == 4: From ce3d2872f703d154a6b8eb2f7411d1cf740b554f Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 09:31:30 -0400 Subject: [PATCH 20/24] hot fix --- src/sasctl/pzmm/import_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 9c320685..c4e9a81d 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -361,7 +361,7 @@ def import_model( Path(model_files), model_prefix, is_viya4=False ) if cls.notebook_output: - print(f"All model files were zipped to {Path(model_files)}.") + print(f"All model files were zipped tofdfffffffffffff {Path(model_files)}.") project_response = mr.get_project(project) project = project_exists( From 63724e8c3e1ffabc0bf4c8646a3c75fce43b1ea3 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Fri, 12 Apr 2024 09:49:12 -0400 Subject: [PATCH 21/24] fixing output for score wrapper --- src/sasctl/pzmm/import_model.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index c4e9a81d..0498a79d 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -351,9 +351,8 @@ def import_model( ) return model, model_files elif score_wrapper: - warn ( - "The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again." - ) + print("The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again.") + if isinstance(model_files, dict): zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=False) else: @@ -361,7 +360,7 @@ def import_model( Path(model_files), model_prefix, is_viya4=False ) if cls.notebook_output: - print(f"All model files were zipped tofdfffffffffffff {Path(model_files)}.") + print(f"All model files were zipped to {Path(model_files)}.") project_response = mr.get_project(project) project = project_exists( From 0713d6bef718f2c67826d417e82c0612f0be8f24 Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Sun, 14 Apr 2024 17:09:15 -0400 Subject: [PATCH 22/24] changing flag to viya4 --- src/sasctl/pzmm/import_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sasctl/pzmm/import_model.py b/src/sasctl/pzmm/import_model.py index 0498a79d..02b654f6 100644 --- a/src/sasctl/pzmm/import_model.py +++ b/src/sasctl/pzmm/import_model.py @@ -354,10 +354,10 @@ def import_model( print("The user has made use of the score_wrapper argument, score code has already been generated and will not be generated again.") if isinstance(model_files, dict): - zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=False) + zip_io_file = zm.zip_files(model_files, model_prefix, is_viya4=True) else: zip_io_file = zm.zip_files( - Path(model_files), model_prefix, is_viya4=False + Path(model_files), model_prefix, is_viya4=True ) if cls.notebook_output: print(f"All model files were zipped to {Path(model_files)}.") From 76b096c51b2d45fa9a2285793c8086d3bf0ee42e Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Tue, 23 Apr 2024 08:06:10 -0400 Subject: [PATCH 23/24] test_write_score_wrapper.py --- tests/unit/test_write_score_wrapper.py | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/unit/test_write_score_wrapper.py diff --git a/tests/unit/test_write_score_wrapper.py b/tests/unit/test_write_score_wrapper.py new file mode 100644 index 00000000..0e5617e8 --- /dev/null +++ b/tests/unit/test_write_score_wrapper.py @@ -0,0 +1,38 @@ +import unittest +from unittest.mock import Mock +from pathlib import Path +from sasctl import pzmm + +class TestScoreWrapper(unittest.TestCase): + def test_write_score_wrapper_function_input(self): + # mock imports + imports = ['torch', 'pandas'] + score_function_definition = 'def score_function():' + score_function_body = 'print("Scoring")' + score_function_exception_body = 'print("Error in scoring")' + items_to_return = 'result' + model_load = 'model = pickle.load(f)' + model_name_with_file_extension = 'model.pkl' + output_variables = ['output_1', 'output_2'] + + # mok the write_score_wrapper_function_input method + result = pzmm.ScoreWrapper.write_score_wrapper_function_input(imports, score_function_definition, score_function_body, score_function_exception_body, items_to_return, model_load, model_name_with_file_extension, output_variables) + + # assert + self.assertIn('import torch', result) + self.assertIn('import pandas', result) + self.assertIn('def score_function():', result) + self.assertIn('print("Scoring")', result) + self.assertIn('result', result) + self.assertIn('model = pickle.load(f)', result) + self.assertIn('model.pkl', result) + self.assertIn('output_1, output_2', result) + + def test_validate_score_wrapper_syntax(self): + # mock code + code = 'def score_function():\n\tprint("Scoring")\n\tresult = model.predict(data)\n\treturn result\n' + #send through validate score_wrapper_syntax + result = pzmm.ScoreWrapper.validate_score_wrapper_syntax(code) + #assert true + self.assertTrue(result) + From 285d82aea5f3ba0b724840bf709a1e3f0339302e Mon Sep 17 00:00:00 2001 From: Nabeel Merali Date: Tue, 23 Apr 2024 08:12:15 -0400 Subject: [PATCH 24/24] hot fix --- tests/unit/test_write_score_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_write_score_wrapper.py b/tests/unit/test_write_score_wrapper.py index 0e5617e8..8c7306b5 100644 --- a/tests/unit/test_write_score_wrapper.py +++ b/tests/unit/test_write_score_wrapper.py @@ -33,6 +33,6 @@ def test_validate_score_wrapper_syntax(self): code = 'def score_function():\n\tprint("Scoring")\n\tresult = model.predict(data)\n\treturn result\n' #send through validate score_wrapper_syntax result = pzmm.ScoreWrapper.validate_score_wrapper_syntax(code) - #assert true + #assert self.assertTrue(result)