Skip to content

Commit 2d90067

Browse files
author
Jon Walker
authored
Merge pull request #24 from sassoftware/bugfix
Bugfix
2 parents 003d375 + e086176 commit 2d90067

13 files changed

+1812
-1661
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11

22
Unreleased
33
----------
4-
5-
-
6-
4+
**Bugfixes**
5+
- Fixed DS2 score code for CAS that was generated when registering a Python model.
6+
- `PyMAS.score_code(dest='ESP')` corrected to `dest='EP'`
7+
- Fixed an issue where long user-defined properties prevented model registration.
8+
79

810
v1.1.1 (2019-8-6)
911
-----------------

src/sasctl/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
55
# SPDX-License-Identifier: Apache-2.0
66

7-
__version__ = '1.1.1'
7+
__version__ = '1.1.2'
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

@@ -156,9 +172,9 @@ def register_model(model, name, project, repository=None, input=None,
156172
files.append({'name': 'dmcas_packagescorecode.sas',
157173
'file': mas_module.score_code(),
158174
'role': 'Score Code'})
159-
files.append({'name': 'dmcas_espscorecode.sas',
160-
'file': mas_module.score_code(dest='ESP'),
161-
'role': 'Score Code'})
175+
files.append({'name': 'dmcas_epscorecode.sas',
176+
'file': mas_module.score_code(dest='CAS'),
177+
'role': 'score'})
162178

163179
model['inputVariables'] = [var.as_model_metadata()
164180
for var in mas_module.variables
@@ -203,7 +219,10 @@ def register_model(model, name, project, repository=None, input=None,
203219
return model
204220

205221

206-
def publish_model(model, destination, code=None, max_retries=60,
222+
def publish_model(model,
223+
destination,
224+
code=None,
225+
max_retries=60,
207226
replace=False, **kwargs):
208227
"""Publish a model to a configured publishing destination.
209228
@@ -248,7 +267,15 @@ def publish_model(model, destination, code=None, max_retries=60,
248267
def submit_request():
249268
# Submit a publishing request
250269
if code is None:
251-
publish_req = mm.publish_model(model, destination, **kwargs)
270+
dest_obj = mp.get_destination(destination)
271+
272+
if dest_obj and dest_obj.destinationType == "cas":
273+
publish_req = mm.publish_model(model, destination,
274+
force=replace,
275+
reload_model_table=True)
276+
else:
277+
publish_req = mm.publish_model(model, destination,
278+
force=replace)
252279
else:
253280
publish_req = mp.publish_model(model, destination,
254281
code=code, **kwargs)
@@ -262,6 +289,11 @@ def submit_request():
262289
# Submit and wait for status
263290
job = submit_request()
264291

292+
# If model was successfully published and it isn't a MAS module, we're done
293+
if job.state.lower() == 'completed' \
294+
and job.destination.destinationType != 'microAnalyticService':
295+
return request_link(job,'self')
296+
265297
# If MAS publish failed and replace=True, attempt to delete the module
266298
# and republish
267299
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)