def test_new_session(missing_packages): HOST = 'example.com' USERNAME = '******' PASSWORD = '******' # Ensure no dependency on swat required with missing_packages('swat'): with mock.patch('sasctl.core.Session.get_token'): s = Session(HOST, USERNAME, PASSWORD) assert USERNAME == s.user assert HOST == s.settings['domain'] assert 'https' == s.settings['protocol'] assert USERNAME == s.settings['username'] assert PASSWORD == s.settings['password'] # Tests don't reset global variables (_session) so explicitly cleanup current_session(None)
def main(args=None): """Main entry point when executed as a command line utility.""" from sasctl import Session, current_session # Find all services and associated commands services = _find_services() parser = _build_parser(services) args = parser.parse_args(args) if args.verbose is None or args.verbose == 0: lvl = logging.WARNING elif args.verbose == 1: lvl = logging.INFO else: lvl = logging.DEBUG handler = logging.StreamHandler() handler.setLevel(lvl) logging.getLogger('sasctl.core').addHandler(handler) logging.getLogger('sasctl.core').setLevel(lvl) warnings.simplefilter('ignore') func = services[args.service][args.command] kwargs = vars(args).copy() # Remove args that shouldn't be passed to the underlying command for k in ['command', 'service', 'insecure', 'verbose', 'format']: kwargs.pop(k, None) username = os.environ.get('SASCTL_USER_NAME') password = os.environ.get('SASCTL_PASSWORD') server = os.environ.get('SASCTL_SERVER_NAME') if server is None: parser.error( "Hostname must be specified in the 'SASCTL_SERVER_NAME' environment variable." ) verify_ssl = not args.insecure try: # current_session() should never be set when executing from the # command line but it allows us to provide a pre-created session # during testing with current_session() or Session( server, username, password, verify_ssl=verify_ssl): result = func(**kwargs) if isinstance(result, list): pprint([str(x) for x in result]) elif isinstance(result, dict) and args.format == 'json': print(json.dumps(result, indent=2)) else: pprint(result) except RuntimeError as e: parser.error(e)
def test_get_model_by_name(): """If multiple models exist with the same name, a warning should be raised. From https://github.com/sassoftware/python-sasctl/issues/92 """ MODEL_NAME = 'Test Model' # Create a dummy session with mock.patch('sasctl.core.requests.Session.request'): current_session('example.com', 'user', 'password') mock_responses = [ # First response is for list_items/list_models [{ 'id': 12345, 'name': MODEL_NAME }, { 'id': 67890, 'name': MODEL_NAME }], # Second response is mock GET for model details { 'id': 12345, 'name': MODEL_NAME }, ] with mock.patch('sasctl._services.model_repository.ModelRepository.request' ) as request: request.side_effect = mock_responses with pytest.warns(Warning): result = mr.get_model(MODEL_NAME) assert result['id'] == 12345 assert result['name'] == MODEL_NAME
def session(request, credentials): import warnings from six.moves import mock from betamax.fixtures.pytest import _casette_name from sasctl import current_session if SKIP_REPLAY: yield Session(**credentials) current_session(None) return # Ignore FutureWarnings from betamax to avoid cluttering test results with warnings.catch_warnings(): warnings.simplefilter('ignore') cassette_name = _casette_name(request, parametrized=False) # Need to instantiate a Session before starting Betamax recording, # but sasctl.Session makes requests (which should be recorded) during # __init__(). Mock __init__ to prevent from running and then manually # execute requests.Session.__init__() so Betamax can use the session. with mock.patch('sasctl.core.Session.__init__', return_value=None): recorded_session = Session() super(Session, recorded_session).__init__() with betamax.Betamax(recorded_session).use_cassette( cassette_name, serialize_with='prettyjson') as recorder: recorder.start() # Manually run the sasctl.Session constructor. Mock out calls to # underlying requests.Session.__init__ to prevent hooks placed by # Betamax from being reset. with mock.patch('sasctl.core.requests.Session.__init__'): recorded_session.__init__(**credentials) current_session(recorded_session) yield recorded_session recorder.stop() current_session(None)
def test_current_session(): assert current_session() is None # Initial session should automatically become the default with mock.patch('sasctl.core.Session.get_token'): s = Session('example.com', 'user', 'password') assert current_session() == s # Subsequent sessions should not overwrite the default with mock.patch('sasctl.core.Session.get_token'): s2 = Session('example.com', 'user2', 'password') assert current_session() != s2 assert current_session() == s # Explicitly set new current session with mock.patch('sasctl.core.Session.get_token'): s3 = current_session('example.com', 'user3', 'password') assert current_session() == s3 # Explicitly change current session with mock.patch('sasctl.core.Session.get_token'): s4 = Session('example.com', 'user4', 'password') current_session(s4) assert 'user4' == current_session().user with mock.patch('sasctl.core.Session.get_token'): with Session('example.com', 'user5', 'password') as s5: with Session('example.com', 'user6', 'password') as s6: assert current_session() == s6 assert current_session() != s5 assert current_session().user == 'user6' assert current_session().user == 'user5' assert current_session().user == 'user4'
def test_create_model(): MODEL_NAME = 'Test Model' PROJECT_NAME = 'Test Project' PROJECT_ID = '12345' USER = '******' with mock.patch('sasctl.core.requests.Session.request'): current_session('example.com', USER, 'password') TARGET = { 'name': MODEL_NAME, 'projectId': PROJECT_ID, 'modeler': USER, 'description': 'model description', 'function': 'Classification', 'algorithm': 'Dummy Algorithm', 'tool': 'pytest', 'champion': True, 'role': 'champion', 'immutable': True, 'retrainable': True, 'scoreCodeType': None, 'targetVariable': None, 'trainTable': None, 'classificationEventProbabilityVariableName': None, 'classificationTargetEventValue': None, 'location': None, 'properties': [ { 'name': 'custom1', 'value': 123 }, { 'name': 'custom2', 'value': 'somevalue' }, ], 'inputVariables': [], 'outputVariables': [], 'version': '2', } # Passed params should be set correctly target = copy.deepcopy(TARGET) with mock.patch( 'sasctl._services.model_repository.ModelRepository.get_project' ) as get_project: with mock.patch('sasctl._services.model_repository.ModelRepository' '.get_model') as get_model: with mock.patch( 'sasctl._services.model_repository.ModelRepository.post' ) as post: get_project.return_value = {'id': PROJECT_ID} get_model.return_value = None _ = mr.create_model( MODEL_NAME, PROJECT_NAME, description=target['description'], function=target['function'], algorithm=target['algorithm'], tool=target['tool'], is_champion=True, is_immutable=True, is_retrainable=True, properties=dict(custom1=123, custom2='somevalue'), ) assert post.call_count == 1 url, data = post.call_args # dict isn't guaranteed to preserve order # so k/v pairs of properties=dict() may be # returned in a different order assert sorted(target['properties'], key=lambda d: d['name']) == sorted( data['json']['properties'], key=lambda d: d['name']) target.pop('properties') data['json'].pop('properties') assert target == data['json'] # Model dict w/ parameters already specified should be allowed # Explicit overrides should be respected. target = copy.deepcopy(TARGET) with mock.patch( 'sasctl._services.model_repository.ModelRepository.get_project' ) as get_project: with mock.patch('sasctl._services.model_repository.ModelRepository' '.get_model') as get_model: with mock.patch( 'sasctl._services.model_repository.ModelRepository.post' ) as post: get_project.return_value = {'id': PROJECT_ID} get_model.return_value = None _ = mr.create_model(copy.deepcopy(target), PROJECT_NAME, description='Updated Model') target['description'] = 'Updated Model' assert post.call_count == 1 url, data = post.call_args # dicts don't preserve order so property order may not match assert target['properties'] == data['json']['properties'] target.pop('properties') data['json'].pop('properties') assert target == data['json']
def test_create_performance_definition(): import copy from sasctl import current_session PROJECT = RestObj({'name': 'Test Project', 'id': '98765'}) MODEL = RestObj({ 'name': 'Test Model', 'id': '12345', 'projectId': PROJECT['id'] }) USER = '******' with mock.patch('sasctl.core.requests.Session.request'): current_session('example.com', USER, 'password') with mock.patch('sasctl._services.model_repository.ModelRepository' '.get_model') as get_model: with mock.patch('sasctl._services.model_repository.ModelRepository' '.get_project') as get_project: with mock.patch('sasctl._services.model_management.ModelManagement' '.post') as post: get_model.return_value = MODEL with pytest.raises(ValueError): # Project missing all required properties get_project.return_value = copy.deepcopy(PROJECT) _ = mm.create_performance_definition( 'model', 'TestLibrary', 'TestData') with pytest.raises(ValueError): # Project missing some required properties get_project.return_value = copy.deepcopy(PROJECT) get_project.return_value['targetVariable'] = 'target' _ = mm.create_performance_definition( 'model', 'TestLibrary', 'TestData') with pytest.raises(ValueError): # Project missing some required properties get_project.return_value = copy.deepcopy(PROJECT) get_project.return_value['targetLevel'] = 'interval' _ = mm.create_performance_definition( 'model', 'TestLibrary', 'TestData') with pytest.raises(ValueError): # Project missing some required properties get_project.return_value = copy.deepcopy(PROJECT) get_project.return_value[ 'predictionVariable'] = 'predicted' _ = mm.create_performance_definition( 'model', 'TestLibrary', 'TestData') get_project.return_value = copy.deepcopy(PROJECT) get_project.return_value['targetVariable'] = 'target' get_project.return_value['targetLevel'] = 'interval' get_project.return_value['predictionVariable'] = 'predicted' _ = mm.create_performance_definition('model', 'TestLibrary', 'TestData', max_bins=3, monitor_challenger=True, monitor_champion=True) assert post.call_count == 1 url, data = post.call_args assert PROJECT['id'] == data['json']['projectId'] assert MODEL['id'] in data['json']['modelIds'] assert 'TestLibrary' == data['json']['dataLibrary'] assert 'TestData' == data['json']['dataPrefix'] assert 'cas-shared-default' == data['json']['casServerId'] assert data['json']['name'] is not None assert data['json']['description'] is not None assert data['json']['maxBins'] == 3 assert data['json']['championMonitored'] == True assert data['json']['challengerMonitored'] == True def test_table_prefix_format(): with pytest.raises(ValueError): # Underscores should not be allowed _ = mm.create_performance_definition('model', 'TestLibrary', 'invalid_name')
#!/usr/bin/env python # encoding: utf-8 # # Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import pytest from six.moves import mock from sasctl.services import microanalytic_score as mas from sasctl import current_session from sasctl.core import RestObj with mock.patch('sasctl.core.requests.Session.request'): current_session('example.com', 'username', 'password') def test_create_python_module(): with mock.patch('sasctl.services.microanalytic_score.post') as post: with pytest.raises(ValueError): mas.create_module() # Source code is required with mock.patch('sasctl.services.microanalytic_score.post') as post: source = '\n'.join( ("def testMethod(var1, var2):", " 'Output: out1, out2'", " out1 = var1 + 5", " out2 = var2.upper()", " return out1, out2")) mas.create_module(source=source) assert post.call_count == 1