Skip to content
Snippets Groups Projects
Commit 589fdcec authored by David Sehnal's avatar David Sehnal
Browse files

model-server: refactoring job API

parent 6f15c1d5
No related branches found
No related tags found
No related merge requests found
......@@ -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[] = [];
......
......@@ -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 || { }, {
JobManager.add({
entryId: job.input,
queryName: job.query,
queryParams: job.params || { },
options: {
modelNums: job.modelNums,
outputFilename: job.output,
binary
}
});
}
JobManager.sort();
......
......@@ -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();
});
......
......@@ -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;
})();
......
......@@ -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;
}
......
......@@ -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);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment