Browse Source

ModelServer JSON based web api

David Sehnal 6 years ago
parent
commit
31e92e4425

+ 6 - 2
src/servers/model/properties.ts

@@ -7,6 +7,10 @@
 import { Model } from 'mol-model/structure';
 import { StructureQualityReport } from './properties/structure-quality-report';
 
-export async function attachModelProperties(model: Model) {
-    await StructureQualityReport.attachFromPDBeApi(model);
+export function attachModelProperties(model: Model): Promise<any>[] {
+    // return a list of promises that start attaching the props in parallel
+    // (if there are downloads etc.)
+    return [
+        StructureQualityReport.attachFromPDBeApi(model)
+    ];
 }

+ 1 - 2
src/servers/model/properties/structure-quality-report.ts

@@ -46,7 +46,6 @@ const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [
     CifField.int<ResidueIndex, ExportCtx>('auth_seq_id', (i, d) => P.residue.auth_seq_id(d.residues[i])),
     CifField.str<ResidueIndex, ExportCtx>('auth_asym_id', (i, d) => P.chain.auth_asym_id(d.residues[i])),
 
-
     CifField.str<ResidueIndex, ExportCtx>('issues', (i, d) => d.issues.get(d.residueIndex[d.residues[i].element])!.join(','))
 ];
 
@@ -106,7 +105,7 @@ export namespace StructureQualityReport {
         if (model.customProperties.has(Descriptor)) return true;
 
         const id = model.label.toLowerCase();
-        const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`);
+        const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, { timeout: 500 });
         const json = await rawData.json();
         const data = json[id];
         if (!data) return false;

+ 8 - 1
src/servers/model/query/atoms.ts

@@ -8,6 +8,7 @@ import { QueryPredicate, StructureElement, StructureProperties as Props, Queries
 import { AtomsQueryParams } from 'mol-model/structure/query/queries/generators';
 
 export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] {
+    if (!params) return [{ }];
     if (Array.isArray(params)) {
         return params.map(p => atomsTest(p));
     } else {
@@ -25,12 +26,14 @@ function atomsTest(params: any): Partial<AtomsQueryParams> {
 }
 
 function entityTest(params: any): QueryPredicate | undefined {
-    if (typeof params.entity_id === 'undefined') return void 0;
+    if (!params || typeof params.entity_id === 'undefined') return void 0;
     const p = Props.entity.id, id = '' + params.label_entity_id;
     return ctx => p(ctx.element) === id;
 }
 
 function chainTest(params: any): QueryPredicate | undefined {
+    if (!params) return void 0;
+
     if (typeof params.label_asym_id !== 'undefined') {
         const p = Props.chain.label_asym_id, id = '' + params.label_asym_id;
         return ctx => p(ctx.element) === id;
@@ -43,6 +46,8 @@ function chainTest(params: any): QueryPredicate | undefined {
 }
 
 function residueTest(params: any): QueryPredicate | undefined {
+    if (!params) return void 0;
+
     const props: StructureElement.Property<any>[] = [], values: any[] = [];
 
     if (typeof params.label_seq_id !== 'undefined') {
@@ -74,6 +79,8 @@ function residueTest(params: any): QueryPredicate | undefined {
 }
 
 function atomTest(params: any): QueryPredicate | undefined {
+    if (!params) return void 0;
+
     const props: StructureElement.Property<any>[] = [], values: any[] = [];
 
     if (typeof params.label_atom_id !== 'undefined') {

+ 25 - 15
src/servers/model/server/api-web.ts

@@ -6,7 +6,6 @@
 
 import * as express from 'express';
 import Config from '../config';
-import { QueryDefinition, QueryList } from './api';
 import { ConsoleLogger } from 'mol-util/console-logger';
 import { resolveJob } from './query';
 import { JobManager } from './jobs';
@@ -81,24 +80,35 @@ async function processNextJob() {
     }
 }
 
-function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
-    app.get(makePath(':entryId/' + queryName), (req, res) => {
-        ConsoleLogger.log('Server', `Query '${req.params.entryId}/${queryName}'...`);
+// function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
+//     app.get(makePath(':entryId/' + queryName), (req, res) => {
+//         ConsoleLogger.log('Server', `Query '${req.params.entryId}/${queryName}'...`);
 
-        if (JobManager.size >= Config.maxQueueLength) {
-            res.status(503).send('Too many queries, please try again later.');
-            res.end();
-            return;
-        }
+//         if (JobManager.size >= Config.maxQueueLength) {
+//             res.status(503).send('Too many queries, please try again later.');
+//             res.end();
+//             return;
+//         }
+
+//         const jobId = JobManager.add('pdb', req.params.entryId, queryName, req.query);
+//         responseMap.set(jobId, res);
+//         if (JobManager.size === 1) processNextJob();
+//     });
+// }
 
-        const jobId = JobManager.add('pdb', req.params.entryId, queryName, req.query);
+export function initWebApi(app: express.Express) {
+    app.get(makePath('query'), (req, res) => {
+        const query = /\?(.*)$/.exec(req.url)![1];
+        const args = JSON.parse(decodeURIComponent(query));
+        const name = args.name;
+        const entryId = args.id;
+        const params = args.params || { };
+        const jobId = JobManager.add('pdb', entryId, name, params);
         responseMap.set(jobId, res);
         if (JobManager.size === 1) processNextJob();
     });
-}
 
-export function initWebApi(app: express.Express) {
-    for (const q of QueryList) {
-        mapQuery(app, q.name, q.definition);
-    }
+    // for (const q of QueryList) {
+    //     mapQuery(app, q.name, q.definition);
+    // }
 }

+ 39 - 47
src/servers/model/server/api.ts

@@ -34,18 +34,18 @@ export interface QueryDefinition {
     structureTransform?: (params: any, s: Structure) => Promise<Structure>
 }
 
-const AtomSiteParams = {
-    entity_id: <QueryParamInfo>{ name: 'entity_id', type: QueryParamType.String, description: 'Corresponds to the \'_entity.id\' or \'*.label_entity_id\' field, depending on the context.' },
+// const AtomSiteParams = {
+//     entity_id: <QueryParamInfo>{ name: 'entity_id', type: QueryParamType.String, description: 'Corresponds to the \'_entity.id\' or \'*.label_entity_id\' field, depending on the context.' },
 
-    label_asym_id: <QueryParamInfo>{ name: 'label_asym_id', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.label_asym_id\' field.' },
-    auth_asym_id: <QueryParamInfo>{ name: 'auth_asym_id', type: QueryParamType.String, exampleValue: 'A', description: 'Corresponds to the \'_atom_site.auth_asym_id\' field.' },
+//     label_asym_id: <QueryParamInfo>{ name: 'label_asym_id', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.label_asym_id\' field.' },
+//     auth_asym_id: <QueryParamInfo>{ name: 'auth_asym_id', type: QueryParamType.String, exampleValue: 'A', description: 'Corresponds to the \'_atom_site.auth_asym_id\' field.' },
 
-    label_seq_id: <QueryParamInfo>{ name: 'label_seq_id', type: QueryParamType.Integer, description: 'Residue seq. number. Corresponds to the \'_atom_site.label_seq_id\' field.' },
-    auth_seq_id: <QueryParamInfo>{ name: 'auth_seq_id', type: QueryParamType.Integer, exampleValue: '200', description: 'Author residue seq. number. Corresponds to the \'_atom_site.auth_seq_id\' field.' },
-    label_comp_id: <QueryParamInfo>{ name: 'label_comp_id', type: QueryParamType.String, description: 'Residue name. Corresponds to the \'_atom_site.label_comp_id\' field.' },
-    auth_comp_id: <QueryParamInfo>{ name: 'auth_comp_id', type: QueryParamType.String, exampleValue: 'REA', description: 'Author residue name. Corresponds to the \'_atom_site.auth_comp_id\' field.' },
-    pdbx_PDB_ins_code: <QueryParamInfo>{ name: 'pdbx_PDB_ins_code', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.pdbx_PDB_ins_code\' field.' },
-};
+//     label_seq_id: <QueryParamInfo>{ name: 'label_seq_id', type: QueryParamType.Integer, description: 'Residue seq. number. Corresponds to the \'_atom_site.label_seq_id\' field.' },
+//     auth_seq_id: <QueryParamInfo>{ name: 'auth_seq_id', type: QueryParamType.Integer, exampleValue: '200', description: 'Author residue seq. number. Corresponds to the \'_atom_site.auth_seq_id\' field.' },
+//     label_comp_id: <QueryParamInfo>{ name: 'label_comp_id', type: QueryParamType.String, description: 'Residue name. Corresponds to the \'_atom_site.label_comp_id\' field.' },
+//     auth_comp_id: <QueryParamInfo>{ name: 'auth_comp_id', type: QueryParamType.String, exampleValue: 'REA', description: 'Author residue name. Corresponds to the \'_atom_site.auth_comp_id\' field.' },
+//     pdbx_PDB_ins_code: <QueryParamInfo>{ name: 'pdbx_PDB_ins_code', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.pdbx_PDB_ins_code\' field.' },
+// };
 
 const AtomSiteTestParams: QueryParamInfo = {
     name: 'atom_site',
@@ -99,10 +99,10 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
         }]
     },
     'residueInteraction': {
-        niceName: 'Residues Inside a Sphere',
+        niceName: 'Residue Interaction',
         description: 'Identifies all residues within the given radius from the source residue. Takes crystal symmetry into account.',
         query(p) {
-            const tests = getAtomsTests(p);
+            const tests = getAtomsTests(p.atom_site);
             const center = Queries.combinators.merge(tests.map(test => Queries.generators.atoms({
                 ...test,
                 entityTest: test.entityTest
@@ -114,17 +114,7 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
         structureTransform(p, s) {
             return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
         },
-        params: [
-            AtomSiteParams.entity_id,
-            AtomSiteParams.label_asym_id,
-            AtomSiteParams.auth_asym_id,
-            AtomSiteParams.label_comp_id,
-            AtomSiteParams.auth_comp_id,
-            AtomSiteParams.pdbx_PDB_ins_code,
-            AtomSiteParams.label_seq_id,
-            AtomSiteParams.auth_seq_id,
-            RadiusParam,
-        ]
+        params: [ AtomSiteTestParams, RadiusParam ]
     },
 };
 
@@ -148,30 +138,32 @@ export const QueryList = (function () {
     }
 })();
 
-function _normalizeQueryParams(params: { [p: string]: string }, paramList: QueryParamInfo[]): { [p: string]: string | number | boolean } {
-    const ret: any = {};
-    for (const p of paramList) {
-        const key = p.name;
-        const value = params[key];
-        if (typeof value === 'undefined' || (typeof value !== 'undefined' && value !== null && value['length'] === 0)) {
-            if (p.required) {
-                throw `The parameter '${key}' is required.`;
-            }
-            if (typeof p.defaultValue !== 'undefined') ret[key] = p.defaultValue;
-        } else {
-            switch (p.type) {
-                case QueryParamType.String: ret[key] = value; break;
-                case QueryParamType.Integer: ret[key] = parseInt(value); break;
-                case QueryParamType.Float: ret[key] = parseFloat(value); break;
-            }
-
-            if (p.validation) p.validation(ret[key]);
-        }
-    }
-
-    return ret;
-}
+// function _normalizeQueryParams(params: { [p: string]: string }, paramList: QueryParamInfo[]): { [p: string]: string | number | boolean } {
+//     const ret: any = {};
+//     for (const p of paramList) {
+//         const key = p.name;
+//         const value = params[key];
+//         if (typeof value === 'undefined' || (typeof value !== 'undefined' && value !== null && value['length'] === 0)) {
+//             if (p.required) {
+//                 throw `The parameter '${key}' is required.`;
+//             }
+//             if (typeof p.defaultValue !== 'undefined') ret[key] = p.defaultValue;
+//         } else {
+//             switch (p.type) {
+//                 case QueryParamType.JSON: ret[key] = JSON.parse(value); break;
+//                 case QueryParamType.String: ret[key] = value; break;
+//                 case QueryParamType.Integer: ret[key] = parseInt(value); break;
+//                 case QueryParamType.Float: ret[key] = parseFloat(value); break;
+//             }
+
+//             if (p.validation) p.validation(ret[key]);
+//         }
+//     }
+
+//     return ret;
+// }
 
 export function normalizeQueryParams(query: QueryDefinition, params: any) {
-    return _normalizeQueryParams(params, query.params);
+    return params;
+    //return _normalizeQueryParams(params, query.params);
 }

+ 1 - 1
src/servers/model/server/query.ts

@@ -137,7 +137,7 @@ const _model_server_params: CifWriter.Category<Job> = {
     instance(job) {
         const params: string[][] = [];
         for (const k of Object.keys(job.normalizedParams)) {
-            params.push([k, '' + job.normalizedParams[k]]);
+            params.push([k, JSON.stringify(job.normalizedParams[k])]);
         }
         return {
             data: params,

+ 12 - 1
src/servers/model/server/structure-wrapper.ts

@@ -107,7 +107,10 @@ async function readStructure(key: string, sourceId: string, entryId: string) {
     perf.end('createModel');
 
     perf.start('attachProps');
-    await attachModelProperties(models[0]);
+    const modelProps = attachModelProperties(models[0]);
+    for (const p of modelProps) {
+        await tryAttach(key, p);
+    }
     perf.end('attachProps');
 
     const structure = Structure.ofModel(models[0]);
@@ -128,4 +131,12 @@ async function readStructure(key: string, sourceId: string, entryId: string) {
     };
 
     return ret;
+}
+
+async function tryAttach(key: string, promise: Promise<any>) {
+    try {
+        await promise;
+    } catch (e) {
+        ConsoleLogger.errorId(key, 'Custom prop:' + e);
+    }
 }