Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental process namespace support #52

Merged
merged 6 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Experimental support for process namespaces, see [API#348](https://github.com/Open-EO/openeo-api/pull/348).

### Changed

- Internally, a new process registry is used to manage and cache processes.
- `listProcesses` doesn't cache requests any longer.

### Fixed

- Return a better error message if issues with reading batch job results occur in `Job.getResultsAsStac`
- `getAll()` functions return only properties for values that are defined

## [2.0.1] - 2021-07-14

Expand Down Expand Up @@ -60,6 +70,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Arrow functions can be used as callbacks in process graph building.
- Fixed nullable return types in TS declaraion
- Fixed other minor issues in TS declaration

## [1.2.0] - 2021-03-11

Expand Down
226 changes: 162 additions & 64 deletions openeo.d.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"webpack-cli": "^3.3.12"
},
"dependencies": {
"@openeo/js-commons": "^1.2.0",
"@openeo/js-commons": "^1.4.0",
"@radiantearth/stac-migrate": "^1.0.0",
"axios": "^0.21.1",
"oidc-client": "^1.11.5"
Expand Down
2 changes: 1 addition & 1 deletion src/authprovider.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class AuthProvider {
*
* Returns `null` if no access token has been set yet (i.e. not authenticated any longer).
*
* @returns {?string}
* @returns {string | null}
*/
getToken() {
if (typeof this.token === 'string') {
Expand Down
4 changes: 3 additions & 1 deletion src/baseentity.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ class BaseEntity {
let obj = {};
for(let backend in this.apiToClientNames) {
let client = this.apiToClientNames[backend];
obj[client] = this[client];
if (typeof this[client] !== 'undefined') {
obj[client] = this[client];
}
}
return Object.assign(obj, this.extra);
}
Expand Down
130 changes: 79 additions & 51 deletions src/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const Parameter = require('./parameter');
const axios = require('axios').default;
const Utils = require('@openeo/js-commons/src/utils');
const ProcessUtils = require("@openeo/js-commons/src/processUtils");
const ProcessRegistry = require('@openeo/js-commons/src/processRegistry');

const PROCESS_META = [
"id", "summary", "description", "categories", "parameters", "returns",
Expand Down Expand Up @@ -83,7 +84,7 @@ class Builder {
*
* @async
* @static
* @param {?string} version
* @param {?string} [version=null]
* @returns {Promise<Builder>}
* @throws {Error}
*/
Expand All @@ -103,7 +104,7 @@ class Builder {
*
* @async
* @static
* @param {?string} url
* @param {string | null} url
* @returns {Promise<Builder>}
* @throws {Error}
*/
Expand All @@ -117,16 +118,17 @@ class Builder {
*
* Each process passed to the constructor is made available as object method.
*
* @param {Array.<Process>|Processes} processes - Either an array containing processes or an object compatible with `GET /processes` of the API.
* @param {Array.<Process>|Processes|ProcessRegistry} processes - Either an array containing processes or an object compatible with `GET /processes` of the API.
* @param {?Builder} parent - The parent builder, usually only used by the Builder itself.
* @param {string} id - A unique identifier for the process.
*/
constructor(processes, parent = null, id = undefined) {
/**
* List of all process specifications.
* @type {Array.<Process>}
* A unique identifier for the process.
* @public
* @type {string}
*/
this.processes = [];
this.id = id;
/**
* The parent builder.
* @type {?Builder}
Expand All @@ -149,57 +151,75 @@ class Builder {
this.parameters = undefined;

/**
* A unique identifier for the process.
* @public
* @type {string}
* List of all non-namespaced process specifications.
* @type {ProcessRegistry}
*/
this.id = id;
this.processes = null;

if (Utils.isObject(processes) && Array.isArray(processes.processes)) {
processes = processes.processes;
// Create process registry if not available yet
if (processes instanceof ProcessRegistry) {
this.processes = processes;
}
if (Array.isArray(processes)) {
for(let process of processes) {
try {
this.addProcessSpec(process);
} catch (error) {
console.warn(error);
}
}
else if (Utils.isObject(processes) && Array.isArray(processes.processes)) {
this.processes = new ProcessRegistry(processes.processes);
}
else if (Array.isArray(processes)) {
this.processes = new ProcessRegistry(processes);
}
else {
throw new Error("Processes are invalid; must be array or object according to the API.");
}

// Create processes
this.processes.all().forEach(process => this.createFunction(process));
}

/**
* Creates a callable function on the builder object for a process.
*
* @param {Process} process
* @throws {Error}
*/
createFunction(process) {
if (typeof this[process.id] !== 'undefined') {
throw new Error("Can't create function for process '" + process.id + "'. Already exists in Builder class.");
}

/**
* Implicitly calls the process with the given name on the back-end by adding it to the process.
*
* This is a shortcut for {@link Builder#process}.
*
* @param {...*} args - The arguments for the process.
* @returns {BuilderNode}
* @see Builder#process
*/
this[process.id] = function(...args) {
// Don't use arrow functions, they don't support the arguments keyword.
return this.process(process.id, args);
};
}

/**
* 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') {
this.processes.push(process);
/**
* Implicitly calls the process with the given name on the back-end by adding it to the process.
*
* This is a shortcut for {@link Builder#process}.
*
* @param {...*} args - The arguments for the process.
* @returns {BuilderNode}
* @see Builder#process
*/
this[process.id] = function(...args) {
// Don't use arrow functions, they don't support the arguments keyword.
return this.process(process.id, args);
};

if (!namespace) {
namespace = 'backend';
}
else {
throw new Error("Can't create function for process '" + process.id + "'. Already exists in Builder class.");
this.processes.add(process, namespace);

// Create callable function for pre-defined processes
if (namespace === 'backend') {
this.createFunction(process);
}
}

Expand Down Expand Up @@ -281,13 +301,14 @@ class Builder {
}

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

/**
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 Boolean(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.
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
* @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, args = {}, description = null) {
let node = new BuilderNode(this, processId, args, description);
let namespace = null;
if (processId.includes('@')) {
let rest;
[processId, ...rest] = processId.split('@');
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
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
17 changes: 13 additions & 4 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,8 +27,8 @@ class BuilderNode {
* @type {Process}
* @readonly
*/
this.spec = this.parent.spec(processId);
if (!Utils.isObject(this.spec)) {
this.spec = this.parent.spec(processId, processNamespace);
if (!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
5 changes: 2 additions & 3 deletions src/builder/parameter.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,14 @@ class Parameter {
*/
set(target, name, value, receiver) {
if (!Reflect.has(target, name)) {
console.warn('Simplified array access is read-only');
throw new Error('Simplified array access is read-only');
}
return Reflect.set(target, name, value, receiver);
}
});
}
else {
console.warn('Simplified array access not supported, use array_element directly');
return parameter;
throw new Error('Simplified array access not supported, use array_element directly');
}
}

Expand Down
14 changes: 11 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 === '@');
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
}

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
2 changes: 1 addition & 1 deletion src/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class Capabilities {
/**
* Get the billing currency.
*
* @returns {?string} The billing currency or `null` if not available.
* @returns {string | null} The billing currency or `null` if not available.
*/
currency() {
return (Utils.isObject(this.data.billing) && typeof this.data.billing.currency === 'string' ? this.data.billing.currency : null);
Expand Down
Loading