Skip to content

Commit 7b4a54c

Browse files
committed
merged changes from v1.1.2
2 parents fcf58e1 + 154b8ca commit 7b4a54c

13 files changed

+1678
-2349
lines changed

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ Unreleased
77

88
**Bugfixes**
99
- Fixed an issue where `model_repository` did not find models by name once pagination limits were reached.
10-
10+
11+
v1.1.2 (2019-8-12)
12+
-----------------
13+
**Improvements**
14+
- CAS model table automatically reloaded on `publish_model` task.
15+
16+
**Bugfixes**
17+
- Fixed DS2 score code for CAS that was generated when registering a Python model.
18+
- `PyMAS.score_code(dest='ESP')` corrected to `dest='EP'`
19+
- Fixed an issue where long user-defined properties prevented model registration.
20+
1121

1222
v1.1.1 (2019-8-6)
1323
-----------------

src/sasctl/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
__version__ = '1.2.0'
88
__author__ = 'SAS'
9-
__credits__ = ['Yi Jian Ching, Lucas De Paula, Peter Tobac, Chris Toth, Jon '
10-
'Walker']
9+
__credits__ = ['Yi Jian Ching, Lucas De Paula, James Kochuba, Peter Tobac, '
10+
'Chris Toth, Jon Walker']
1111
__license__ = 'Apache 2.0'
1212
__copyright__ = 'Copyright © 2019, SAS Institute Inc., ' \
1313
'Cary, NC, USA. All Rights Reserved.'

src/sasctl/_services/model_management.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,30 @@ class ModelManagement(Service):
2020
'performance tasks')
2121

2222
# TODO: set ds2MultiType
23-
def publish_model(self, model, destination, name=None, force=False):
23+
def publish_model(self,
24+
model,
25+
destination,
26+
name=None,
27+
force=False,
28+
reload_model_table=False):
29+
"""
30+
31+
Parameters
32+
----------
33+
model
34+
destination
35+
name
36+
force : bool, optional
37+
Whether to overwrite the model if it already exists in the
38+
publishing `destination`.
39+
reload_model_table : bool, optional
40+
Whether the model table in CAS should be reloaded. Defaults to
41+
False.
42+
43+
Returns
44+
-------
45+
46+
"""
2447
from .model_repository import ModelRepository
2548
from .model_publish import ModelPublish
2649

@@ -37,7 +60,8 @@ def publish_model(self, model, destination, name=None, force=False):
3760

3861
# TODO: Verify allowed formats by destination type.
3962
# As of 19w04 MAS throws HTTP 500 if name is in invalid format.
40-
model_name = name or '{}_{}'.format(model_obj['name'].replace(' ', ''), model_obj['id']).replace('-', '')
63+
model_name = name or '{}_{}'.format(model_obj['name'].replace(' ', ''),
64+
model_obj['id']).replace('-', '')
4165

