DS2 modules, running
in SAS Micro Analytic Service, can publish and execute Python modules.
Note that Python 2.7
or Python 3.4 must be available for SAS Micro Analytic Service to
load. If both are available, SAS Micro Analytic Service loads Python
3.4. See
Python Support in SAS Micro Analytic Service, for information about installing Python and configuring
the environment variables necessary to allow Python to run embedded
in SAS Micro Analytic Service. As is the case when calling any package
from DS2, it is recommended that you always check return codes where
available, and return any error codes using an output argument from
your DS2 method.
To call Python from
DS2, use the DS2 package called pymas. Each pymas package instance
represents exactly one Python module revision. You can create as many
instances as you want, allowing multiple modules to be used.
Here are some operations
that a DS2 module would typically perform.
Instantiate the following
DS2 package:
Calling publish() compiles
your Python module and sets it as the module that is represented by
this pymas instance. Subsequent pymas function calls, such as setting
values and executing methods, operate on this module. The Python
code is passed as a string in the first argument. Pass the name that
you want to give to your new Python module in the second argument.
publish() returns the revision number that SAS Micro Analytic Service
assigned to your new module. You could use this revision number later
to execute or delete a specific revision of your module. If you do
not specify a revision number, the latest revision is assumed. If
your Python code fails to publish (because of syntax errors, for example),
then -1 is returned for the revision number.
revision = py.publish( pgm, moduleName );
In very rare cases,
you might need to use a prior revision of a module rather than the
latest revision that would be selected by default. Or, rather than
publishing a Python module from DS2, you might need to specify a module
that was previously published to SAS Micro Analytic Service by an
external client. In these rare cases, you can call useModule() instead
of publish(). If a module was already associated with your pymas
instance before calling useModule(), then useModule() disassociates
the current module from the instance before making the specified module
current.
rc = py.useModule( moduleName, revision);
Before calling Python,
you must tell the pymas instance which method to execute. This is
accomplished by calling useMethod(). In addition to specifying the
method (Python function) to call, useMethod() also validates that
the method exists within the current module, prepares the pymas instance
to receive the input values for the specific method arguments, and
prepares to return any output values from the method execution.
rc = py.useMethod( methodName );
Call the type-specific
setter methods to set input values before executing the method. Because
these setters store arguments by name, they can be called in any order,
and they insert the values in the correct positions:
py.setDouble(“airflow”, sensor_maf);
Since the DS2 package
instance represents a single revision, the execute() method needs
no arguments.
After execution, call
getters to retrieve the results.
score = py.getDouble(“credit_score”);
Scalar argument setters
are of the form:
return_code = set<type>(name, value)
Scalar argument getters
are of the form:
Array argument setters
are of the form:
rc = set<type>Array(name, array-value)
Array argument getters
are of the following form.
Note: DS2 passes arrays and output
values by reference.
get<type>Array(name, array-value, rc)
The example below assumes
that you have declared your package as py:
dcl package pymas py;
dcl int rc;
dcl bigint result;
rc = py.publish(python_source_code, my_module_name);
py.setString(“inString”, “A string”);
py.execute()
result = py.getLong(“outLong”);
The complete set of
DS2 package methods follows, where rc
is
the integer return code, and py
is the package
instance.
Methods for Python module
management and execution:
rc = py.publish(python_source_code, "module_name");
rc = py.remove();
rc = py.isLoaded(); // returns true is Python is available and false otherwise
revision = py.getRevisionNumber();
rc = py.setTimeZone(time_zone_identifier);
rc = py.execute();
Scalar argument setters:
rc = py.setString(argument_name, value);
rc = py.setBool(argument_name, value);
rc = py.setLong(argument_name, value);
rc = py.setInt(argument_name, value);
rc = py.setDouble(argument_name, value);
rc = py.setDateTime(argument_name, value);
rc = py.setDate(argument_name, value);
rc = py.setTime(argument_name, value);
Scalar argument getters:
string_value = py.getString(argument_name);
int_value = py.getBool(argument_name);
long_value = py.getLong(argument_name);
int_value = py.getInt(argument_name);
double_value = py.getDouble(argument_name);
date_time_value = py.getDateTime(argument_name);
date_value = py.getDate(argument_name);
time_value = py.getTime(argument_name);
Array argument setters:
rc = py.setStringArray(argument_name, string_array);
rc = py.setBoolArray(argument_name, integer_array);
rc = py.setLongArray(argument_name, bigint_array);
rc = py.setIntArray(argument_name, integer_array);
rc = py.setDoubleArray(argument_name, double_array);
rc = py.setDateTimeArray(argument_name, date_time_array);
rc = py.setDateArray(argument_name, date_array);
rc = py.setTimeArray(argument_name, time_array);
Array argument getters:
py.getStringArray(argument_name, string_array, rc);
py.getBoolArray(argument_name, integer_array, rc);
py.getLongArray(argument_name, bigint_array, rc);
py.getIntArray(argument_name, integer_array, rc);
py.getDoubleArray(argument_name, double_array, rc);
py.getDateTimeArray(argument_name, date_time_array, rc);
py.getDateArray(argument_name, date_array, rc);
py.getTimeArray(argument_name, time_array, rc);
Python 2.x uses
ASCII as the default encoding. Therefore, you must specify another
encoding at the top of the file to use non-ASCII Unicode characters
in literals. As a best practice, when using Python 2.x,
always use the following as the first line of your Python script:
Also, in Python 2.x,
the Unicode literal must be preceded by the letter u. Therefore,
literal strings should be written using the following form:
Note: Python 3.x uses
UTF-8 as the default encoding, so these issues affect Python 2.x only.
When using Python 3.x, the
default encoding can be used, and literals can simply be enclosed
in quotation marks.
If you prefer not to
insert the linefeed characters yourself, you can add the Python source
code line-by-line using the appendSrcLine() method. When the entire
Python program has been added, you then call the getSource() method.
The getSource() method returns the Python program as one string, inserting
linefeed characters between Python source code lines. You can then
pass that string to the publish method to publish the Python program
in SAS Micro Analytic Service. Here is an example.
data tstinput; a = 8; b = 4; output; a = 10; b = 2; output;
run;
proc ds2;
ds2_options sas;
package testpkg /overwrite=yes;
dcl package pymas py();
dcl package logger logr('App.TableServices.DS2.Runtime.Log');
dcl varchar(67108864) character set utf8 pycode;
dcl int rc revision;
method testpkg( varchar(2048) modulename, varchar(2048)pyfuncname );
rc = py.appendSrcLine('# Here is the first Python function:');
rc = py.appendSrcLine('def domath1(a, b):');
rc = py.appendSrcLine(' "Output: c, d"');
rc = py.appendSrcLine(' print("Will compute {0} times {1}".format(a, b))');
rc = py.appendSrcLine(' c = a * b');
rc = py.appendSrcLine(' print("domath1 c is {0}".format(c))');
rc = py.appendSrcLine(' print("domath1 also do {0} div {1}".format(a, b))');
rc = py.appendSrcLine(' d = a / b');
rc = py.appendSrcLine(' print("domath1 d is {0}".format(d))');
rc = py.appendSrcLine(' return c, d');
rc = py.appendSrcLine('');
rc = py.appendSrcLine('# Here is the second function:');
rc = py.appendSrcLine('def domath2(a, b):');
rc = py.appendSrcLine(' "Output: c, d"');
rc = py.appendSrcLine(' c,d = domath1( a, b )');
rc = py.appendSrcLine(' print("domath2: c is {0} and d is {1}".format(c,d))');
rc = py.appendSrcLine(' return c, d' );
pycode = py.getSource();
logr.log( 'I', 'pycode=$s', pycode );
revision = py.publish( pycode, modulename );
if revision lt 1 then
logr.log( 'E', 'pymas.publish() failed.');
rc = py.useMethod( pyfuncname );
if rc then
logr.log( 'E', 'pymas.useMethod() failed.');
end;
method exec( double a, double b, in_out int rc,
in_out double c, in_out double d );
rc = py.setDouble( 'a', a ); if rc then return;
rc = py.setDouble( 'b', b ); if rc then return;
rc = py.execute(); if rc then return;
c = py.getDouble( 'c' );
d = py.getDouble( 'd' );
end;
endpackage;
data _null_;
dcl package logger logr( 'App.TableServices.DS2.Runtime.Log' );
dcl package testpkg t( 'my Python Module Context name', 'domath2' );
dcl int rc;
dcl double a b c d;
method run();
a = b = c = d = 0.0;
set tstinput;
t.exec( a, b, rc, c, d );
logr.log( 'I', '##### Results: a=$s b=$s c=$s d=$s',
a, b, c, d );
put a= b= c= d=;
end;
enddata;
run;
quit;
When using PROC DS2
in a SAS session to create a pymas package instance, you cannot provide
the Python program as one big quoted literal string. The reason is
that the SAS tokenizer strips out the embedded line-ending characters,
causing indentation problems in the Python code. In this situation,
the pymas package's appendSrcLine() and getSource() methods can be
used to produce a DS2 character variable containing the lines of code
concatenated together with embedded linefeed characters separating
the lines of Python code. Once you have added each line of your Python
code to the pymas package instance using the appendSrcLine() method,
you can use the "getSource() method to retrieve the complete
program into a DS2 character variable, which can then be provided
as the first input argument to the pymas publish() method. Here is
an example.
ds2_options sas;
package testpkg /overwrite=yes;
dcl package pymas py();
dcl package logger logr('App.tk.MAS');
dcl varchar(67108864) character set utf8 pycode;
dcl int rc revision;
method testpkg( varchar(256) modulename,
varchar(256) pyfuncname );
rc = py.appendSrcLine('# The first Python function:');
rc = py.appendSrcLine('def domath1(a, b):');
rc = py.appendSrcLine(' "Output: c, d"');
rc = py.appendSrcLine(' c = a * b');
rc = py.appendSrcLine(' d = a / b');
rc = py.appendSrcLine(' return c, d');
rc = py.appendSrcLine('');
rc = py.appendSrcLine('# Here is the second function:');
rc = py.appendSrcLine('def domath2(a, b):');
rc = py.appendSrcLine(' "Output: c, d"');
rc = py.appendSrcLine(' c,d = domath1( a, b )');
if rc then logr.log( 'E', 'py.appendSrcLine() failed.');
rc = py.appendSrcLine(' return c, d' );
pycode = py.getSource();
revision = py.publish( pycode, modulename );
if revision lt 1 then
logr.log( 'E', 'py.publish() failed.');
rc = py.useMethod( pyfuncname );
if rc then logr.log( 'E', 'py.useMethod() failed.');
end;
method usefunc( varchar(256) pyfuncname );
rc = py.useMethod( pyfuncname );
if rc then logr.log( 'E', 'py.useMethod() failed.');
end;
method exec( double a, double b, in_out int rc,
in_out double c, in_out double d );
rc = py.setDouble( 'a', a ); if rc then return;
rc = py.setDouble( 'b', b ); if rc then return;
rc = py.execute(); if rc then return;
c = py.getDouble( 'c' );
d = py.getDouble( 'd' );
end;
endpackage;