Skip to content

Commit

Permalink
Merge pull request #11 from cqframework/api_key
Browse files Browse the repository at this point in the history
Api key
  • Loading branch information
cmoesel authored Oct 22, 2020
2 parents 8f71e15 + ce835f8 commit 4a93ca3
Show file tree
Hide file tree
Showing 6 changed files with 394 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 2017,
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ Downloading value set definitions from VSAC requires a valid UMLS account. The
function allows a UMLS username and password to be passed in. Alternately, the UMLS username and password can be
provided via `UMLS_USER_NAME` and `UMLS_PASSWORD` environment variables.

**NOTE**: As of Jan 1 2021 VSAC will no longer accept accept username and password and will require an API key. The code
service's `ensureValueSetsWithAPIKey` allows a UMLS API key to be passed in. Alternatively, the UMLS API key can be
provided via `UMLS_API_KEY` environment variables.

## Downloading Value Set Definitions

The `ensureValueSets` and `ensureValueSetsInLibrary` functions are the only functions that attempt to download value
Expand Down
48 changes: 47 additions & 1 deletion lib/CodeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const fs = require('fs');
const proc = require('process');
const env = proc.env;
const path = require('path');
const {downloadFromVSAC} = require('./download-vsac');
const {downloadFromVSAC,downloadFromVSACWithAPIKey} = require('./download-vsac');
const extractOidAndVersion = require('./extractOidAndVersion');

/**
Expand Down Expand Up @@ -64,12 +64,16 @@ class CodeService {
* Given a list of value set references, will ensure that each has a local
* definition. If a local definition does not exist, the value set will
* be downloaded using the VSAC API.
* @deprecated As of Jan 1 2021 VSAC will no longer accept accept username and password.
* Please use ensureValueSetsWithAPIKey() instead.
* @param {Object} valueSetList - an array of objects, each containing "name"
* and "id" properties, with an optional "version" property
* @returns {Promise.<undefined,Error>} A promise that returns nothing when
* resolved and returns an error when rejected.
*/
ensureValueSets(valueSetList = [], umlsUserName = env['UMLS_USER_NAME'], umlsPassword = env['UMLS_PASSWORD'], caching = true) {
console.warn('WARNING! As of Jan 1 2021 VSAC will no longer accept accept username and password. As such ' +
'ensureValueSets() has been deprecated, please use the new ensureValueSetsWithAPIKey() function instead!');
// First, filter out the value sets we already have
const filteredVSList = valueSetList.filter(vs => {
const result = this.findValueSet(vs.id, vs.version);
Expand All @@ -84,21 +88,63 @@ class CodeService {
return downloadFromVSAC(umlsUserName, umlsPassword, filteredVSList, this.cache, this.valueSets, caching);
}
}
/**
* Given a list of value set references, will ensure that each has a local
* definition. If a local definition does not exist, the value set will
* be downloaded using the VSAC API.
* @param {Object} valueSetList - an array of objects, each containing "name"
* and "id" properties, with an optional "version" property
* @returns {Promise.<undefined,Error>} A promise that returns nothing when
* resolved and returns an error when rejected.
*/
ensureValueSetsWithAPIKey(valueSetList = [], umlsAPIKey = env['UMLS_API_KEY'], caching = true) {
// First, filter out the value sets we already have
const filteredVSList = valueSetList.filter(vs => {
const result = this.findValueSet(vs.id, vs.version);
return typeof result === 'undefined';
});
// Now download from VSAC if necessary
if (filteredVSList.length == 0) {
return Promise.resolve();
} else if ( typeof umlsAPIKey === 'undefined' || umlsAPIKey == null) {
return Promise.reject('Failed to download value sets since UMLS_API_KEY is not set.');
} else {
return downloadFromVSACWithAPIKey(umlsAPIKey,filteredVSList, this.cache, this.valueSets, caching);
}
}

/**
* Given a library, will detect referenced value sets and ensure that each has a local definition. If a local definition
* does not exist, the value set will be downloaded using the VSAC API.
* @deprecated As of Jan 1 2021 VSAC will no longer accept accept username and password. Please use
* ensureValueSetsInLibraryWithAPIKey() instead.
* @param {Object} library - the CQL Library object to look for referenced value sets in
* @param {boolean} checkIncluded - indicates if "included" libraries should also be checked
* @param {string} umlsUserName - the UMLS username to use when downloading value sets (defaults to env "UMLS_USER_NAME")
* @param {string} umlsPassword - the UMLS password to use when downloading value sets (defaults to env "UMLS_PASSWORD")
* @returns {Promise.<undefined,Error>} A promise that returns nothing when resolved and returns an error when rejected.
*/
ensureValueSetsInLibrary(library, checkIncluded = true, umlsUserName = env['UMLS_USER_NAME'], umlsPassword = env['UMLS_PASSWORD'], caching = true) {
console.warn('WARNING! As of Jan 1 2021 VSAC will no longer accept accept username and password. As such ' +
'ensureValueSetsInLibrary() has been deprecated, please use the new ensureValueSetsInLibraryWithAPIKey() function instead!');
const valueSets = extractSetOfValueSetsFromLibrary(library, checkIncluded);
return this.ensureValueSets(Array.from(valueSets), umlsUserName, umlsPassword, caching);
}

/**
* Given a library, will detect referenced value sets and ensure that each has a local definition. If a local definition
* does not exist, the value set will be downloaded using the VSAC API.
* @param {Object} library - the CQL Library object to look for referenced value sets in
* @param {boolean} checkIncluded - indicates if "included" libraries should also be checked
* @param {string} umlsAPIKey - the UMLS API Key to use when downloading value sets (defaults to env "UMLS_USER_NAME")
* @returns {Promise.<undefined,Error>} A promise that returns nothing when resolved and returns an error when rejected.
*/
ensureValueSetsInLibraryWithAPIKey(library, checkIncluded = true, umlsAPIKey = env['UMLS_API_KEY'], caching = true) {
const valueSets = extractSetOfValueSetsFromLibrary(library, checkIncluded);
return this.ensureValueSetsWithAPIKey(Array.from(valueSets), umlsAPIKey, caching);
}


/**
* The findValueSetsByOid function is kept for backwards compatibility (and since cql-execution
* calls it), but now it just calls the more appropriately named findValuesets.
Expand Down
71 changes: 70 additions & 1 deletion lib/download-vsac.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ const parseVSACXML = require('./parse-vsac');
const extractOidAndVersion = require('./extractOidAndVersion');
const debug = require('debug')('vsac'); // To turn on DEBUG: $ export DEBUG=vsac



/*
* @deprecated: As of Jan 1 2021 VSAC will no longer accept accept username and password.
* Please use downloadFromVSACWithAPIKey instead.
*/
function downloadFromVSAC(username, password, input, output, vsDB={}, caching=true) {
const oidsAndVersions = [];
Object.keys(input).forEach((key) => {
Expand Down Expand Up @@ -55,6 +61,59 @@ function downloadFromVSAC(username, password, input, output, vsDB={}, caching=tr
}
}

function downloadFromVSACWithAPIKey(apiKey, input, output, vsDB={}, caching=true){
const oidsAndVersions = [];
Object.keys(input).forEach((key) => {
let [id, version] = [input[key].id, input[key].version];
const [oid, embeddedVersion] = extractOidAndVersion(id);
if (version == null && embeddedVersion != null) {
version = embeddedVersion;
}
if (vsDB[oid] == null || vsDB[oid][version] == null) {
oidsAndVersions.push({ oid, version });
}
});
if (oidsAndVersions.length) {
output = path.resolve(output);
if (caching && !fs.existsSync(output)){
mkdirp.sync(output);
}
return getTicketGrantingTicketWithAPIKey(apiKey)
.then((ticketGrantingTicket) => {
const promises = oidsAndVersions.map(({ oid, version }) => {
// Catch errors and convert to resolutions returning an error. This ensures Promise.all waits for all promises.
// See: http://stackoverflow.com/questions/31424561/wait-until-all-es6-promises-complete-even-rejected-promises
return downloadValueSet(ticketGrantingTicket, oid, version, output, vsDB, caching)
.catch((err) => {
debug(`Error downloading valueset ${oid}${version || ''}`, err);
return new Error(`Error downloading valueset: ${oid}${version || ''}`);
});
});
return Promise.all(promises);
})
.then((results) => {
const errors = results.filter(r => r instanceof Error);
if (results.length - errors.length > 0) {
// There were results, so write the file first before resolving/rejecting
return writeFile(path.join(output, 'valueset-db.json'), JSON.stringify(vsDB, null, 2), caching)
.then(
(result) => errors.length == 0 ? result : Promise.reject(errors),
(err) => { errors.push(err); return Promise.reject(errors); }
);
}
if (errors.length > 0) {
return Promise.reject(errors);
}
});
} else {
return Promise.resolve();
}
}

/*
* @deprecated: As of Jan 1 2021 VSAC will no longer accept accept username and password.
* Please use getTicketGrantingTicketWithAPIKey instead.
*/
function getTicketGrantingTicket(username, password) {
debug('Getting TGT');
const options = {
Expand All @@ -65,6 +124,16 @@ function getTicketGrantingTicket(username, password) {
return rpn(options);
}

function getTicketGrantingTicketWithAPIKey(apiKey){
debug('Getting TGT');
const options = {
method: 'POST',
url: 'https://vsac.nlm.nih.gov/vsac/ws/Ticket',
form: { apikey: apiKey }
};
return rpn(options);
}

function downloadValueSet(ticketGrantingTicket, oid, version, output, vsDB={}, caching=true) {
return getServiceTicket(ticketGrantingTicket)
.then((serviceTicket) => {
Expand Down Expand Up @@ -117,4 +186,4 @@ function writeFile(file, data, caching=true) {
});
}

module.exports = {downloadFromVSAC};
module.exports = {downloadFromVSAC,downloadFromVSACWithAPIKey};
32 changes: 32 additions & 0 deletions manual-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable no-console */
const { CodeService } = require('./lib/CodeService');
const proc = require('process');
const env = proc.env;

const VALUESET = { name: 'HDL Cholesterol', id: '2.16.840.1.113883.3.464.1003.104.12.1013' };

async function main() {
if (env['UMLS_USER_NAME'] == null && env['UMLS_PASSWORD'] == null && env['UMLS_API_KEY'] == null) {
console.error('This test requires you to set the UMLS_API_KEY environment variable');
process.exit(1);
}

const codeService = new CodeService('manual-test-vsac-cache', false);
console.log(`CALL: codeService.findValueSet(${VALUESET.id})`);
console.log('EXPECT: undefined');
let found = codeService.findValueSet(VALUESET.id);
console.log(`RESULT: ${found}`);
console.log();
console.log(`CALL: codeService.ensureValueSetsWithAPIKey(['${JSON.stringify(VALUESET)}'], env['UMLS_API_KEY'], false);`);
await codeService.ensureValueSetsWithAPIKey([VALUESET], env['UMLS_API_KEY'], false);
console.log('EXPECT: <void>');
console.log(`RESULT: <void>`);
console.log();
console.log(`CALL: codeService.findValueSet(${VALUESET.id})`);
found = codeService.findValueSet(VALUESET.id);
console.log('EXPECT: <valueSet w/ two codes>');
console.log(`RESULT: ${JSON.stringify(found, null, 2)}`);
}

main().catch(e => console.error(e));

Loading

0 comments on commit 4a93ca3

Please sign in to comment.