-
Notifications
You must be signed in to change notification settings - Fork 10
lazyProgrammer
Philipp Lenssen writes in an article titled "Why Good Programmers Are Lazy and Dumb"
Lazy because only lazy programmers will want to write the kind of tools that might replace them in the end. Lazy, because only a lazy programmer will avoid writing monotonous, repetitive code -- thus avoiding redundancy, the enemy of software maintenance and flexible refactoring. Mostly, the tools and processes that come out of this endeavor fired by laziness will speed up the production. This makes a lazy programmer a smart and efficient programmer.
From a SAS Viya Application developer's perspective
One of the great strengths of SAS Viya is the large collection of REST APIs that allow customers to integrate SAS Viya capabilities into their applications. Using these APIs, the user's applications access the full complement of SAS Viya capabilities - data preparation, analysis and reporting.
Just about every SAS Viya application will call a few key services to do the heavy lifting on the Viya server. The figure below shows these typical patterns.
How many times does one have to write the same code to call different REST APIs when those are all similar, but for the payload? How many times does one to have to write code to "reduce" the JSON response to be useful in presentations?
You too can become a smart lazy programmer by using the techniques discussed in this article. Spend less time writing code that writes itself, spend more time solving real business problems and use the extra time for things you always wanted to do.
Keeping with the lazy programmer point of view I have developed two JavaScript libraries to take care of the tedious part of application development.
-
restaf is a lightweight, UI-framework agnostic, JavaScript library designed to be easy-to-use for app developers. restaf takes advantage of the consistency of the REST APIs to reduce the responses from the server into a standard form, and it also manages the data on the client side. This reduction enables developers to interact with the server in a repeating pattern regardless of which service the application is using.
-
restaflib is a helper library built on top of restaf to simplify the common patterns described above.
Code developed with these libraries run in a browser or a nodejs application. All the examples shown in this article run in either scenario.
Include the following two script tags.
<script src="https://unpkg.com/@sassoftware/restaf/dist/restaf.min.js"></script>
<script src="https://unpkg.com/@sassoftware/restaflib/dist/restaflib.min.js"></script>
Two globals restaf and restaflib will be available for use in your script tags.
Install restaf and restaflib using the following command
npm install @sassoftware/restaf @sassoftware/restaflib
A typical initialization code(for both web and nodejs applications) will look like this:
let store = restaf.initStore();
store.logon(logonPayload)
.then (() =< {
...your code...
})
.catch(err => {
...handle errors...
})
Please refer to the authentication page of the restaf repository for details on how the logon method works.
For fast access to specific sections use these links
- SAS Programs
- Run Cas Action
- Upload Data to CAS
- Run CASL Script on Server
- Score with MAS
- Score with CAS
- Scoring with CAS actions
- Embed VA reports
- Other common scenarios
The rest of article explains how one can program for the common scenarios shown in the figure above.
A traditional SAS program consists of PROC statements, data step, and macros. In SAS Viya this executes using the compute service.
The compute service API covers many different uses. The simplest and most common use cases are run a SAS job and view the ODS output, log, listing, and browse the generated tables.
This method creates a compute service session.
Syntax
let session = await restaflib.computeSetup(store, contextName);
The parameters are:
-
store - restaf store object
-
contextName - (optional) if not specified the first context in the list is used
This method executes the specified code on the server
Syntax
let computeSummary = await restaflib.computeRun(store, session, src, macros);
The parameters are:
-
store - restaf store object
-
session - the session object obtained thru the computeSetup method
-
src - a string containing the source code to be executed
-
macros - (optional) A javascript object that is sent to the server as macros. For example, if this object is {lib: 'sasuser', data: 'mydata'} the following macros are prepended to the source code.
%let lib=sasuser; %let data=mydata;
The result from the computeRun method is used to retrieve specific results using the following methods.
This method retrieves the ods output, log, listing, and the list of tables.
Syntax
let result = await restaflib.computeResults(store, computeSummary,
type);
The parameters are:
-
store - the restaf store
-
computeSummary - the output from restaflib.computeRun
-
type - A string with one of the following values ods|log|listing|tables. Based on this option different results are returned.
The results based on the value of type are:
-
ods - This returns a string that has the ODS output. This html can be passed to some component for display.
-
log - This returns all the log for this particular job. The content is an array of objects that follows the media type for loglines
-
listing - This returns all the listings for this particular job with the same format as the log.
-
tables - This returns an array with the names of the tables from the job. Use this name to retrieve the actual records using the restaflib.computeFetchData method described below.
Use this method to page thru the records of the tables returned by the computeRun method.
Syntax
let result = await restaflib.computeFetchData(store, computeSummary,
tablename, direction);
The parameters are:
-
store -- the restaf store
-
computeSummary - the output from restaflib.computeRun
-
tablename - The name of the table returned (see computeResults method above).
-
direction - A string indicatilng which direction to scroll (next, prev) and get records. If not specified, the first set of records are returned. The valid values of this parameter depend on what was returned in the scrollOptions field of the result.
The result from the computeFetchData has the following schema:
{
rows : An array of data rows - [{val1, val2,...}, {val1,val2,...}] ,
columns : The standard schema for the columns,
scrollOptions: An array of valid scrolling directions - first,prev,next,last
}
async function example () {
let computeSession = await computeSetup(store);
/* prep input */
let macros = {maxRows: 500};
let src = `ods html style=barrettsblue;
data work.dtemp1;
array x{10};
do j = 1 to &maxRows;
do i = 1 to 10;
x{i} = i * 10;
end;
output;
put _ALL_;
end;
run;
proc print;run;
ods html close;`;
// Run the SAS job
let computeSummary = await restaflib.computeRun(store, computeSession, src, macros);
// Get log, listing, ods and list of tables
let log = await restaflib.computeResults(store, computeSummary, 'log');
let list = await restaflib.computeResults(store, computeSummary, 'listing');
let ods = await restaflib.computeResults(store, computeSummary, 'ods');
let tables = await restaflib.computeResults(store, computeSummary, 'tables');
// Scroll thru one of the tables
let data = await restaflib.computeFetchData(store,computeSummary, 'DTEMP1');
console.log(data.columns);
console.log(data.rows);
do {
data = await restaflib.computeFetchData(store,computeSummary, 'DTEMP1', 'next');
if (data !== null) {
console.log(data.rows);
}
} while (data != null);
// Delete session
await store.apiCall(computeSession.links('delete'));
}
This method creates a CAS Session
Syntax
let {session} = await restaflib.casSetup(store);
This method runs the specified action.
Syntax
let actionResult = await restaflib.casActionRun(store, session,
payload);
The arguments to this method are:
-
store - the store object for restaf
-
session - the session object from the casSetup method
-
payload - an object with the schema describe below
The payload is:
{
action : name of the action - actionset.actionname,
data : parameters for the selected actio
headers: If needed
}
async function example() {
let {session} = await restaflib.casSetup (store);
let p = {
action: 'builtins.echo',
data : {
code: 'data casuser.score; x1=10;x2=20;x3=30; score1 = x1+x2+x3;run; '
}
};
r = await restaflib.casActionRun(store, session, p);
await store.apiCall(session.links('delete'));
return 'done';
};
The result of the action will vary based on the action. For more details, refer to the CAS Actions documentation.
This action requires information be passed in the header. Below is an example for uploading a cars.csv file to casuser.cars:
let JSON_Parameters = {
casout: {
caslib: 'casuser' /* a valid caslib */,
name : 'cars' /* name of output file on cas server */
},
importOptions: {
fileType: 'csv' /* type of the file being uploaded */
}
};
let p = {
headers: { 'JSON-Parameters': JSON_Parameters },
data : readFile(filename, fileType), /* read file from a local csv file */
action : 'table.upload'
};
let actionResult = await restaflib.casActionRun(store, session, p);
... process results ...
A simpler way is to use the casUpload method, described in the next section.
The casUpload method handles the common scenario of loading CSV into CAS for further processing.
This method creates a CAS Session.
Syntax
let {session} = await restaflib.casSetup(store);
Syntax
let r = await restaflib.casUpload(store, session, filePath, output,
save, altSrc);
The parameters are:
-
store -- the restaf store
-
session -- the current CAS session obtained via casSetup
-
filePath -- path to the file (ex: /mydata/cars.csv)
-
output -- set to true if you want the data to be saved; the default is false
-
altSrc -- in some applications the CSV file is generated programmatically -- in that case pass the string here
The casUpload function handles other file types also -- see here for more details.
async function example(path, table) {
let {session} = await casSetup(store);
await restaflib.casUpload(store, session,
'./mydata/cars.csv', 'casuser.cars', true, null);
};
A common workflow may require multiple CAS actions, in sequence, with additional flow control. The CASL language is ideal for such scripting. The CASL code executes using the sccasl.runCASl action. While this action could run using the casActionRun method, the usefulness of this approach dictates a separate method. This section shows the use of caslRun method to run the CASL scripts on the server.
Syntax
let result = restaflib.caslRun(store, session, casl, args)
The arguments to this function are:
-
store - the restaf store
-
session - the session obtained through casSetup
-
casl - A string with the CASL code to execute
-
args - The args parameter (a standard js object) is passed to the CASL program as a CASL dictionary named _args_. This is a friendly way to pass parameters to your CASL programs.
The caslRun executes the sccasl.runcasl action on the CAS server. If the status in the results is non-zero caslRun throws an error you handle in your catch block. The key elements of the response are:
-
result.log - is the log lines
-
result.disposition - the standard CAS disposition object
-
result.results - the object passed to the send_response function in the CASL program
async function example() {
let {session} = await restaflib.casSetup (store);
/* Recommendation: Store the casl code in a repository */
let casl = `
print 'input values';
print _args_;
action datastep.runcode/ single='YES' code = 'data casuser.a; x=1; run;';
action table.fetch r=r1/
table= { caslib= 'casuser', name= 'a' } ;
run;
action datastep.runcode/ single='YES' code = 'data casuser.b; y=1; run;';
action table.fetch r=r2/
table= { caslib= 'casuser', name= 'b' } ;
run;
c = {a=10, b=20}; /* just to show that anything can be returned */
send_response({a=r1, b=r2, c=c});
`;
let args = {a: "this is arguments", b: "more data"};
let result = await restaflib.caslRun (store, session, casl, args);
console.log(JSON.stringify(result, null,4));
await store.apiCall(session.links('delete'));
}
The Micro Analytic Score (MAS) service runs real-time scoring for many models and a large number of users. The models to be scored are published to the server.
This method sets up the necessary information to score with MAS.
Syntax
let masControl = await restaflib.masSetup(store, models);
The arguments to this method are:
-
store - restaf store
-
models - an array of model names you want to use for scoring
Use this method to get information on the input to the scoring model.
Syntax
let input = masDescribe(masControl, modelName, step);
The parameters to this method are:
-
masControl - the object returned from masSetup
-
modelName -- the name of the model
-
step - the step you are interested in (defaults to score)
This is the method you will call to score each scenario
Syntax
let result = await restaflib.masRun(store, masControl,
modelName,scenario, step);
The parameters to this method are:
-
store -- the restaf store
-
masControl - the object from casSetup
-
modelName - the name of the model to use for scoring; has to have been specified in the models array to casSetup
-
scenario - a JS object with the input values (see example below)
-
step -- (optional) If not specified, the first 'score' in the model will be used for scoring.
async function example () {
let models = ['sdktgo_iris`];`
let masControl = await restaflib.masSetup(store, models);
let scenario = {
petallengthcm: 1.5,
petalwidthcm : 0.2,
sepallengthcm: 5,
sepalwidthcm : 3
};
let result = await ``restaflib.masRun`` (store, ``masControl``, models[0], scenario);
console.log(result);
};
The sample output is shown below
A sample result is:
{
I_Species`` : 'Iris-setosa',
P_SpeciesIris_setosa : 1,
P_SpeciesIris_versicolor : 0,
P_SpeciesIris_virginica : 0,
_leaf_id_ : 2 }
Models can be published to different destinations. MAS is one of the destinations. The CAS server is also a valid destination.
The caslScore method is used to score models published to a CAS destination.
Syntax
let r = await restaflib.caslScore(store, session, scenario);
The three arguments to this method are:
-
store - the restaf store
-
session - the CAS session object obtained via casSetup
-
payload - Information for scoring. See the description below.
The payload (see example below) has the following data:
-
the name of the model
-
the Caslib and name of where the model is stored (a sashdat)
-
the scenario for scoring
async function example() {
let {session} = await restaflib.casSetup(store);
let scenario = {
modelName: 'Gradient_Boosting_7adb0404_85e3_474d_9d03_1059ef7ae008',
model : { caslib: 'public', name: 'testpublish' },
scenario : {
sensor_ratio : 4.3,
days_out_of_service: 5
}
};
let r = await restaflib.caslScore(store, session, scenario);
console.log(JSON.stringify(r, null,4));
}
The result is a Javascript object with the data returned from scoring. In the example above the result is:
{
"days_out_of_service": 5,
"sensor_ratio": 4.3,
"_Index_": 1,
"EM_EVENTPROBABILITY": 0.10521221221641,
"I_FAILURE": " 0",
"P_FAILURE0": 0.89478778778358,
"P_FAILURE1": 0.10521221221641,
"_WARN_": "",
"EM_CLASSIFICATION": " 0",
"EM_PROBABILITY": 0.89478778778358
}
In some scenarios, the models have been imported into a CAS server. The form of the models could be one of these:
-
Astore objects
-
Data step code for scoring
-
DS2 code for scoring
Use the caslScore method discussed earlier for scoring but with a different payload -- mainly not specifying the modelName
The method will decide the correct action based on the content of the model.
Syntax
let r = await restaflib.caslScore(store, session, scenario);
The three arguments to this method are:
-
store - the restaf store
-
session - the CAS session object obtained via casSetup
-
payload - Information for scoring. See the description below.
The payload (see example below) has the following data:
-
The Caslib and name of where the model is stored (a sashdat)
-
The scenario for scoring
async function example() {
let scenario = {
model : { caslib: 'casuser', name: 'gradient_boosting___bad_2jest' },
scenario: {
LOAN : 100000,
VALUE : 10000,
JOB : 'J1',
CLAGE : 100,
CLNO : 20,
DEBTINC: 20,
DELINQ : 2,
DEROG : 0,
MORTDUE: 4000,
NINQ : 1,
YOJ : 10
}
};
r = await caslScore(store, session, scenario);
return r;
};
A very common scenario is embedding SAS Visual Analytics reports in web applications.
The HTML tag for embedding the report looks like this:
<sas-report url=your-viya-server reportUri=uri-to-the report
authenticationType="credentials"></sas-report>
To use this tag one needs the URI for the report. The following methods helps you get this information.
Initialize access to the reports service. *Note, this is a method on the store.
Syntax
let {reports} = await store.addServices('reports');
This method returns the URI for the selected report.
Syntax
let reportList = restaflib.getReportUri(store, name-of-your-report);
returns an array of the form [name-of-your-report, uri-for-that-report]
Here is a sample code to set the properties for a sas-report tag with the uri of the report.
objectElement = document.getElementById('report0');
objectElement.authenticationType ="credentials";
objectElement.url = host;/* the url for the Viya server*/
objectElement.reportUri = reportsList[0].uri;
In many applications there is a need to list items(resources) like these: List of files, List of MAS models, List of VA reports etc... Below is an example for listing all the VA reports. Replace all references of "reports" to your favorite service(files, folders etc...)
async function listReports(store) {
//connect to reports microservice and get root links
let { reports } = await store.addServices('reports');
//get the first list of reports
let result= await store.apiCall(reports.links('reports'));
console.log(JSON.stringify(result.itemsList(), null,4));
// get the rest of the reports
let next;
// do this loop while the service returns the next link
while ((next = result.scrollCmds('next')) !== null) {
result = await store.apiCall(next);
console.log(JSON.stringify(result.itemsList(), null,4));
}
return 'done';
};
SAS Viya REST APIs allow developers to do a lot more than what was discussed in this article. Users can use restaf and more wrapper libraries like restaflib to build full applications. These libraries are available as open source at https://github.com/sassoftware
Please feel free to clone these and modify them as you see fit.
These are links to some examples that might be useful for learning purposes:
-
Web application demos https://github.com/sassoftware/restaf-uidemos/tree/master/public
-
nodejs demos - [https://github.com/sassoftware/restaf/tree/2.0.0/packages/examples/testFunctions](https://github.com/sassoftware/restaf/tree/2.0.0/packages/ s/testFunctions)
-
AWS serverless function - https://github.com/sassoftware/restaf/tree/2.0.0/packages/serverless-score/src
-
Optimization application - https://github.com/sassoftware/restaf/tree/2.0.0/packages/serverless-score/src
.... Under construction...
-
restaf
-
restaflib
-
Examples:
- Cas Server
- Compute Server
- Scoring with MAS
- Scoring with CAS
- Utility
-
CAS Related Functions
-
MAS Related Functions
-
Compute Server Related Functions
-
Reports Related Functions
-
Interactive CLI
-
Special Topic