4266
request = {
4367
"name": model_obj.get('name'),
@@ -56,8 +80,12 @@ def publish_model(self, model, destination, name=None, force=False):
5680
# Publishes a model that has already been registered in the model
5781
# repository.
5882
# Unlike model_publish service, does not require Code to be specified.
59-
r = self.post('/publish', json=request, params=dict(force=force),
60-
headers={'Content-Type': 'application/vnd.sas.models.publishing.request.asynchronous+json'})
83+
r = self.post('/publish',
84+
json=request,
85+
params=dict(force=force,
86+
reloadModelTable=reload_model_table),
87+
headers={'Content-Type':
88+
'application/vnd.sas.models.publishing.request.asynchronous+json'})
6189
return r
6290

6391
def create_performance_definition(self,

src/sasctl/tasks.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,43 @@
2525

2626

2727
def _sklearn_to_dict(model):
28+
# As of Viya 3.4 model registration fails if character fields are longer
29+
# than 1024 characters
30+
DESC_MAXLEN = 1024
31+
32+
# As of Viya 3.4 model registration fails if user-defined properties are
33+
# longer than 512 characters.
34+
PROP_MAXLEN = 512
35+
2836
# Convert Scikit-learn values to built-in Model Manager values
2937
mappings = {'LogisticRegression': 'Logistic regression',
3038
'LinearRegression': 'Linear regression',
3139
'SVC': 'Support vector machine',
3240
'GradientBoostingClassifier': 'Gradient boosting',
41+
'XGBClassifier': 'Gradient boosting',
42+
'XGBRegressor': 'Gradient boosting',
3343
'RandomForestClassifier': 'Forest',
3444
'DecisionTreeClassifier': 'Decision tree',
3545
'DecisionTreeRegressor': 'Decision tree',
3646
'classifier': 'Classification',
3747
'regressor': 'Prediction'}
3848

49+
if hasattr(model, '_final_estimator'):
50+
estimator = type(model._final_estimator)
51+
else:
52+
estimator = type(model)
53+
3954
# Can tell if multi-class .multi_class
4055
result = dict(
41-
description=str(model),
42-
algorithm=mappings.get(type(model).__name__, type(model).__name__),
56+
description=str(model)[:DESC_MAXLEN],
57+
algorithm=mappings.get(estimator.__name__, estimator.__name__),
4358
scoreCodeType='ds2MultiType',
4459
trainCodeType='Python',
4560
function=mappings.get(model._estimator_type, model._estimator_type),
4661
tool='Python %s.%s'
4762
% (sys.version_info.major, sys.version_info.minor),
48-
properties=[{'name': k, 'value': v}
63+
properties=[{'name': str(k)[:PROP_MAXLEN],
64+
'value': str(v)[:PROP_MAXLEN]}
4965
for k, v in model.get_params().items()]
5066
)
5167

@@ -198,9 +214,9 @@ def get_version(x):
198214
files.append({'name': 'dmcas_packagescorecode.sas',
199215
'file': mas_module.score_code(),
200216
'role': 'Score Code'})
201-
files.append({'name': 'dmcas_espscorecode.sas',
202-
'file': mas_module.score_code(dest='ESP'),
203-
'role': 'Score Code'})
217+
files.append({'name': 'dmcas_epscorecode.sas',
218+
'file': mas_module.score_code(dest='CAS'),
219+
'role': 'score'})
204220

205221
model['inputVariables'] = [var.as_model_metadata()
206222
for var in mas_module.variables
@@ -245,7 +261,10 @@ def get_version(x):
245261
return model
246262

247263

248-
def publish_model(model, destination, code=None, max_retries=60,
264+
def publish_model(model,
265+
destination,
266+
code=None,
267+
max_retries=60,
249268
replace=False, **kwargs):
250269
"""Publish a model to a configured publishing destination.
251270
@@ -290,7 +309,15 @@ def publish_model(model, destination, code=None, max_retries=60,
290309
def submit_request():
291310
# Submit a publishing request
292311
if code is None:
293-
publish_req = mm.publish_model(model, destination, **kwargs)
312+
dest_obj = mp.get_destination(destination)
313+
314+
if dest_obj and dest_obj.destinationType == "cas":
315+
publish_req = mm.publish_model(model, destination,
316+
force=replace,
317+
reload_model_table=True)
318+
else:
319+
publish_req = mm.publish_model(model, destination,
320+
force=replace)
294321
else:
295322
publish_req = mp.publish_model(model, destination,
296323
code=code, **kwargs)
@@ -304,6 +331,11 @@ def submit_request():
304331
# Submit and wait for status
305332
job = submit_request()
306333

334+
# If model was successfully published and it isn't a MAS module, we're done
335+
if job.state.lower() == 'completed' \
336+
and job.destination.destinationType != 'microAnalyticService':
337+
return request_link(job,'self')
338+
307339
# If MAS publish failed and replace=True, attempt to delete the module
308340
# and republish
309341
if job.state.lower() == 'failed' and replace and \

src/sasctl/utils/pymas/core.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def score_code(self, input_table=None, output_table=None, columns=None, dest='MA
345345
The name of the table where execution results will be written
346346
columns : list of str
347347
Names of the columns from `table` that will be passed to `func`
348-
dest : str {'MAS', 'ESP', 'CAS'}
348+
dest : str {'MAS', 'EP', 'CAS'}
349349
350350
Returns
351351
-------
@@ -365,28 +365,24 @@ def score_code(self, input_table=None, output_table=None, columns=None, dest='MA
365365
# Get package code
366366
code = tuple(self.package.code().split('\n'))
367367

368-
if dest == 'ESP':
368+
if dest == 'EP':
369369
code = ('data sasep.out;', ) + code + (' method run();',
370370
' set SASEP.IN;',
371371
' end;',
372372
' method term();',
373373
' end;',
374374
'enddata;')
375375
elif dest == 'CAS':
376-
if input_table is None:
377-
raise ValueError('An input table must be specified when executing the code in CAS.')
378-
379-
output_table = output_table or input_table + '_pymas'
380376
thread = DS2Thread(self.variables, input_table, column_names=columns, return_message=self.return_message,
381377
package=self.package)
382378

383379
code += (str(thread),
384-
' data {} (overwrite=yes);'.format(output_table),
385-
' dcl thread {} t;'.format(thread.name),
386-
' method run();',
387-
' set from t;',
388-
' output {} ;'.format(output_table),
389-
' end;',
390-
' enddata;')
391-
392-
return '\n'.join(code)
380+
'data SASEP.out;',
381+
' dcl thread {} t;'.format(thread.name),
382+
' method run();',
383+
' set from t;',
384+
' output;',
385+
' end;',
386+
'enddata;')
387+
388+
return '\n'.join(code)

src/sasctl/utils/pymas/ds2.py

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,6 @@ def name(self):
252252
return 'pyMasThread'
253253

254254
def __str__(self):
255-
extras = ['"rc"'] if self.return_code else []
256-
extras += ['"msg"'] if self.return_message else []
257-
258255
array_input = any([v.is_array for v in self.variables if not v.out])
259256

260257
# If passing column data into Python as an array, need extra assignment statements to set values
@@ -272,25 +269,18 @@ def __str__(self):
272269
declarations = '\n'.join([v.as_declaration() for v in self.variables if
273270
v.out or v.is_array])
274271

275-
keep_vars = [v.name for v in self.variables] + extras
272+
keep_vars = [v.name for v in self.variables]
276273

277-
code = (" thread {} / inline;".format(self.name),
278-
" dcl package {} decisionPackage();".format(
279-
self.package.name),
274+
code = ("thread {} / inline;".format(self.name),
275+
" dcl package {} pythonPackage();".format(self.package.name),
280276
declarations,
281-
' dcl double "rc";',
282-
" dcl char(4096) character set utf8 msg;",
283-
# " keep {};".format(' '.join(keep_vars)),
284-
" method run();",
285-
" dcl integer localRC;",
286-
" set {} ( );".format(self.table),
277+
" method run();",
278+
" set SASEP.in;",
287279
var_assignments,
288-
" decisionPackage.{}({});".format(
289-
self.package.methods[0].name,
290-
','.join(keep_vars)),
291-
" output;",
292-
" end;",
293-
" endthread;")
280+
" pythonPackage.{}({});".format(self.package.methods[0].name,','.join(keep_vars)),
281+
" output;",
282+
" end;",
283+
"endthread;")
294284

295285
code = '\n'.join(code)
296286

0 commit comments

Comments
 (0)