4
4
# Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
5
5
# SPDX-License-Identifier: Apache-2.0
6
6
7
- import json
8
- import os
9
- import pickle
10
- import random
11
7
import tempfile
12
8
import unittest
13
9
from pathlib import Path
14
10
from unittest .mock import DEFAULT , MagicMock , patch
15
11
16
- import numpy as np
17
12
import pandas as pd
18
13
import pytest
19
14
20
- import sasctl .pzmm as pzmm
21
15
from sasctl import current_session
22
16
from sasctl .core import RestObj , VersionInfo
23
- from sasctl .pzmm .write_score_code import ScoreCode as sc
24
17
from sasctl .pzmm .write_score_code import ScoreCode
25
18
26
19
@@ -38,7 +31,7 @@ def score_code_mocks():
38
31
_write_imports = MagicMock (),
39
32
_viya35_model_load = DEFAULT ,
40
33
_viya4_model_load = DEFAULT ,
41
- _check_valid_model_prefix = DEFAULT ,
34
+ sanitize_model_prefix = DEFAULT ,
42
35
_impute_missing_values = MagicMock (),
43
36
_predict_method = MagicMock (),
44
37
_predictions_to_metrics = MagicMock (),
@@ -54,6 +47,7 @@ def test_get_model_id():
54
47
- Model not found
55
48
- Model found
56
49
"""
50
+ sc = ScoreCode ()
57
51
with pytest .raises (ValueError ):
58
52
sc ._get_model_id (None )
59
53
@@ -74,6 +68,7 @@ def test_check_for_invalid_variable_names():
74
68
- Valid variables only
75
69
"""
76
70
var_list = ["bad_variable" , "good_variable" , "awful variable" ]
71
+ sc = ScoreCode ()
77
72
with pytest .raises (SyntaxError ):
78
73
sc ._check_for_invalid_variable_names (var_list )
79
74
try :
@@ -97,21 +92,21 @@ def test_write_imports():
97
92
current_session ("example.com" , "user" , "password" )
98
93
99
94
with patch ("sasctl.core.Session.version_info" ) as version :
95
+ sc = ScoreCode ()
100
96
version .return_value = VersionInfo (3 )
101
97
sc ._write_imports (pickle_type = "dill" )
102
98
assert "import settings" not in sc .score_code
103
99
assert "import dill" in sc .score_code
104
- sc .score_code = ""
105
100
101
+ sc = ScoreCode ()
106
102
version .return_value = VersionInfo (4 )
107
103
sc ._write_imports (mojo_model = True )
108
104
assert "import settings" in sc .score_code
109
105
assert "import h2o" in sc .score_code
110
- sc .score_code = ""
111
106
107
+ sc = ScoreCode ()
112
108
sc ._write_imports (binary_string = b"test binary string" )
113
109
assert "import codecs" in sc .score_code
114
- sc .score_code = ""
115
110
116
111
117
112
def test_viya35_model_load ():
@@ -121,20 +116,20 @@ def test_viya35_model_load():
121
116
- mojo model
122
117
- binary h2o model
123
118
"""
119
+ sc = ScoreCode ()
124
120
load_text = sc ._viya35_model_load ("1234" , "normal" )
125
121
assert "pickle.load(pickle_model)" in sc .score_code
126
122
assert "pickle.load(pickle_model)" in load_text
127
- sc .score_code = ""
128
123
124
+ sc = ScoreCode ()
129
125
mojo_text = sc ._viya35_model_load ("2345" , "mojo" , mojo_model = True )
130
126
assert "h2o.import_mojo" in sc .score_code
131
127
assert "h2o.import_mojo" in mojo_text
132
- sc .score_code = ""
133
128
129
+ sc = ScoreCode ()
134
130
binary_text = sc ._viya35_model_load ("3456" , "binary" , binary_h2o_model = True )
135
131
assert "h2o.load" in sc .score_code
136
132
assert "h2o.load" in binary_text
137
- sc .score_code = ""
138
133
139
134
140
135
def test_viya4_model_load ():
@@ -145,21 +140,22 @@ def test_viya4_model_load():
145
140
- binary h2o model
146
141
- Tensorflow keras model
147
142
"""
143
+ sc = ScoreCode ()
148
144
load_text = sc ._viya4_model_load ("normal" )
149
145
assert "pickle.load(pickle_model)" in sc .score_code
150
146
assert "pickle.load(pickle_model)" in load_text
151
- sc .score_code = ""
152
147
148
+ sc = ScoreCode ()
153
149
mojo_text = sc ._viya4_model_load ("mojo" , mojo_model = True )
154
150
assert "h2o.import_mojo" in sc .score_code
155
151
assert "h2o.import_mojo" in mojo_text
156
- sc .score_code = ""
157
152
153
+ sc = ScoreCode ()
158
154
binary_text = sc ._viya4_model_load ("binary" , binary_h2o_model = True )
159
155
assert "h2o.load" in sc .score_code
160
156
assert "h2o.load" in binary_text
161
- sc .score_code = ""
162
157
158
+ sc = ScoreCode ()
163
159
keras_text = sc ._viya4_model_load ("tensorflow" , tf_keras_model = True )
164
160
assert "tf.keras.models.load_model" in sc .score_code
165
161
assert "tf.keras.models.load_model" in keras_text
@@ -174,20 +170,23 @@ def test_impute_missing_values():
174
170
test_df = pd .DataFrame (
175
171
data = [[0 , "a" , 1 ], [2 , "b" , 0 ]], columns = ["num" , "char" , "bin" ]
176
172
)
173
+ sc = ScoreCode ()
177
174
sc ._impute_missing_values (test_df , True )
178
- assert "'num': 1" in sc .score_code
175
+ assert "'num': 1" in sc .score_code or "'num': np.float64(1.0)" in sc . score_code
179
176
assert "'char': ''" in sc .score_code
180
- assert "'bin': 0" in sc .score_code
177
+ assert "'bin': 0" in sc .score_code or "'bin': np.int64(0)" in sc . score_code
181
178
179
+ sc = ScoreCode ()
182
180
sc ._impute_missing_values (test_df , [5 , "test" , 1 ])
183
- assert "'num': 5" in sc .score_code
181
+ assert "'num': 5" in sc .score_code or "'num': np.float64(5.0)" in sc . score_code
184
182
assert "'char': 'test'" in sc .score_code
185
- assert "'bin': 1" in sc .score_code
183
+ assert "'bin': 1" in sc .score_code or "'bin': np.int64(1)" in sc . score_code
186
184
185
+ sc = ScoreCode ()
187
186
sc ._impute_missing_values (test_df , {"a" : 5 , "b" : "test" , "c" : 1 })
188
- assert "'a': 5" in sc .score_code
187
+ assert "'a': 5" in sc .score_code or "'a': np.float64(5.0)" in sc . score_code
189
188
assert "'b': 'test'" in sc .score_code
190
- assert "'c': 1" in sc .score_code
189
+ assert "'c': 1" in sc .score_code or "'c': np.int64(1)" in sc . score_code
191
190
192
191
193
192
def test_predict_method ():
@@ -197,19 +196,19 @@ def test_predict_method():
197
196
- h2o model, based of dtype_list input
198
197
- statsmodels model
199
198
"""
199
+ sc = ScoreCode ()
200
200
var_list = ["first" , "second" , "third" ]
201
201
dtype_list = ["str" , "int" , "float" ]
202
202
sc ._predict_method (predict_proba , var_list )
203
203
assert '{"first": first, "second": second' in sc .score_code
204
- sc .score_code = ""
205
204
205
+ sc = ScoreCode ()
206
206
sc ._predict_method (predict_proba , var_list , dtype_list = dtype_list )
207
207
assert "column_types = " in sc .score_code
208
- sc .score_code = ""
209
208
209
+ sc = ScoreCode ()
210
210
sc ._predict_method (predict_proba , var_list , statsmodels_model = True )
211
211
assert '{"const": const, "first": first' in sc .score_code
212
- sc .score_code = ""
213
212
214
213
215
214
def test_determine_returns_type ():
@@ -220,6 +219,7 @@ def test_determine_returns_type():
220
219
- mixture of values/types
221
220
- unexpected types
222
221
"""
222
+ sc = ScoreCode ()
223
223
assert sc ._determine_returns_type (["TestReturn" , 1.2 ]) == [True , False ]
224
224
assert sc ._determine_returns_type ([int , float , str ]) == [False , False , True ]
225
225
assert sc ._determine_returns_type (["TestReturn" , int , str ]) == [True , False , True ]
@@ -232,6 +232,7 @@ def test_yield_score_metrics():
232
232
- Any order for classification/prediction values
233
233
- No target variable provided
234
234
"""
235
+ sc = ScoreCode ()
235
236
metrics = sc ._yield_score_metrics (
236
237
[False , True , False ], ["Math" , "English" ], "ClassyVar"
237
238
)
@@ -251,6 +252,7 @@ def test_determine_score_metrics():
251
252
- len(predict_returns == False) = [0, =len(tv), Any]
252
253
- target_variable = [None, Any]
253
254
"""
255
+ sc = ScoreCode ()
254
256
assert sc ._determine_score_metrics ([float ], "TestPredict" , None ) == [
255
257
f"I_TestPredict"
256
258
]
@@ -305,7 +307,7 @@ def test_determine_score_metrics():
305
307
306
308
class TestNoTargetsNoThresholds (unittest .TestCase ):
307
309
def setUp (self ):
308
- self .sc = ScoreCode
310
+ self .sc = ScoreCode ()
309
311
310
312
def tearDown (self ):
311
313
self .sc .score_code = ""
@@ -417,11 +419,9 @@ def test_multi_metric_h2o(self):
417
419
418
420
class TestBinaryTarget (unittest .TestCase ):
419
421
def setUp (self ):
420
- self .sc = ScoreCode
422
+ self .sc = ScoreCode ()
421
423
self .target_values = ["A" , "B" ]
422
424
423
- def tearDown (self ):
424
- self .sc .score_code = ""
425
425
426
426
def execute_snippet (self , * args ):
427
427
scope = {}
@@ -430,6 +430,8 @@ def execute_snippet(self, *args):
430
430
return test_snippet (* args )
431
431
432
432
def test_improper_arguments (self ):
433
+ sc = ScoreCode ()
434
+
433
435
with pytest .raises (ValueError ):
434
436
sc ._binary_target ([], [], ["A" , 1 , 2 , 3 ])
435
437
with pytest .raises (ValueError ):
@@ -830,17 +832,14 @@ def test_three_metrics_three_returns(self):
830
832
831
833
class TestNonbinaryTargets (unittest .TestCase ):
832
834
def setUp (self ):
833
- self .sc = ScoreCode
835
+ self .sc = ScoreCode ()
834
836
self .sc .score_code = (
835
837
"import pandas as pd\n "
836
838
"import numpy as np\n "
837
839
"def test_snippet(input_array, prediction):\n "
838
840
)
839
841
self .target_values = ["A" , "B" , "C" ]
840
842
841
- def tearDown (self ):
842
- self .sc .score_code = ""
843
-
844
843
def execute_snippet (self , * args ):
845
844
scope = {}
846
845
exec (self .sc .score_code , scope )
@@ -1090,6 +1089,7 @@ def test_predictions_to_metrics():
1090
1089
- raise error for binary model w/ nonbinary targets
1091
1090
- raise error for no target_values, but thresholds provided
1092
1091
"""
1092
+ sc = ScoreCode ()
1093
1093
with patch ("sasctl.pzmm.ScoreCode._no_targets_no_thresholds" ) as func :
1094
1094
metrics = ["Classification" ]
1095
1095
returns = [1 ]
@@ -1122,6 +1122,7 @@ def test_input_var_lists():
1122
1122
- default
1123
1123
- mlflow
1124
1124
"""
1125
+ sc = ScoreCode ()
1125
1126
data = pd .DataFrame (data = [[1 , "A" ], [5 , "B" ]], columns = ["First" , "Second" ])
1126
1127
var_list , dtypes_list = sc ._input_var_lists (data )
1127
1128
assert var_list == ["First" , "Second" ]
@@ -1146,6 +1147,8 @@ def test_check_viya_version(mock_version, mock_get_model):
1146
1147
current_session (None )
1147
1148
mock_version .return_value = None
1148
1149
model = {"name" : "Test" , "id" : "abc123" }
1150
+ sc = ScoreCode ()
1151
+
1149
1152
with pytest .warns ():
1150
1153
assert sc ._check_viya_version (model ) is None
1151
1154
@@ -1170,6 +1173,7 @@ def test_check_valid_model_prefix():
1170
1173
- check model_prefix validity
1171
1174
- raise warning and replace if invalid
1172
1175
"""
1176
+ sc = ScoreCode ()
1173
1177
assert sc .sanitize_model_prefix ("TestPrefix" ) == "TestPrefix"
1174
1178
assert sc .sanitize_model_prefix ("Test Prefix" ) == "Test_Prefix"
1175
1179
@@ -1194,9 +1198,10 @@ def test_write_score_code(score_code_mocks):
1194
1198
score_code_mocks ["_viya35_model_load" ].return_value = "3.5"
1195
1199
score_code_mocks ["_viya4_model_load" ].return_value = "4"
1196
1200
score_code_mocks ["_viya35_score_code_import" ].return_value = ("MAS" , "CAS" )
1197
- score_code_mocks ["_check_valid_model_prefix " ].return_value = "TestModel"
1201
+ score_code_mocks ["sanitize_model_prefix " ].return_value = "TestModel"
1198
1202
1199
1203
# No binary string or model file provided
1204
+ sc = ScoreCode ()
1200
1205
with pytest .raises (ValueError ):
1201
1206
sc .write_score_code (
1202
1207
"TestModel" ,
@@ -1205,6 +1210,7 @@ def test_write_score_code(score_code_mocks):
1205
1210
)
1206
1211
1207
1212
# Binary string and model file provided
1213
+ sc = ScoreCode ()
1208
1214
with pytest .raises (ValueError ):
1209
1215
sc .write_score_code (
1210
1216
"TestModel" ,
@@ -1214,6 +1220,7 @@ def test_write_score_code(score_code_mocks):
1214
1220
binary_string = b"Binary model string." ,
1215
1221
)
1216
1222
1223
+ sc = ScoreCode ()
1217
1224
sc .write_score_code (
1218
1225
"TestModel" ,
1219
1226
pd .DataFrame (data = [["A" , 1 ], ["B" , 2 ]], columns = ["First" , "Second" ]),
@@ -1222,6 +1229,7 @@ def test_write_score_code(score_code_mocks):
1222
1229
)
1223
1230
score_code_mocks ["_viya4_model_load" ].assert_called_once ()
1224
1231
1232
+ sc = ScoreCode ()
1225
1233
score_code_mocks ["_check_viya_version" ].return_value = "abc123"
1226
1234
sc .write_score_code (
1227
1235
"TestModel" ,
@@ -1231,6 +1239,7 @@ def test_write_score_code(score_code_mocks):
1231
1239
)
1232
1240
score_code_mocks ["_viya35_model_load" ].assert_called_once ()
1233
1241
1242
+ sc = ScoreCode ()
1234
1243
output_dict = sc .write_score_code (
1235
1244
"TestModel" ,
1236
1245
pd .DataFrame (data = [["A" , 1 ], ["B" , 2 ]], columns = ["First" , "Second" ]),
@@ -1241,6 +1250,7 @@ def test_write_score_code(score_code_mocks):
1241
1250
assert "dmcas_packagescorecode.sas" in output_dict
1242
1251
assert "dmcas_epscorecode.sas" in output_dict
1243
1252
1253
+ sc = ScoreCode ()
1244
1254
tmp_dir = tempfile .TemporaryDirectory ()
1245
1255
sc .write_score_code (
1246
1256
"TestModel" ,
0 commit comments