From 589fdcec031104fc4d1f73e1ab5dbd536434326a Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 1 Oct 2018 14:18:58 +0200 Subject: [PATCH] model-server: refactoring job API --- src/servers/model/query/atoms.ts | 15 +++---- src/servers/model/server/api-local.ts | 16 ++++--- src/servers/model/server/api-web.ts | 8 +++- src/servers/model/server/api.ts | 60 ++++++++++++++++----------- src/servers/model/server/jobs.ts | 36 +++++++++------- src/servers/model/test.ts | 16 +++++-- 6 files changed, 95 insertions(+), 56 deletions(-) diff --git a/src/servers/model/query/atoms.ts b/src/servers/model/query/atoms.ts index 1e0bd7c31..0901023f9 100644 --- a/src/servers/model/query/atoms.ts +++ b/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[] = []; diff --git a/src/servers/model/server/api-local.ts b/src/servers/model/server/api-local.ts index ff80abade..f1d5e2f1c 100644 --- a/src/servers/model/server/api-local.ts +++ b/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(); diff --git a/src/servers/model/server/api-web.ts b/src/servers/model/server/api-web.ts index 9e1e2b4cd..a141dbfef 100644 --- a/src/servers/model/server/api-web.ts +++ b/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(); }); diff --git a/src/servers/model/server/api.ts b/src/servers/model/server/api.ts index 6c73c776f..ac0e7147c 100644 --- a/src/servers/model/server/api.ts +++ b/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; })(); diff --git a/src/servers/model/server/jobs.ts b/src/servers/model/server/jobs.ts index adba35f1a..4019cc654 100644 --- a/src/servers/model/server/jobs.ts +++ b/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; } diff --git a/src/servers/model/test.ts b/src/servers/model/test.ts index 14bafb1ba..c346381c4 100644 --- a/src/servers/model/test.ts +++ b/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); -- GitLab