Browse Source

model-server: refactoring job API

David Sehnal 6 years ago
parent
commit
589fdcec03

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

@@ -6,8 +6,9 @@
 
 import { QueryPredicate, StructureElement, StructureProperties as Props } from 'mol-model/structure';
 import { AtomsQueryParams } from 'mol-model/structure/query/queries/generators';
+import { AtomSiteSchema } from '../server/api';
 
-export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] {
+export function getAtomsTests(params: AtomSiteSchema): Partial<AtomsQueryParams>[] {
     if (!params) return [{ }];
     if (Array.isArray(params)) {
         return params.map(p => atomsTest(p));
@@ -16,7 +17,7 @@ export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] {
     }
 }
 
-function atomsTest(params: any): Partial<AtomsQueryParams> {
+function atomsTest(params: AtomSiteSchema): Partial<AtomsQueryParams> {
     return {
         entityTest: entityTest(params),
         chainTest: chainTest(params),
@@ -25,13 +26,13 @@ function atomsTest(params: any): Partial<AtomsQueryParams> {
     };
 }
 
-function entityTest(params: any): QueryPredicate | undefined {
-    if (!params || typeof params.entity_id === 'undefined') return void 0;
+function entityTest(params: AtomSiteSchema): QueryPredicate | undefined {
+    if (!params || typeof params.label_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 {
+function chainTest(params: AtomSiteSchema): QueryPredicate | undefined {
     if (!params) return void 0;
 
     if (typeof params.label_asym_id !== 'undefined') {
@@ -45,7 +46,7 @@ function chainTest(params: any): QueryPredicate | undefined {
     return void 0;
 }
 
-function residueTest(params: any): QueryPredicate | undefined {
+function residueTest(params: AtomSiteSchema): QueryPredicate | undefined {
     if (!params) return void 0;
 
     const props: StructureElement.Property<any>[] = [], values: any[] = [];
@@ -78,7 +79,7 @@ function residueTest(params: any): QueryPredicate | undefined {
     return andEqual(props, values);
 }
 
-function atomTest(params: any): QueryPredicate | undefined {
+function atomTest(params: AtomSiteSchema): QueryPredicate | undefined {
     if (!params) return void 0;
 
     const props: StructureElement.Property<any>[] = [], values: any[] = [];

+ 11 - 5
src/servers/model/server/api-local.ts

@@ -12,11 +12,12 @@ import { resolveJob } from './query';
 import { StructureCache } from './structure-wrapper';
 import { now } from 'mol-task';
 import { PerformanceMonitor } from 'mol-util/performance-monitor';
+import { QueryName } from './api';
 
 export type LocalInput = {
     input: string,
     output: string,
-    query: string,
+    query: QueryName,
     modelNums?: number[],
     params?: any,
     binary?: boolean
@@ -30,10 +31,15 @@ export async function runLocal(input: LocalInput) {
 
     for (const job of input) {
         const binary = /\.bcif/.test(job.output);
-        JobManager.add('_local_', job.input, job.query, job.params || { }, {
-            modelNums: job.modelNums,
-            outputFilename: job.output,
-            binary
+        JobManager.add({
+            entryId: job.input,
+            queryName: job.query,
+            queryParams: job.params || { },
+            options: {
+                modelNums: job.modelNums,
+                outputFilename: job.output,
+                binary
+            }
         });
     }
     JobManager.sort();

+ 7 - 1
src/servers/model/server/api-web.ts

@@ -134,7 +134,13 @@ export function initWebApi(app: express.Express) {
         const name = args.name;
         const entryId = args.id;
         const queryParams = args.params || { };
-        const jobId = JobManager.add('pdb', entryId, name, queryParams, { modelNums: args.modelNums, binary: args.binary });
+        const jobId = JobManager.add({
+            sourceId: 'pdb',
+            entryId,
+            queryName: name,
+            queryParams,
+            options: { modelNums: args.modelNums, binary: args.binary }
+        });
         responseMap.set(jobId, res);
         if (JobManager.size === 1) processNextJob();
     });

+ 36 - 24
src/servers/model/server/api.ts

@@ -24,28 +24,33 @@ export interface QueryParamInfo {
     validation?: (v: any) => void
 }
 
-export interface QueryDefinition {
+export interface QueryDefinition<Params = any> {
     name: string,
     niceName: string,
     exampleId: string, // default is 1cbs
     query: (params: any, structure: Structure) => StructureQuery,
     description: string,
     params: QueryParamInfo[],
-    structureTransform?: (params: any, s: Structure) => Promise<Structure>
+    structureTransform?: (params: any, s: Structure) => Promise<Structure>,
+    '@params': Params
 }
 
-// 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.' },
+export interface AtomSiteSchema {
+    label_entity_id?: string,
 
-//     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?: string,
+    auth_asym_id?: string,
 
-//     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_comp_id?: string,
+    auth_comp_id?: string,
+    label_seq_id?: string,
+    auth_seq_id?: string,
+    pdbx_PDB_ins_code?: string,
+
+    label_atom_id?: string,
+    auth_atom_id?: string,
+    type_symbol?: string
+}
 
 const AtomSiteTestParams: QueryParamInfo = {
     name: 'atom_site',
@@ -67,15 +72,19 @@ const RadiusParam: QueryParamInfo = {
     }
 };
 
-const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
-    'full': { niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' },
-    'atoms': {
+function Q<Params = any>(definition: Partial<QueryDefinition<Params>>) {
+    return definition;
+}
+
+const QueryMap = {
+    'full': Q<{} | undefined>({ niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' }),
+    'atoms': Q<{ atom_site: AtomSiteSchema }>({
         niceName: 'Atoms',
         description: 'Atoms satisfying the given criteria.',
         query: p => Queries.combinators.merge(getAtomsTests(p.atom_site).map(test => Queries.generators.atoms(test))),
         params: [ AtomSiteTestParams ]
-    },
-    'symmetryMates': {
+    }),
+    'symmetryMates': Q<{ radius: number }>({
         niceName: 'Symmetry Mates',
         description: 'Computes crystal symmetry mates within the specified radius.',
         query: () => Queries.generators.all,
@@ -83,8 +92,8 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
             return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
         },
         params: [ RadiusParam ]
-    },
-    'assembly': {
+    }),
+    'assembly': Q<{ name: string }>({
         niceName: 'Assembly',
         description: 'Computes structural assembly.',
         query: () => Queries.generators.all,
@@ -98,8 +107,8 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
             exampleValues: ['1'],
             description: 'Assembly name.'
         }]
-    },
-    'residueInteraction': {
+    }),
+    'residueInteraction': Q<{ atom_site: AtomSiteSchema, radius: number }>({
         niceName: 'Residue Interaction',
         description: 'Identifies all residues within the given radius from the source residue. Takes crystal symmetry into account.',
         query(p) {
@@ -116,16 +125,19 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
             return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
         },
         params: [ AtomSiteTestParams, RadiusParam ]
-    },
+    }),
 };
 
-export function getQueryByName(name: string): QueryDefinition {
+export type QueryName = keyof typeof QueryMap
+export type QueryParams<Q extends QueryName> = Partial<(typeof QueryMap)[Q]['@params']>
+
+export function getQueryByName(name: QueryName): QueryDefinition {
     return QueryMap[name] as QueryDefinition;
 }
 
 export const QueryList = (function () {
     const list: { name: string, definition: QueryDefinition }[] = [];
-    for (const k of Object.keys(QueryMap)) list.push({ name: k, definition: <QueryDefinition>QueryMap[k] });
+    for (const k of Object.keys(QueryMap)) list.push({ name: k, definition: <QueryDefinition>QueryMap[k as QueryName] });
     list.sort(function (a, b) { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0 });
     return list;
 })();

+ 21 - 15
src/servers/model/server/jobs.ts

@@ -5,7 +5,7 @@
  */
 
 import { UUID } from 'mol-util';
-import { getQueryByName, normalizeQueryParams, QueryDefinition } from './api';
+import { getQueryByName, normalizeQueryParams, QueryDefinition, QueryName, QueryParams } from './api';
 import { LinkedList } from 'mol-data/generic';
 
 export interface ResponseFormat {
@@ -28,24 +28,31 @@ export interface Job {
     outputFilename?: string
 }
 
-export function createJob(sourceId: '_local_' | string, entryId: string, queryName: string, queryParams: any,
-        options ?: { modelNums?: number[], outputFilename?: string, binary?: boolean }): Job {
-    const queryDefinition = getQueryByName(queryName);
-    if (!queryDefinition) throw new Error(`Query '${queryName}' is not supported.`);
+export interface JobDefinition<Name extends QueryName> {
+    sourceId?: string, // = '_local_',
+    entryId: string,
+    queryName: Name,
+    queryParams: QueryParams<Name>,
+    options?: { modelNums?: number[], outputFilename?: string, binary?: boolean }
+}
+
+export function createJob<Name extends QueryName>(definition: JobDefinition<Name>): Job {
+    const queryDefinition = getQueryByName(definition.queryName);
+    if (!queryDefinition) throw new Error(`Query '${definition.queryName}' is not supported.`);
 
-    const normalizedParams = normalizeQueryParams(queryDefinition, queryParams);
+    const normalizedParams = normalizeQueryParams(queryDefinition, definition.queryParams);
 
     return {
         id: UUID.create(),
         datetime_utc: `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`,
-        key: `${sourceId}/${entryId}`,
-        sourceId,
-        entryId,
+        key: `${definition.sourceId}/${definition.entryId}`,
+        sourceId: definition.sourceId || '_local_',
+        entryId: definition.entryId,
         queryDefinition,
         normalizedParams,
-        responseFormat: { isBinary: !!(options && options.binary) },
-        modelNums: options && options.modelNums,
-        outputFilename: options && options.outputFilename
+        responseFormat: { isBinary: !!(definition.options && definition.options.binary) },
+        modelNums: definition.options && definition.options.modelNums,
+        outputFilename: definition.options && definition.options.outputFilename
     };
 }
 
@@ -56,9 +63,8 @@ class _JobQueue {
         return this.list.count;
     }
 
-    add(sourceId: '_local_' | string, entryId: string, queryName: string, queryParams: any,
-            options?: { modelNums?: number[], outputFilename?: string, binary?: boolean }) {
-        const job = createJob(sourceId, entryId, queryName, queryParams, options);
+    add<Name extends QueryName>(definition: JobDefinition<Name>) {
+        const job = createJob(definition);
         this.list.addLast(job);
         return job.id;
     }

+ 12 - 4
src/servers/model/test.ts

@@ -41,12 +41,20 @@ if (!fs.existsSync(outPath)) fs.mkdirSync(outPath);
 
 async function run() {
     try {
-        // const request = createJob('_local_', 'e:/test/quick/1cbs_updated.cif', 'residueInteraction', { label_comp_id: 'REA' });
-        // const encoder = await resolveJob(request);
-        // const writer = wrapFile('e:/test/mol-star/1cbs_full.cif');
         // const testFile = '1crn.cif'
         const testFile = '1grm_updated.cif'
-        const request = createJob('_local_', path.join(examplesPath, testFile), 'full', {});
+        // const request = createJob({
+        //     entryId: path.join(examplesPath, testFile),
+        //     queryName: 'full',
+        //     queryParams: { }
+        // });
+        const request = createJob({
+            entryId: path.join(examplesPath, testFile),
+            queryName: 'atoms',
+            queryParams: {
+                atom_site: { label_comp_id: 'ALA' }
+            }
+        });
         const encoder = await resolveJob(request);
         const writer = wrapFile(path.join(outPath, testFile));
         encoder.writeTo(writer);