Skip to content

Commit

Permalink
Experimental process namespace support Open-EO/openeo-api#348
Browse files Browse the repository at this point in the history
  • Loading branch information
m-mohr committed Aug 5, 2021
1 parent 203a091 commit aac1a11
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 58 deletions.
66 changes: 46 additions & 20 deletions openeo.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1346,12 +1346,13 @@ declare module OpenEO {
* Operators: - (subtract), + (add), / (divide), * (multiply), ^ (power)
*
* It supports all mathematical functions (i.e. expects a number and returns a number) the back-end implements, e.g. `sqrt(x)`.
* For namespaced processes, use for example `process#namespace(x)` - EXPERIMENTAL!
*
* Only available if a builder is specified in the constructor:
* You can refer to output from processes with a leading `#`, e.g. `#loadco1` if the node to refer to has the key `loadco1`.
*
* Only available if a parent node is set via `setNode()`:
* Parameters can be accessed simply by name.
* Parameters can be accessed simply by name.
* If the first parameter is a (labeled) array, the value for a specific index or label can be accessed by typing the numeric index or textual label with a `$` in front, for example `$B1` for the label `B1` or `$0` for the first element in the array. Numeric labels are not supported.
* You can access subsequent parameters by adding additional `$` at the beginning, e.g. `$$0` to access the first element of an array in the second parameter, `$$$0` for the same in the third parameter etc.
*
Expand Down Expand Up @@ -1430,8 +1431,9 @@ declare module OpenEO {
* @param {string} processId
* @param {object.<string, *>} [processArgs={}]
* @param {?string} [processDescription=null]
* @param {?string} [processNamespace=null]
*/
constructor(parent: Builder, processId: string, processArgs?: any, processDescription?: string | null);
constructor(parent: Builder, processId: string, processArgs?: any, processDescription?: string | null, processNamespace?: string | null);
/**
* The parent builder.
* @type {Builder}
Expand All @@ -1448,6 +1450,11 @@ declare module OpenEO {
* @type {string}
*/
id: string;
/**
* The namespace of the process - EXPERIMENTAL!
* @type {string}
*/
namespace: string;
/**
* The arguments for the process.
* @type {object.<string, *>}
Expand Down Expand Up @@ -1508,7 +1515,7 @@ declare module OpenEO {
*
* @protected
* @param {?BuilderNode} [parentNode=null]
* @param {?string} parentParameter
* @param {?string} [parentParameter=null]
* @returns {BuilderNode}
*/
protected createBuilder(parentNode?: BuilderNode | null, parentParameter?: string | null): BuilderNode;
Expand Down Expand Up @@ -1608,7 +1615,7 @@ declare module OpenEO {
*
* @async
* @static
* @param {?string} version
* @param {?string} [version=null]
* @returns {Promise<Builder>}
* @throws {Error}
*/
Expand Down Expand Up @@ -1637,10 +1644,17 @@ declare module OpenEO {
*/
constructor(processes: Array<Process> | Processes, parent?: Builder | null, id?: string);
/**
* List of all process specifications.
* List of all non-namespaced process specifications.
* @type {Array.<Process>}
*/
processes: Array<Process>;
/**
* Namespaced process specifications. EXPERIMENTAL!
* @type {Object.<string, Array.<Process>>}
*/
namespacedProcesses: {
[x: string]: Array<Process>;
};
/**
* The parent builder.
* @type {?Builder}
Expand Down Expand Up @@ -1670,9 +1684,10 @@ declare module OpenEO {
* Adds a process specification to the builder so that it can be used to create a process graph.
*
* @param {Process} process - Process specification compliant to openEO API
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @throws {Error}
*/
addProcessSpec(process: Process): void;
addProcessSpec(process: Process, namespace?: string | null): void;
/**
* Sets the parent for this Builder.
*
Expand Down Expand Up @@ -1705,12 +1720,13 @@ declare module OpenEO {
*/
addParameter(parameter: any, root?: boolean): void;
/**
* Returns the process specification for the given process identifier.
* Returns the process specification for the given process identifier and namespace.
*
* @param {string} id
* @param {string} id - Process identifier
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @returns {Process}
*/
spec(id: string): Process;
spec(id: string, namespace?: string | null): Process;
/**
* Adds a mathematical formula to the process.
*
Expand All @@ -1723,18 +1739,19 @@ declare module OpenEO {
*/
math(formula: string): BuilderNode;
/**
* Checks whether a process with the given id is supported by the back-end.
* Checks whether a process with the given id and namespace is supported by the back-end.
*
* @param {string} processId - The id of the process to call.
* @param {string} processId - The id of the process.
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @returns {boolean}
*/
supports(processId: string): boolean;
supports(processId: string, namespace?: string | null): boolean;
/**
* Adds another process call to the process chain.
*
* @param {string} processId - The id of the process to call.
* @param {object.<string, *>|Array} args - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process.
* @param {?string} description - An optional description for the process call.
* @param {string} processId - The id of the process to call. To access a namespaced process, use the `process@namespace` notation.
* @param {object.<string, *>|Array} [args={}] - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process.
* @param {?string} [description=null] - An optional description for the process call.
* @returns {BuilderNode}
*/
process(processId: string, args?: any | any[], description?: string | null): BuilderNode;
Expand Down Expand Up @@ -1872,27 +1889,32 @@ declare module OpenEO {
*/
listCollectionItems(collectionId: string, spatialExtent?: Array<number> | null, temporalExtent?: Array<any> | null, limit?: number | null): AsyncGenerator<any, void, unknown>;
/**
* List all processes available on the back-end.
* List processes available on the back-end.
*
* Requests pre-defined processes by default.
* Set the namespace parameter to request processes from a specific namespace.
*
* Data is cached in memory.
* Pre-defined processes are cached in memory.
*
* @async
* @param {?string} [namespace=null] - Namespace of the processes (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @returns {Promise<Processes>} - A response compatible to the API specification.
* @throws {Error}
*/
listProcesses(): Promise<Processes>;
listProcesses(namespace?: string | null): Promise<Processes>;
/**
* Get information about a single process.
*
* @async
* @param {string} processId - Collection ID to request further metadata for.
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @returns {Promise<?Process>} - A single process as object, or `null` if none is found.
* @throws {Error}
* @see Connection#listProcesses
*/
describeProcess(processId: string): Promise<Process | null>;
describeProcess(processId: string, namespace?: string | null): Promise<Process | null>;
/**
* Returns an object to simply build user-defined processes.
* Returns an object to simply build user-defined processes based upon pre-defined processes.
*
* @async
* @param {string} id - A name for the process.
Expand Down Expand Up @@ -2572,6 +2594,10 @@ declare module OpenEO {
export type Processes = {
processes: Array<Process>;
links: Array<Link>;
/**
* EXPERIMENTAL!
*/
namespaces: Array<string> | null;
};
/**
* An openEO processing chain.
Expand Down
60 changes: 44 additions & 16 deletions src/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Builder {
*
* @async
* @static
* @param {?string} version
* @param {?string} [version=null]
* @returns {Promise<Builder>}
* @throws {Error}
*/
Expand Down Expand Up @@ -123,10 +123,15 @@ class Builder {
*/
constructor(processes, parent = null, id = undefined) {
/**
* List of all process specifications.
* List of all non-namespaced process specifications.
* @type {Array.<Process>}
*/
this.processes = [];
/**
* Namespaced process specifications. EXPERIMENTAL!
* @type {Object.<string, Array.<Process>>}
*/
this.namespacedProcesses = {};
/**
* The parent builder.
* @type {?Builder}
Expand Down Expand Up @@ -176,13 +181,20 @@ class Builder {
* Adds a process specification to the builder so that it can be used to create a process graph.
*
* @param {Process} process - Process specification compliant to openEO API
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @throws {Error}
*/
addProcessSpec(process) {
addProcessSpec(process, namespace = null) {
if (!Utils.isObject(process)) {
throw new Error("Process '" + process.id + "' must be an object.");
}
if (typeof this[process.id] === 'undefined') {
if (namespace && namespace !== 'backend' && namespace !== 'user') {
if (!this.namespacedProcesses[namespace]) {
this.namespacedProcesses[namespace] = [];
}
this.namespacedProcesses[namespace].push(process);
}
else if (typeof this[process.id] === 'undefined') {
this.processes.push(process);
/**
* Implicitly calls the process with the given name on the back-end by adding it to the process.
Expand Down Expand Up @@ -281,13 +293,22 @@ class Builder {
}

/**
* Returns the process specification for the given process identifier.
* Returns the process specification for the given process identifier and namespace.
*
* @param {string} id
* @param {string} id - Process identifier
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @returns {Process}
*/
spec(id) {
return this.processes.find(process => process.id === id);
spec(id, namespace = null) {
if (namespace && namespace !== 'backend' && namespace !== 'user') {
if (!this.namespacedProcesses[namespace]) {
return;
}
return this.namespacedProcesses[namespace].find(process => process.id === id);
}
else {
return this.processes.find(process => process.id === id);
}
}

/**
Expand All @@ -308,25 +329,32 @@ class Builder {
}

/**
* Checks whether a process with the given id is supported by the back-end.
* Checks whether a process with the given id and namespace is supported by the back-end.
*
* @param {string} processId - The id of the process to call.
* @param {string} processId - The id of the process.
* @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
* @returns {boolean}
*/
supports(processId) {
return Utils.isObject(this.spec(processId));
supports(processId, namespace = null) {
return Utils.isObject(this.spec(processId, namespace));
}

/**
* Adds another process call to the process chain.
*
* @param {string} processId - The id of the process to call.
* @param {object.<string, *>|Array} args - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process.
* @param {?string} description - An optional description for the process call.
* @param {string} processId - The id of the process to call. To access a namespaced process, use the `process@namespace` notation.
* @param {object.<string, *>|Array} [args={}] - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process.
* @param {?string} [description=null] - An optional description for the process call.a
* @returns {BuilderNode}
*/
process(processId, args = {}, description = null) {
let node = new BuilderNode(this, processId, args, description);
let namespace = null;
if (processId.includes('@')) {
let rest;
[processId, ...rest] = processId.split('@');
namespace = rest.join('@');
}
let node = new BuilderNode(this, processId, args, description, namespace);
this.nodes[node.id] = node;
return node;
}
Expand Down
1 change: 1 addition & 0 deletions src/builder/formula.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const BuilderNode = require('./node');
* Operators: - (subtract), + (add), / (divide), * (multiply), ^ (power)
*
* It supports all mathematical functions (i.e. expects a number and returns a number) the back-end implements, e.g. `sqrt(x)`.
* For namespaced processes, use for example `process#namespace(x)` - EXPERIMENTAL!
*
* Only available if a builder is specified in the constructor:
* You can refer to output from processes with a leading `#`, e.g. `#loadco1` if the node to refer to has the key `loadco1`.
Expand Down
15 changes: 12 additions & 3 deletions src/builder/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class BuilderNode {
* @param {string} processId
* @param {object.<string, *>} [processArgs={}]
* @param {?string} [processDescription=null]
* @param {?string} [processNamespace=null]
*/
constructor(parent, processId, processArgs = {}, processDescription = null) {
constructor(parent, processId, processArgs = {}, processDescription = null, processNamespace = null) {
/**
* The parent builder.
* @type {Builder}
Expand All @@ -26,7 +27,7 @@ class BuilderNode {
* @type {Process}
* @readonly
*/
this.spec = this.parent.spec(processId);
this.spec = this.parent.spec(processId, processNamespace);
if (!Utils.isObject(this.spec)) {
throw new Error("Process doesn't exist: " + processId);
}
Expand All @@ -36,6 +37,11 @@ class BuilderNode {
* @type {string}
*/
this.id = parent.generateId(processId);
/**
* The namespace of the process - EXPERIMENTAL!
* @type {string}
*/
this.namespace = processNamespace;
/**
* The arguments for the process.
* @type {object.<string, *>}
Expand Down Expand Up @@ -176,7 +182,7 @@ class BuilderNode {
*
* @protected
* @param {?BuilderNode} [parentNode=null]
* @param {?string} parentParameter
* @param {?string} [parentParameter=null]
* @returns {BuilderNode}
*/
createBuilder(parentNode = null, parentParameter = null) {
Expand Down Expand Up @@ -228,6 +234,9 @@ class BuilderNode {
process_id: this.spec.id,
arguments: {}
};
if (this.namespace) {
obj.namespace = this.namespace;
}
for(let name in this.arguments) {
if (typeof this.arguments[name] !== 'undefined') {
obj.arguments[name] = this.exportArgument(this.arguments[name], name);
Expand Down
15 changes: 12 additions & 3 deletions src/builder/tapdigit.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,12 @@ TapDigit.Lexer = function () {
return (ch === '_') || (ch === '#') || (ch === '$') || isLetter(ch);
}

function isIdentifierPart(ch) {
return (ch === '_') || isLetter(ch) || isDecimalDigit(ch);
function isAdditionalNamespaceChar(ch) {
return (ch === '-') || (ch === '.') || (ch === '~') || (ch === '@');
}

function isIdentifierPart(ch, ns = false) {
return (ch === '_') || isLetter(ch) || isDecimalDigit(ch) || (ns && isAdditionalNamespaceChar(ch));
}

function scanIdentifier() {
Expand All @@ -130,6 +134,7 @@ TapDigit.Lexer = function () {
}

let id = getNextChar();
let ns = false;
while (true) {
let ch = peekNextChar();
// If the first character is a $, it is allowed that more $ follow directly after
Expand All @@ -138,7 +143,10 @@ TapDigit.Lexer = function () {
startCh = ''; // Stop allowing $ once the first non-$ has been found
} // else: allowed
}
else if (!isIdentifierPart(ch)) {
else if (ch === '@') {
ns = true;
}
else if (!isIdentifierPart(ch, ns)) {
break;
}
id += getNextChar();
Expand Down Expand Up @@ -357,6 +365,7 @@ TapDigit.Parser = function () {
if (matchOp(token, '(')) {
lexer.next();
expr = parseExpression();
console.log(expr);
token = lexer.next();
if (!matchOp(token, ')')) {
throw new SyntaxError('Expecting )');
Expand Down
Loading

0 comments on commit aac1a11

Please sign in to comment.