Skip to content

Commit 00c0b45

Browse files
committed
improved pymas scoring
removed winkerberos requirement moved exceptions
1 parent 6a4e48a commit 00c0b45

13 files changed

+749
-375
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Unreleased
44
**Changed**
55
- Exceptions moved from `sasctl.core` to `sasctl.exceptions`
66
- `SWATCASActionError` raised if ASTORE cannot be saved during model registration.
7+
- Improved handling of MAS calls made via `define_steps()`
78

89

910
v1.0.0 (2019-07-24)

doc/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ Installation
4545

4646
For basic functionality::
4747

48-
pip install git+https://gitlab.sas.com/xeno/python-sasctl
48+
pip install sasctl
4949

5050
For full functionality::
5151

52-
pip install git+https://gitlab.sas.com/xeno/python-sasctl#egg=sasctl[all]
52+
pip install sasctl[all]
5353

5454
Quickstart
5555
----------

examples/sklearn_model.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
module = publish_model(model_name, 'maslocal')
3838

3939
# Select the first row of training data
40-
x = {k.lower(): v for k, v in X.iloc[0, :].items()}
40+
x = X.iloc[0, :]
4141

4242
# Call the published module and score the record
4343
result = module.score(**x)
44+
print(result)

src/sasctl/_services/microanalytic_store.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,23 +129,63 @@ def create_module(self, name=None, description=None, source=None,
129129
return r
130130

131131
def define_steps(self, module):
132+
"""Defines python methods on a module that automatically call the
133+
corresponding MAS steps.
134+
135+
Parameters
136+
----------
137+
module : str or dict
138+
Name, id, or dictionary representation of a module
139+
140+
Returns
141+
-------
142+
module
143+
144+
"""
132145
import types
133146

134147
module = self.get_module(module)
135148

149+
# Define a method for each step of the module
136150
for id in module.get('stepIds', []):
137151
step = self.get_module_step(module, id)
138152

139-
name = '_{}_{}'.format(module.name, step.id)
153+
# Method should have an argument for each parameter of the step
140154
arguments = [k['name'] for k in step.inputs]
141155
arg_types = [k['type'] for k in step.inputs]
156+
157+
# Format call to execute_module_step()
142158
call_params = ['{}={}'.format(i, i) for i in arguments]
159+
160+
# Set type hints for the function
143161
type_string = ' # type: ({})'.format(', '.join(arg_types))
144162

145-
code = ('def {}({}):'.format(name, ', '.join(arguments)),
163+
# Method signature
164+
signature = 'def _%s_%s(%s, **kwargs):' % (module.name,
165+
step.id,
166+
', '.join(a for a
167+
in arguments))
168+
# MAS always lower-cases variable names
169+
# Since the original Python variables may have a different case,
170+
# allow kwargs to be used to input alternative caps
171+
arg_checks = ['for k in kwargs.keys():']
172+
for arg in arguments:
173+
arg_checks.append(" if k.lower() == '%s':" % arg.lower())
174+
arg_checks.append(" %s = kwargs[k]" % arg)
175+
arg_checks.append(" continue")
176+
177+
# Full method source code
178+
# Drops 'rc' and 'msg' from return values
179+
code = (signature,
146180
type_string,
147-
' """docstring"""',
148-
' return execute_module_step(module, step, return_dict=False, {})'.format(', '.join(call_params))
181+
' """Execute step %s of module %s."""' % (step, module),
182+
'\n'.join([' %s' % a for a in arg_checks]),
183+
' r = execute_module_step(module, step, {})'.format(', '.join(call_params)),
184+
' r.pop("rc", None)',
185+
' r.pop("msg", None)',
186+
' if len(r) == 1:',
187+
' return r.popitem()[1]',
188+
' return tuple(v for v in r.values())'
149189
)
150190

151191
code = '\n'.join(code)
@@ -156,7 +196,9 @@ def define_steps(self, module):
156196
'module': module,
157197
'step': step})
158198

159-
func = types.FunctionType(compiled.co_consts[0], env)
199+
func = types.FunctionType(compiled.co_consts[0],
200+
env,
201+
argdefs=tuple(None for x in arguments))
160202

161203
setattr(module, step.id, func)
162204

src/sasctl/core.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
except ImportError:
3434
kerberos = None
3535

36-
from sasctl.utils.cli import sasctl_command
36+
from .utils.cli import sasctl_command
37+
from . import exceptions
3738

3839
logger = logging.getLogger(__name__)
3940

@@ -491,7 +492,7 @@ def _get_token_with_password(self):
491492
auth=('sas.ec', ''))
492493

493494
if r.status_code == 401:
494-
raise AuthenticationError("Authentication failed for user '%s'." % username)
495+
raise exceptions.AuthenticationError(username)
495496
else:
496497
r.raise_for_status()
497498

@@ -902,6 +903,3 @@ class SasctlError(Exception):
902903

903904
class TimeoutError(SasctlError):
904905
pass
905-
906-
class AuthenticationError(SasctlError):
907-
pass

src/sasctl/exceptions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python
2+
# encoding: utf-8
3+
#
4+
# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
8+
class AuthenticationError(ValueError):
9+
"""A user could not be authenticated."""
10+
11+
def __init__(self, username, *args, **kwargs):
12+
msg = "Authentication failed for user '%s'." % username
13+
super(AuthenticationError, self).__init__(msg, *args, **kwargs)
14+
15+

src/sasctl/tasks.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,10 @@ def publish_model(model, destination, code=None, max_retries=60, **kwargs):
220220
# A successfully submitted request doesn't mean a successfully published model.
221221
# Response for publish request includes link to check publish log
222222
job = mr._monitor_job(publish_req)
223-
# log = mr._monitor_job(request_link(publish_req, 'publishingLog'),
224-
# 'self',
225-
# lambda x: x.get('log', '').upper().startswith('SUCCESS==='))
223+
224+
if job.state.lower() == 'failed':
225+
pass
226+
226227
log = request_link(job, 'publishingLog')
227228
msg = log.get('log').lstrip('SUCCESS===')
228229

test_requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ pandas
77
swat
88
sphinx == 1.8
99
tox
10-
kerberos ; platform_system != "Windows"
11-
winkerberos ; platform_system == "Windows"
10+
kerberos ; platform_system != "Windows"

tests/cassettes/tests.integration.test_tasks.TestModels.test_publish_sklearn.json

Lines changed: 571 additions & 121 deletions
Large diffs are not rendered by default.

tests/cassettes/tests.integration.test_tasks.TestModels.test_register_sklearn.json

Lines changed: 80 additions & 199 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)